Optimizing Azure Logic Apps for Better Exception Handling

Author: Jayakumar SrinivasanDate: 02-Dec-2024

Overview of the Problem

Introduction

Developing complex logic apps that interact with multiple external API, databases, FTP servers, etc., often presents significant challenges in exception handling. This complexity is exacerbated when processing within loops. Such logic apps are notoriously difficult to troubleshoot, requiring a detailed examination of the run history to identify failures. Additionally, during my reviews, I have observed that these logic apps frequently send generic error messages with irrelevant status codes when exceptions occur.

A sample complexity

In my reviews, I frequently encounter logic apps performing following tasks: retrieve all files from an SFTP folder, obtain a token from an Authentication API, call an external system with the token, parse the data of each file in a loop, send the parsed data to another API, make database entries, move the file to different SFTP folders based on processing success or failure, and exit the logic app with a response or Terminate action. The developers often declare very few scopes to manage exceptions, resulting in generic “Internal Server Error” messages. Consequently, the development team must examine the run history to pinpoint the failure.


Complexity Root Cause

Analysis

When developing complex logic apps, it is crucial to handle exceptions at critical points, such as API calls, file processing, database interactions, and JSON parsing from external systems. Common issues identified in my reviews include:

  • Unrecognized system complexity at the start of development
  • Inadequate complexity assessment during design
  • Insufficient development time
  • Excessive use of connectors
  • Uncertainty in managing exceptions within control loops

Proposed Solution

Solution Description

The proposed solution leverages the default capabilities of logic apps to avoid complexity. It involves using two variables: a string variable to capture error messages from various actions and a Boolean variable to indicate exceptions in the workflow. The key aspect of this solution is defining scopes for every complex external or built-in connector. Consider creating separate logic apps to overcome action nesting depth limitations.

By defining scopes at each connector allows for capturing exceptions specific to an operation. The string variable will append exceptions from various connectors, enabling the storing precise error messages related to specific operations. The Boolean variable flag will be set false denoting exceptions in the workflow. The string variable is particularly useful for control connectors, as it helps identify failed items within loops by summarizing the failures (e.g., out of 5 items, 1 failed) with appropriate exception messages.

Key Considerations Before Development

  • Clear requirements for development
  • Involvement of built-in and custom connectors
  • Possibility of separating workflows into child logic apps
  • Fixed request and response schemas for external systems

Benefits

Following will be the benefits of the proposed solution

  • Precise identification of exceptions
  • Context-specific exception messages
  • Summary of exceptions when using control (loop) connectors
  • Decision-making facilitation at the end of the workflow to determine logic app success or failure
  • Simplified troubleshooting

References


Creating Services with AngularJS

In this blog, we are going to see how to create a AngularJS service that will make use of the $q service for performing asynchronous processing. The $q service of AngularJS. You can find the $q documentation here. I have used TypeScript for scripting the classes as its been a blessing for a C# developers like me.

The scenario for this service will be, Products Service will call an external business service to get the data. The business service is developed by an external team which is not ready during the UI development. I hope most of us might have encountered such scenario and one of the ways to deal with such scenarios is to create a stub. I am going to use Repository Pattern that will intern call static or service repository based on condition.

The following is the class diagram of the classes and interfaces involved and a short note below provides a short description about it

UMLDiagram1

Repository pattern for getting data from service or static repository

IRepositoryManagerBase:

This interface provides the implementation for creating a repository manager class. This is a generic interface the takes any type as the parameter. The method GetRepository will be used to load the repositories based on the criteria passed. Will explain more about it in ProductsRepositoryManager class.

export interface IRepositoryManagerBase
{
    GetRepository(repositoryType: string): IRepository;
    RepositoryType: string;
    Repository: IRepository;
}

IRepository:

Interface used to provide CRUD operation on repository classes like GetAll, GetbyId, SaveItem, DeleteItem, AddItem, DeleteAll, SaveItems, AddItems and DeleteItems. The important one to understand here is that all the method returns promise.

export interface IRepository{
    GetAll(): ng.IPromise<Array>;
    GetbyId(id: number): ng.IPromise;
    SaveItem(item: TEntity): ng.IPromise;
    DeleteItem(item: TEntity): ng.IPromise;
    AddItem(item: TEntity): ng.IPromise;
    DeleteAll(): ng.IPromise;
    SaveItems(items: Array): ng.IPromise;
    AddItems(items: Array): ng.IPromise;
    DeleteItems(items: Array): ng.IPromise;
}

IProductRepository:

This interface extends the IRepository interface and has specialization method specific to Product

export interface IProductRepository extends IRepository{
    GetByName(name: string): ng.IPromise<Array>;
    GetByCode(code: string): ng.IPromise<Array>;
    GetByCategory(category: string): ng.IPromise<Array>;
}

Handling Asynchronous calls with AngularJS $q service

ProductServiceRepository:

This repository class extends IProductRepository interface that used to get the data from an external service and as of now I will give the skeleton of how to implement the deferred pattern all the methods. This can be considered as a boilerplate code and can used in any AngularJS.

export class ProductServiceRepository implements IProductRepository<Models.Products.Products>{
    static $inject = ["$q"];
    constructor(private $q: ng.IQService) {
    }
    public GetAll(): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public GetbyId(id: number): ng.IPromise<Models.Products.Products>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public GetByName(name: string): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public GetByCode(code: string): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public GetByCategory(category: string): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public SaveItem(item: Models.Products.Products): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public DeleteItem(item: Models.Products.Products): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public AddItem(item: Models.Products.Products): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }

        return d.promise;
    }

    public DeleteAll(): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public SaveItems(item: Array<Models.Products.Products>): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public AddItems(item: Array<Models.Products.Products>): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }

    public DeleteItems(item: Array<Models.Products.Products>): ng.IPromise<boolean>{
        var d = this.$q.defer();
        try {
            // Call to external service here
            d.resolve();
        }
        catch (e) {
            // Code to get the error from service here
            d.reject(e);
        }
        return d.promise;
    }
}

ProductStaticRepository:

This repository class extends IProductRepository interface that used to populate the required inside very method and store in a collection. This method also uses deferred pattern all the methods. This method will come handy until the Business Service is made ready.

export class ProductStaticRepository implements IProductRepository<Models.Products.Products>{
    static $inject = ["$q"];
    private result: Array<Models.Products.Products> = new Array<Models.Products.Products>();
    constructor(private $q: ng.IQService) {
    }

    public GetAll(): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        try {
            this.result = new Array<Models.Products.Products>();
            for(var counter:number = 0; counter < 10; counter++){
                var item = new Models.Products.Products(
                    counter + 1,
                    "ProductName " + (counter+1).toString(),
                    "PROD" + (counter+1).toString(),
                    new Date(),
                    "Product" + (counter+1).toString() + "-Description",
                    (counter+1)*1000,
                    "ImageUrl00"+ (counter+1).toString(), (counter%2 == 0) ? "Toys": "Books");
                this.result.push(item);
            }
            d.resolve(this.result);
        }
        catch (e) {
            d.reject(e);
        }

        return d.promise;
    }

    public GetbyId(id: number): ng.IPromise<Models.Products.Products>{
        var d = this.$q.defer();
        var result: Models.Products.Products;
        try {
            for(var item in this.result){
                result = this.result[item];
                if(result.ProductId == id)
                {
                    break;
                }
            }

            d.resolve(result);
        }
        catch (e) {
            d.reject(e);
        }

        return d.promise;
    }

    public GetByName(name: string): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        var results: Array<Models.Products.Products>;
        try {
            var currentitem: Models.Products.Products;
            for(var item in this.result){
                currentitem = this.result[item];
                if(currentitem.ProductName == name)
                {
                    results.push(currentitem);
                }
            }

            d.resolve(results);
        }
        catch (e) {
            d.reject(e);
        }

        return d.promise;
    }

    public GetByCode(code: string): ng.IPromise<Array<Models.Products.Products>>{
        var d = this.$q.defer();
        var results: Array<Models.Products.Products>;
        try {
            var currentitem: Models.Products.Products;
            for(var item in this.result){
                currentitem = this.result[item];
                if(currentitem.ProductCode == code)
                {
                    results.push(currentitem);
                }
            }

            d.resolve(results);
        }
        catch (e) {
            d.reject(e);
        }

        return d.promise;
    }

    public GetByCategory(category: string): ng.IPromise<Models.Products.Products[]>{
        var d = this.$q.defer();
        var results: Array<Models.Products.Products>;
        try {
            var currentitem: Models.Products.Products;
            for(var item in this.result){
                currentitem = this.result[item];
                if(currentitem.ProductCategory == category)
                {
                    results.push(currentitem);
                }
            }

            d.resolve(results);
        }
        catch (e) {
            // $.Deferred<Models.Products.Products[]>().reject(e);
            d.reject(e);
        }

        return d.promise;
    }

    public SaveItem(item: Models.Products.Products): ng.IPromise<boolean>{
        ...
    }

    public DeleteItem(item: Models.Products.Products): ng.IPromise<boolean>{
            ...
    }

    public AddItem(item: Models.Products.Products): ng.IPromise<boolean>{
            ...
    }

    public DeleteAll(): ng.IPromise<boolean>{
            ...
    }

    public SaveItems(item: Array<Models.Products.Products>): ng.IPromise<boolean>{
            ...
    }

    public AddItems(item: Array<Models.Products.Products>): ng.IPromise<boolean>{
            ...
    }

    public DeleteItems(item: Array<Models.Products.Products>): ng.IPromise<boolean>{
            ...
    }
}

ProductRepositoryManager:

This class is repository manager class that provides the repository class based on the string parameter passed to the GetRepository(repositoryType: string). If the parameter is passed as “Static” then an instance of ProductStaticRepository is returned or when “Service” is passed as parameter then ProductServiceRepository is returned.

export class ProductRepositoryManager implements IRepositoryManagerBase<Models.Products.Products>{
    private repoType: string;
    private _repository: IRepository<Models.Products.Products>;
    constructor(repositoryType: string, private $q: ng.IQService){
        this.repoType = repositoryType;
        this._repository = this.GetRepository(this.repoType);
    }

    public GetRepository(repositoryType: string) : IRepository<Models.Products.Products>{
        this.repoType = repositoryType;
        var itemToReturn:IRepository<Models.Products.Products>;
        switch(repositoryType){
            case "Static":
                itemToReturn = new ProductStaticRepository(this.$q);
                break;
            case "Service":
                itemToReturn = new ProductServiceRepository(this.$q);
                break;
            case "Others":
            default:
                itemToReturn = null;
                break;
        }
        return itemToReturn;
    }

    public get Repository(): IRepository<Models.Products.Products>{
        return this._repository;
    }

    public get RepositoryType(): string{
        return this.repoType;
    }

    public set RepositoryType(value: string){
        this.repoType = value;
    }
}

Product Service

This is the AngularJS service that is derived from the IRepositoryManagerBase class and this will create an instance of ProductServiceRepository/ProductStaticRepository repository class based on the criteria. Some important explanation on the implementation in the ProductService is given below

export class ProductService extends ProductRepositoryManager{
    static $inject = ["$q"];
    constructor($q: ng.IQService, repoType: string){
        super(repoType, $q);
    }
}

Every Journey has a beginning….

I have been into Microsoft Technology for more that 13+ years and had a wonderful journey until now. I have been blessed for working with some of the great minds in the industry and has been a excellent journey so far.

There are quiet few reasons that I see blogging can improve me in the following ways

  • Sharing my knowledge to the global audience and improvising it based on their feedback
  • To gain more confidence in myself on my technical capabilities and new things that I explore
  • Learn by contributing

Well nothing more I can think of as of now. I am happy that I an starting this journey and looking into attracting global developer community….