Part 2 – Build azure function to process the messages in Azure Service topic
5 mins read

Part 2 – Build azure function to process the messages in Azure Service topic

In this second part of my three part series, I am going to list down steps to build an azure function to process Confirmed Purchased orders which which we are receiving in Azure Service topic. We deployed our topic with two subscriptions to essentially build multiple messaging pipelines for different consuming applications. The idea is to demonstrate how Azure cloud messaging service can help architects and developer building a messaging platform for multiple target applications and hence avoiding P2P integrations. Microsoft Azure provides cloud messaging services (MaaS) which scale up and and scale out on demand and easy to operate and maintain.

Scenario

In the Part 1, we configured an OOTB business event with a service topic having multiple subscriptions. In this part, we are assuming a real world scenario. When a PO is confirmed, it is ready to be fulfilled by the warehouse. Suppose the customer has an external warehouse management system that accepts a data through its Web API. Here are the high level steps to achieve our goal.

  • Build an Azure function with input binding to trigger when a message is received in the service bus topic subscription.
  • Parse the message to extract purchase orders details.
  • Extract the purchase order information needed for the external warehousing application.
  • Construct a web API call to Post and update to the external warehousing application via its Web API.

Note: The external web API that I am using in this here is outside the scope of this post. I already have a pre-build API that I’ll be utilizing here for the demo purposes. If you want to learn how to build a web API real quick using Azure functions, please refer to my earlier blog post here.

How to build REST APIs with Azure functions

Prerequisites:

  • Access to Azure subscription with permissions to create azure functions. The monthly Visual studio developer subscription would work.
  • Some foundational and working knowledge of Azure function. If you are new to this, please refer to the following Microsoft documentation for a quick start. https://docs.microsoft.com/en-us/azure/azure-functions/
  • Visual Studio (community edition or Visual Studio code would work)

Build the Azure function Web API

In visual Studio 2019, ensure you have installed the Azure Workload while installing the VS. if you haven’t done it, you can always do it by running the installation process again. Here is the docs reference.

https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio

Create a new Project of Type Azure function.

Give a meaningful name to the project and click Create. I named it BusinessEventsFunctionApp.

Now right click on Project and add a new Azure function.

Select HTTP Trigger input binding and click OK.

Now before we go into the details of function’s code structure, add a class to parse JSON that we receive from Service bus. Define usual Getters and Setters.

    public class PurchaseOrder
    {
        public string LegalEntity { get; set; }
        public string PurchaseJournal { get; set; }
        public string PurchaseType { get; set; }
        public DateTime PurchaseOrderDate { get; set; }
        public string PurchaseOrderNumber { get; set; }
        public string TransactionCurrencyCode { get; set; }
        public double TransactionCurrencyAmount { get; set; }
        public string VendorAccount { get; set; }
        public string BusinessEventId { get; set; }
        public string ControlNumber { get; set; }
        public string EventId { get; set; }
        public string EventName { get; set; }
        public string MajorVersion { get; set; }
        public string MinorVersion { get; set; }
    }
  • Now change the code in your function as the following. Here what we are doing is
    • De-serializing the message received by utilizing JsonConvert.Deserialize() method and parsing it our PurchaseOrder class structure which we defined in the earlier step.
    • Then we are logging the Vendor account, Purchase Order ID and the Purchase Order date. We’ll pass this information to the external warehousing system.
    • Now we construct an API call and call the external System API.
    • I have this external API also built using Azure function and running on localhost at the following address.

http://localhost:7071/api/PurchaseOrderShipment

    public static class CallExternalAPI
    {
        [FunctionName("CallExternalAPI")]
        public static async void Run([ServiceBusTrigger("confirmedpos", "ConfirmedPOsExternalAPI", 
                                Connection = "ConnectionString")]
                                string mySbMsg, ILogger log)
        {
            HttpRequestMessage req = new HttpRequestMessage();

            log.LogInformation($"C# ServiceBus topic trigger function processed message: {mySbMsg}");

            // Deserialize the recieved message.
            var obj = JsonConvert.DeserializeObject<PurchaseOrder>(mySbMsg);

            // Log the extracted informaiton.
            log.LogInformation($"{obj.VendorAccount}");
            log.LogInformation($"{obj.PurchaseOrderNumber}");
            log.LogInformation($"{obj.PurchaseOrderDate}");

            try { 
            // Call Your  API
            HttpClient newClient = new HttpClient();

            //Read Server Response
            string myJson = "{'VendorAccount' : '" + obj.VendorAccount + "', 'PurchaseOrderID' : '" + obj.PurchaseOrderNumber + "', 'PurchaseOrderDate' : '" + obj.PurchaseOrderDate + "'}";

            var response = await newClient.PostAsync("http://localhost:7071/api/PurchaseOrderShipment", new StringContent(myJson, Encoding.UTF8, "application/json"));

            }
            catch (Exception ex)
            {
                log.LogInformation("Some exception occured {0}", string.Format(ex.Message));
            }
        }

So the flow is that as soon as the PO is confirmed, a message is dropped on the topic which will auto-trigger the above function. The function will then parse the message and update an external API. Since we are running development on our local dev machine, we need to configure the different ports to avoid port conflict and run both of these APIs on the same server. In order to configure the different port, add this in the settings.json file in VS.

  • Lets run this function. make sure the D365 FinOps environment is also running where the business event was configured in the previous post.
Machine generated alternative text:
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
•46 AM] 
Version—3. 
on=(null)) 
15/23/2e2e 
•15/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
[5/23/2e2e 
5 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
e. 13353. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
5. 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
"MessageWaitTimeout " : 
"BatchOptions' 
"ee : el : eel' 
"MaxMessageCount": leee, 
"OperationTimeout": ' 
"AutoComplete": true 
Httpoptions 
"Dynamic ThrottlesEnab1ed" : 
false, 
"MaxConcurrent Requests " : 
-1, 
"MaxOutstandingRequests " : 
-1, 
"RoutePrefix": "apl 
Starting JobHost 
Starting Host (Hostld=hsusd1r9ex43hm-13617998e2, Instance1d=fe2de89e-bcc8-452e-9b84-8e1f6c585af2, 
e, ProcessId=55248, AppDomainId=1, InDebugMode=Fa1se, InDiagnosticMode=Fa1se, FunctionsExtensionVersi 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
AM] 
Loading functions metadata 
1 functions loaded 
Generating 1 job function(s) 
Function 'CallExterna1API' is async but does not return a Task. Your function may not run correct 
Found the following functions : 
Busines s Events FunctionApp. Call ExternalAPI . Run 
Initializing function HTTP routes 
No HTTP routes mapped 
Host initialized (474ms) 
Host started (794ms) 
Job host started 
Hosting environment: Production 
Content root path: C: 
Now listening on: http://e.e.e.e:7e73 
Application s 
ut down. 
[5/23/2e2e AM] Host lock lease acquired by instance ID 'eeeeeeeeeeeeeeeeeeeeeeeeeoc3E8DB' .
  • Here is the code base for the fictitious external API to receive the confirmed POs and add them in the in-memory list.

public static class POConfirmedApi
{
    static List orders = new List();    
    
    [FunctionName("ShipPurchaseOrder")]
    public static async Task<IActionResult> ShipPurchaseOrder(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "PurchaseOrderShipment")] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Update the list of purchase orders.");
        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        var order = JsonConvert.DeserializeObject<PurchaseOrder>(requestBody);
        orders.Add(order);
        return new OkObjectResult(order);
    }
    [FunctionName("GetPurchaseOrders")]
    public static async Task<IActionResult> GetPurchaseOrders(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "PurchaseOrderShipment")] HttpRequest req,
    ILogger log)
    {
        log.LogInformation("Getting list of Purchase Orders.");
        return new OkObjectResult(orders);
    }
}
public class PurchaseOrder
{
    public string VendorAccount { get; set; }
    public string PurchaseOrderID { get; set; }        
    public DateTime PurchaseOrderDate { get; set; }        
}

Add the above code in a separate function app project and then Run.

Login to the D365 FinOps environment and pick any PO. Edit it and then confirm.

At this point, you should have noticed some movement on both functions’ running consoles. In the first window, the message is retrieved from the service topic, extracted and pushed to the external API. In the second window the external API confirms the receipt of the message with limited details. As a best practice, only the required details should be pushed to the external systems.

Now we can validate if the external system has it added in its in-memory list by calling a GET on the same API.

The API returns the same PO which we confirmed in D365 FinOps.

Next in the final part of this three-part series, we’ll create another function to trigger on the subscription of the same topic to send an email.