Using functions with Logic Apps

Up to this point, we have been discussing how to create and use functions within a Function App for general use cases when serverless computation is required or when we wish to create an event-driven solution.

Functions can also be called directly from a Logic App. While it is possible to use the event-driven nature of functions to create a highly decoupled and scalable solution, using functions directly called within a Logic App provides a mechanism to add simple business logic that cannot be achieved through the standard functionality available within Logic Apps.

This introduces the Logic Apps equivalent of lambda expressions, which are essentially anonymous functions that are invoked only when required (https://msdn.microsoft.com/en-us/library/bb397687.aspx).

Calling functions directly

How to create a Logic App has been discussed previously, so for the purpose of this scenario, we will assume that one has already been created using the Blank Logic App template.

A call to a function is an action within a Logic App. Therefore, in order for the function to be called, we need a trigger that starts a new instance of the Logic App.

For the purpose of testing and building, we will use a simple request/response pattern for the Logic App. Using a request/response allows us to use a simple tool, such as Postman (https://www.getpostman.com/), to POST a message to the API endpoint and wait for the response.

Once we have added the initial request to the Logic App and configured it appropriately, we choose +New step followed by Add an action.

We expand the dropdown at the top of the action and choose Azure Functions.

Calling functions directly

You can then click through and select the Function App that will contain or already contains the required function.

Calling functions directly

After selection, only functions within the Function App that are generic WebHooks are displayed and can be selected. Generic WebHooks are the only supported functions that can be used within a Logic App. Alternatively, a new generic WebHooks function can be created directly from the Logic Apps designer, including code.

Calling functions directly

It is possible to use data from the previous trigger within the Logic App or pass a blank payload by specifying {}.

Calling functions directly

Clicking on Next allows you to then choose a name for the function and provide code inline.

Calling functions directly

Clicking on Create finishes the process and creates the function in the chosen Function App directly.

Calling functions directly

To provide a more concrete walkthrough of the process, it is useful to consider a scenario that is specific to our reference implementation used throughout this book.

Scenario - Invoice checker

When an invoice is received by Sunny Electricals, it needs to be checked to make sure the invoice value contained in the inbound message is equal to the total provided by the items that are selected by the customer.

We define a JSON message that will be received by the Logic App request trigger and processed by the function:

{ 
  "Invoice": { 
    "Number": "123", 
    "CustomerID": "10", 
    "CustomerName": "John Smith", 
    "Items": { 
      "Item": [ 
        { 
          "ProductCode": "SCW01", 
          "ProductDescription": "50mm Screws", 
          "Quantity": "100", 
          "Price": "5.99", 
          "Total": "599.00" 
        }, 
        { 
          "ProductCode": "SCW02", 
          "ProductDescription": "75mm Screws", 
          "Quantity": "100", 
          "Price": "7.99", 
          "Total": "799.00" 
        } 
      ] 
    }, 
    "TotalCost": "1398.00" 
  } 
} 

We can see that we have an Invoice, Number, CustomerID, and CustomerName, and a list of items that have been ordered. The final element is the calculated cost as created by our store frontend. We want to check that the value of TotalCost is equal to the sum of totals for the items based on the item Total.

We start by creating a new function, CheckInvoiceTotal, remembering that in order for it to be called from a Logic App it needs to be a generic WebHook.

Scenario - Invoice checker

For our function, we need a C# class that represents our JSON payload. We can create this by hand or use a tool to do the work for us. In this case, we use an online resource for this (http://json2csharp.com/), which produces the following code:

public class Item 
{ 
    public string ProductCode { get; set; } 
    public string ProductDescription { get; set; } 
    public string Quantity { get; set; } 
    public string Price { get; set; } 
    public string Total { get; set; } 
} 
 
public class Items 
{ 
    public List<Item> Item { get; set; } 
} 
 
public class Invoice 
{ 
    public string Number { get; set; } 
    public string CustomerID { get; set; } 
    public string CustomerName { get; set; } 
    public Items Items { get; set; } 
    public string TotalCost { get; set; } 
} 
 
public class RootObject 
{ 
    public Invoice Invoice { get; set; } 
} 

We could place this code in an external assembly or a shared .csx file for inclusion across many functions or simply include inline with the function itself, as shown previously in the chapter.

To perform the calculation for the function, we need to read the incoming request, deserialize to our invoice object and then check values:

#r "Newtonsoft.Json" 
 
using System.Net; 
using Newtonsoft.Json; 
 
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) 
{ 
    log.Info($"C# HTTP trigger function processed a request. 
    RequestUri={req.RequestUri}"); 
 
    // Get request body. 
    dynamic data = await req.Content.ReadAsAsync<object>(); 
 
    // Validate the invoice. 
    RootObject r = 
    JsonConvert.DeserializeObject<RootObject>(data.ToString()); 
    decimal totalCost = 0M; 
    decimal itemTotal = 0M; 
    decimal runningTotal = 0M; 
 
    log.Info(data.ToString()); 
 
    log.Info(String.Format("Total cost = {0}", r.Invoice.TotalCost)); 
 
    if (!Decimal.TryParse(r.Invoice.TotalCost, out totalCost)) 
    { 
        return req.CreateResponse(HttpStatusCode.InternalServerError, 
    "Failed to process Invoice - cannot parse value for TotalCost."); 
    } 
 
    // Loop through the invoice items and check that the calculated 
       cost equals the specified total cost. 
    foreach (var item in r.Invoice.Items.Item) 
    { 
           itemTotal = 0M; 
           if (!Decimal.TryParse(item.Total, out itemTotal)) 
           { 
               return req.CreateResponse(HttpStatusCode.InternalServerError, "Failed to process invoice item - cannot parse value for Total."); 
           } 
           runningTotal += itemTotal; 
    } 
 
    log.Info(String.Format("Running total = {0}", runningTotal)); 
 
    if (totalCost != runningTotal) 
    { 
        return req.CreateResponse(HttpStatusCode.BadRequest, 
            String.Format("Invoice total cost = ${0}.  Calculated total 
        cost = ${1}", totalCost, runningTotal)); 
    } 
 
    return req.CreateResponse(HttpStatusCode.OK, 
            String.Format("Invoice total cost = ${0}.  
   Calculated total cost = ${1}", totalCost, runningTotal)); 
} 

Finally, we need to return a response with an appropriate HTTP status code--either HttpStatusCode.OK if we have equality or HttpStatusCode.BadRequest if we do not.

Once we have created the function, we need to save it and ensure that it compiles correctly. We can use the test harness within the function itself to test the function behaves as expected for all our conditions.

Scenario - Invoice checker

We can now create the Logic App that will call the function. As before, we create a Logic App that starts with a request trigger. Within the request trigger, we can define the schema that represents the request body. This is a great way to make the inbound request strongly typed. To create the schema, we can use the sample message and a tool to generate the schema ( http://jsonschema.net/) and then paste the result into the Logic App REQUEST BODY JSON SCHEMA configuration.

When we add an Azure Function from the same region and select the Function App in which we created our function, we can select it from the list.

Scenario - Invoice checker

We need to define the payload object that will be passed in to the function, which for this scenario, will simply be the body of the request.

Scenario - Invoice checker

Finally, we click on + New step and add a response that uses the status code and body of the response from the function.

Scenario - Invoice checker

This completes the Logic App that we can build from within the designer. When our function returns an HTTP response, a status code of 200 indicates success, whereas a status code of 400 or 500 indicates failure.

We need to amend the Logic App to ensure that both conditions return a response as, by default, only a Succeeded message is returned. For this, we switch to Code View and add Failed to the runAfter entry in the Response action.

Scenario - Invoice checker

We need to test that we have a functioning Logic App, and we can do this using Postman. First, we need the URL to which we need to post our payload.

Once the Logic App has been saved, we obtain the URL from the URL box in the Request object within the Logic App.

Scenario - Invoice checker

Within Postman, we set the method to POST, paste in the URL for the Logic App, paste in our message, and set the content type to application/json and then click on Send. If we have configured the Logic App correctly, we expect to see a status code of 200 OK if we have sent a valid message that contains totals that match.

Scenario - Invoice checker

We can test our other scenarios to ensure that we get the correct responses.

Once we have completed our testing, the Logic App can now be used elsewhere by simply calling the URL and passing the correct payload as shown.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset