praktikada-azure-durable-funksiyalar-ve-chaining-patterni

Praktikada Azure Durable funksiyalar və Chaining patterni

“The Developer voice in .NET” seriyası üzrə məqalələrimizi davam etdirərək, bu dəfə sizə Azure Durable funksiyalar və onların praktikada istifadə etdiyimiz application patternlərindən danışacağıq. Bu seriya məqalələrimizdə indiyə kimi yazdığımız postları aşağıdakı sıra ilə oxuya bilərsiniz:

  1. Apache Axis Servisin .NET CORE-da istifadəsi
  2. N-Layered Arxitekturada istisna hallarının emalı
  3. Praktikada Azure Funksiyalarla N-Layered Arxitektura


Durable funksiyalar nədir?

               Durable funksiyalar Azure funksiyaların bir əlavəsidir. Onlar vasitəsilə stateful funksiyalar yaza bilirik. Durable funksiyalar adətən icrası uzun çəkən (long running operations) və addım –addım icra olunduqdan sonra nəticəsi step-by-step müəyyən olunan funksionallığı icra edən zaman istifadə olunur. Durable funksiyalar vasitəsilə aşağıdakı proqramlaşdırma modelləri istifadə edə bilərik:

1)     Stateful workflowlar üçün – bunun üçün orchestrator funksiyalar var.

2)     Stateful entitilər üçün – bunun üçün entity funksiyalar var.

Bu məqaləmiz orchestrator funksiyalar və onların function chaining patterninə həsr olunub.

Məqaləyə keçməzdən əvvəl real pratikamiz üçün real Acceptance Criteria ilə tanış olaq:

Function should return the following response:

 1)Successful response with Status URL

 2)If Source Folder does not exist or Destination Folder does not exist

Response Message: "Source Folder {name} does not exist." or "Destination Folder {name} does not exist

Function is in Progress

1)Response Message: "Sub-Folders moved: {Funding, Renewals, ...} ; Sub-Folder move in progress: {Tax Administration}"

 Function is in a completed state

1)Response Message: "Source folder {name} documents successfully moved with {Merge} Attribute Option. Sub-Folders moved: {Funding, Renewals, ...}"

 2)Destination Sub-Folder does not exist:

3)Accumulate Error with folder name to show in the Response

4)Response Message: "Destination Folders {Funding, Renewals, ...} do not exist; Documents from those folders are not moved"

5)Exception occurred during HomeVault call:

6)Response Message: "Error in HomeVault call {methodName}. Folders moved: {List of Folders that are moved}; Error details: {Error details}"

7)After successful move All Folders and Documents from Source Folder should exist in the Destination Folder

8)After successful move Folders and Documents should NOT exist in the Source

9)When ALL documents from Sub-Folder are moved, empty Sub-Folder should be deleted

10)When ALL Folders and Documents are moved without any error, empty Source Folder should be deleted

11)Level 1 Folders from Source should exist in the Destination:

12)Level 1 Sub-Folders from Source should be created in the Destination

 

Qısaca ifadə etsək, bir qovluqdakı bütün qovluq, altqovluq və faylların digər qovluğa köçürülməsi üçün Azure funksiya yazmaq lazımdır. Amma Əgər ana qovluğun içindəki 1 LEVEL qovluqlar destinationda yoxdursa ozaman köçürmə uğursuz olmalıdır. Yanlız 1 LEVEL qovluq olduqdan sonra köçürməyə icazə var. 2 və sonrakı LEVEL qovluqlar olmadıqda onu özümüz yaradırıq.

Qeyd edək ki, digər templatelər kimi, Azure funksiyaları da realizə etmək üçün visual studioda hazır template toplusu var. Bununçün şablonlar bölməsindən Azure functions seçmək lazımdır.

       Azure Durable funksiyalarda əsas iş birbaşa HttpStart funksiyası(original çağırılma adı FunctionName atributu verilib. Burada” MoveFolder”) ilə başlayır. Bu funksiyaya bir növ orchestration üçün entry point olaraq baxa bilərik. Funksiya işə düşərkən application environment tərəfindən bir sıra default argumentlərlə təmin olunur. Bu arqumentlər imkan verirki , durable funksiya lazımi parametrləri alıb lazımi destinationlara(funksiyalara) versin. Funksiyanın qəbul etdiyi argumentlər:

1)     HttpTrigger atributu ilə mark olunmuş və HttpRequestMessage tipində dəyişən vasitəsilə çöldən gələn parametrləri HTTP sorğusu əsasında alıb emal edə bilirik. Həmçinin sorğunun HTTP POST olduğu və endpoint adresi buradan müəyyənləşir.

2)     DurableClient atributu ilə mark olunmuş IDurableOrchestrationClient interfeysli obyekt vasitəsilə durable funksiyanın lifecyclenı idarə etmək olur. Maraqlı məqam ondan ibarətdir ki, bu obyekti başqa bir classa paketləyib activity-lərə(az sorna danışacağıq bunlar haqda) vermək olmur.

 [FunctionName("MoveFolder")]
        public static async Task HttpStart(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = "documents/folder/move")] HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            try
            {
                string stringContent = await req.Content.ReadAsStringAsync();

                JObject obj = JObject.Parse(stringContent);

                var request = obj.ToObject();

                log.LogInformation($"Request received: {stringContent}");

                string instanceId = await starter.StartNewAsync("OriginVaultMoveFolderDurableFunctions", request);

                log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

               return new HttpResponseMessageResult(starter.CreateCheckStatusResponse(req, instanceId));
            }
            catch (Exception ex)
            {
                log.LogError(ex, "MoveDocument Failed!");
                return new HttpResponseMessageResult(System.Net.HttpStatusCode.BadRequest,new { ex.Message });
            }
        }

Orchestrator entry pointinin əsas işi parametrləri qəbul edib əsas Orchestratora ötürməkdən ibarətdir. Burada əsas Orchestrator “OriginVaultMoveFolderDurableFunctions”dır.HttpStart gələn json informasiyanı obyektə çevirdikdən sonra sadəcə starter.StartNewAsync metodunu çağıraraq əsas orchestratoru işə salır. O isə kod daxilində RunOrchestrator adı altında işə düşür. Digər bütün elementləri də idarə edən məhz bu funksiyadır.

 [FunctionName("OriginVaultMoveFolderDurableFunctions")]
        public  async Taskstring>> RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var output = new List<string>();
           try
            {
                //entry point orchestratorun verdiyi obyekti almaq üçün bu metodu çağırırıq
                var request = context.GetInput();
               
                context.SetCustomStatus("Checking if can start moving files from Source to Destination...");
               
                var startResponse = await context.CallActivityAsync("HV_CanStartMoving", request);
                output.AddRange(startResponse.Messages);
                if(startResponse.StartMoveDocument.CanStartMoving())
                {
                    context.SetCustomStatus("Request approved. Can Start moving from Source to Destination");

                    context.SetCustomStatus("Preparing for getting documents from the folder...");

                    var getItemsResponse = await context.CallActivityAsync("HV_GetItemsInFolder"new GetItemsInFolderRequest
                    {
                        SourceSystem = request.SourceSystem
                        , SourceFolderId = startResponse.StartMoveDocument.SourceFolderId
                        , FolderName = request.Document.Source.RelativeFolderPath
                    });
                    output.AddRange(getItemsResponse.Messages);
                    if(!getItemsResponse.DocumentListResponse.IsFolderEmpty())
                    {
                        context.SetCustomStatus("Got source folder's documents. Preparing to move...");
                        var rsp = new MoveFilesRequest
                        {
                            Request = getItemsResponse.DocumentListResponse,
                            MoveFolderData = request
                        };
                        var _output = await context.CallSubOrchestratorAsyncstring>>("HV_MoveFiles", rsp);
                        output.AddRange(_output);
                    }
                    else
                    {
                        string message = "Folder Empty. Nothing to move!";
                        context.SetCustomStatus(message);
                        output.Add(message);
                    }
                   
                }
                else
                {
                    context.SetCustomStatus("Error : Request declined. Can't start moving. Source or Destination are incorrect!");
                }
                    
            }
            catch(ServiceException exp)
            {
                output.Add(exp.Message);
                string validationErrors = exp.GetErrrorsAsString();
                if (validationErrors.Length > 0)
                    output.Add(validationErrors);
            }
            catch(Exception exp)
            {
                output.Add(exp.Message);
                _logger.LogError(exp.Message);
            }
            context.SetCustomStatus("Document moving completed...");
            return output;
        }

OriginVaultMoveFolderDurableFunctions funksiyası əsas orchestrator olduğuna görə OrchestratorTrigger ilə mark olunub və yenə IDurableOrchestratorClient interfeysi ilə işləyir. Entry point tərəfindən işə salınır və entry pointin verdiyi paramterləri almaq üçün öz interfeysinin Getİnput() metodunu istifadə edir.

      IDurableOrchestratorClient məlumatları 2 formada istifadəçiyə göstərməyə imkan verir: output customStatus vasitəsilə. Adəti üzrə məlumatları göstərmək üçün outputdan istifadə edirik. Sadəcə bəzən lazım olduqda output ilə birgə SetCustomStatus metodunu istifadə edərək öz müəyyənləşdirdiyimiz status xarakterli mesajları da ekranda göstərə bilirik.

       Orchestrator öz daxilində bir neçə application pattern forması istifadə edərək lifecycle-ni müyyən edə bilir. Taskdan asılı olaraq bu patternləri mixed formada və ya pattern by pattern istifadə edə bilərik. Durable functionlar aşağıdakı application patternləri sitfadə edir:

1)Function chaining

2)Fan-out/fan-in

3)Async HTTP APIs

4)Monitoring

5)Human interaction

6)Aggregator (stateful entities)

                                       Function chaining patterni

Bu patterndə funksiyalar zəncirvari formada, biri digərini gözləməklə işə düşür. Bir funksiya işini bitirib müəyyən response verdikdən sonra, adəti üzrə bu responsu digər funksiya request oalraq qəbul edir və bu proses bu cür davam edir. Patternin adı da buradan gəlir. Bu mexanizm Asp.NET Core-da middlewarelərin işləmə prinsipi ilə eynidir.

Function chaining numunəsinə RunOrchestrator funksiyamızda da rast gəlmək olar. Nəzər yetirsək, ilk öncə CallActivityAsync metodu ilə HV_CanStartMoving funksiyamız işə salınır. Həmin funksiya qovluqların köçürülmə prosesinə hazırlığını yoxlayır və bizə source və destination folderlərin identifikatorlarını obyekt olaraq qaytarır.

        [FunctionName("HV_CanStartMoving")]
        public async Task CanStartMoving([ActivityTrigger] MoveFolderData moveFolderData)
        {
            var output = new StartMoveDocumentOutput();
            try 
            {
                output.StartMoveDocument = await _moveFolderProvider.CanStartMoving(moveFolderData);
                output.AddMessage(moveFolderData.GenerateSuccessOutputMessage());
            }
            catch(Exception exp)
            {
                output.AddMessage(moveFolderData.GenerateExceptionMessage(exp));
            }
            return output;
        }

Daha sonra bu funksiyadan alınan cavab HV_GetItemsInFolder funkyasına arqument olaraq verilir. Gördüyümüz kimi, bir funksiyanın nəticəsini obiri funksiya giriş parametri olaraq istifadə edir.

       [FunctionName("HV_GetItemsInFolder")]
        public async Task GetItemsInFolder([ActivityTrigger] GetItemsInFolderRequest request)
        {
            var output = new DocumentListOutput();
            output.AddMessage($"Trying to get documents from `{request.FolderName}` folder...");
            try
            {
                output.DocumentListResponse = await _moveFolderProvider.GetItemsInFolderAsync(request.SourceSystem, request.SourceFolderId);
                output.AddMessage($"{output.GetItemsCount()} {request.GenerateSuccessOutputMessage()}");
            }
            catch(Exception exp)
            {
                output.AddMessage(request.GenerateExceptionMessage(exp));
            }
            return output;
        }


Yekunda POSTMAN-la Gonderilen sorgu ilə tanış olaq:

{
       "SourceSystem": "XXXXX",
       "Document": {
                       "MoveOption": "Merge",
                       "Source": {
                                       "BaseFolderPath": "Origin - Residential Working",
                                       "BaseFolderId": "1313131",
                                       "RelativeFolderPath": "/10009431"
                       },
                       "Destination": {
                                       "BaseFolderPath": "Origin - Residential Working",
                                       "BaseFolderId": "1313131",
                                       "RelativeFolderPath": "/10000"
                       }
       }
}

Alinan cavabın ilk hissəsində Durable funksiyalar bizə belə bir linklər toplusu və identifikator qaytarır

{
    "id": "ce50495bc31a4f5fb82e956e22f5ca63",
    "statusQueryGetUri": "http://localhost:7076/runtime/webhooks/durabletask/instances/ce50495bc31a4f5fb82e956e22f5ca63?taskHub=TestHubName&connection=Storage&code=8x2IdVNL9z5Bu3k3bH93VxXnr03QTbWE0ECeXTViB/2m8pCG28cbRQ==",
    "sendEventPostUri": "http://localhost:7076/runtime/webhooks/durabletask/instances/ce50495bc31a4f5fb82e956e22f5ca63/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=8x2IdVNL9z5Bu3k3bH93VxXnr03QTbWE0ECeXTViB/2m8pCG28cbRQ==",
    "terminatePostUri": "http://localhost:7076/runtime/webhooks/durabletask/instances/ce50495bc31a4f5fb82e956e22f5ca63/terminate?reason={text}&taskHub=TestHubName&connection=Storage&code=8x2IdVNL9z5Bu3k3bH93VxXnr03QTbWE0ECeXTViB/2m8pCG28cbRQ==",
    "purgeHistoryDeleteUri": "http://localhost:7076/runtime/webhooks/durabletask/instances/ce50495bc31a4f5fb82e956e22f5ca63?taskHub=TestHubName&connection=Storage&code=8x2IdVNL9z5Bu3k3bH93VxXnr03QTbWE0ECeXTViB/2m8pCG28cbRQ=="
}

 Buradan statusQueryGetUri ilə Durable funkiyanın addım addım işləməyini və hər request verdikdə cavabı hazırlaması ilə tanış ola bilərik. Həmin url-ə keçdikdə belə bir görünüş əldə edirik:

[
    "Source and Destination are OK. Can Start Moving Files...",
    "Trying to get documents from `/10009431` folder...",
    "4 Document(s) detected inside `/10009431` folder.",
    "Move Files successfully started...",
    "Warning : An item with the name '3.txt' already exists.. Can't move `3.txt` Document to `/10000`.",
    "Warning : An item with the name '2.txt' already exists.. Can't move `2.txt` Document to `/10000`.",
    "Preparing to find folder : `/Test3` inside Destination...",
    "/Test3 exists in destination!",
    "Trying to get documents from `Test3` folder...",
    "2 Document(s) detected inside `Test3` folder.",
    "Warning : An item with the name 'Test4' already exists.. Can't move `Test4` Folder to `/10000/Test3`.",
    "Warning : An item with the name '2.txt' already exists.. Can't move `2.txt` Document to `/10000/Test3`.",
    "Preparing to find folder : `/Test9` inside Destination...",
    "Warning : ./Test9 doesn't exist in destination!!!"
]

Yuxarıda görünən mesajları özümüz outout gathering ilə hazırlamışıq və növbəti məqalələrdə bu haqda ətraflı danışacağıq.

Koda qayıdacaq olsaq, əgər nəzərdə tutulan biznes tələblər ödənilərsə ozaman CallSubOrchestratorAsync funksiyası vasitəsilə alt orkestreytor çağrılır. SubOrchestratorlar Durable function arxitekturası quran zaman ortaya çıxan, birdən artıq root orchestrator imkanı lazım olan istifadə olunan mexanizmdir. Bizim nümunədə faylların bir yerdən digər yerə köçürülməsi prosesi suborchestrator funksiyasına ayrılıb. Tapşırığın əsas kod bloku məhz bu funksiya vasitəsilə yerinə yetirilir. Növbəti məqalələrimizdə bu yazının davamı olaraq fan-in fan-out application patterni, output mesajların toplanma imkani, statusun yaradılma variantı ilə tanış ola biləcəksiniz.


PS: Məqalələrimizi bəyənirsinizsə, zəhmət olmasa məqalənin yuxarısındakı düymələr vasitəsilə müxtəlif sosial şəbəkələrdə yazılarımızı paylaşaraq bizə dəstək ola bilərsiniz. 

 

 

Tural

Tural Süleymani

Süleymani Tural Microsoft-un MCSD statuslu mütəxəssisidir, 2008-ci ildən bu yana proqramlaşdırma üzrə tədris aparır

Müəllifin bu dildə ən son postları

Bu yazıları da bəyənə bilərsiniz