Jul 28, 2019 - Using queues to offload Web API

With the ubiquitous of smartphones and mobile devices a great number of people are getting more and more accustomed to accessing all kinds of digital services remotely, anywhere they feel convenient to do so. The adoption of web APIs to back these digital services is now even more customary, and the performance of web APIs will have a direct impact on user experience (UX).

This article presents a technique for optimizing web APIs by offloading part of its operations to asynchronous queues, resulting in faster response times and improved UX. First we analyze the anatomy of a web API request, then we introduce the structure of a queuing system, and finally we combine both for achieving more efficient execution flows.

Anatomy of a Web API Request

A great deal of backend services are composed of a web API module on top of a database. In this model code is only executed on demand, i.e., whenever the API gets called. There are no background processes executing behind the scenes. It is simple to develop and operations are predictable, easy to follow.

This model can be pushed really far, as long as the API constraints itself to light weight operations with simple interactions. However, if there’s a need to implement more complex and demanding operations sticking to this model will result in longer processing times and degraded performance. The diagrams below helps understand why:

light-web-request

In light weight web API requests calls arrive at the endpoint, are routed to the application, business logic is executed, perhaps fetching or persisting some data, and finally a response is produced and returned to the client. Operations are mostly short in duration and everything runs within the application boundaries.

heavy-web-request

In more complex web API requests a lot more is going on. Secondary web requests may be triggered in response to the primary request, multiple database queries may be required for processing data and more sophisticated algorithms may be executed in the request main thread. As the diagram shows, these operations will last longer and are not limited to the application boundaries.

Naturally, as operations grow in complexity comes the need for an improved application architecture for orchestrating execution flows and preserving general web API consistency.

The Queuing System

A typical queuing system is composed of a message broker program which provides a publisher/subscriber interface for interacting with named message queues. A message queue is a store of published messages, which are consumed in sequential or prioritized order by one or more subscribers. The message broker provides numerous configuration options for handling messages, including:

  • Durability: messages may be stored in memory, written to disk, or committed to a database depending on reliability requirements
  • Routing policies: in scenarios with multiple message brokers these policies define which servers to publish/subscribe messages
  • Message filtering: specific criteria may be applied to determine which messages are available to individual subscribers
  • Purging policies: messages may have an expiration, after which they are automatically purged
  • Acknowledgement notification: the broker may wait an acknowledgement from the subscriber before committing a message queue removal

message-broker

Message queues are a great solution for orchestrating asynchronous communications. In the image above we have three publishers connected to the broker, publishing messages to three different queues. On the other end four subscribers are also connected to the broker consuming messages from these queues. Routing is flexible and performed internally by the broker.

This example is only illustrative, and depending on application requirements we can have several types of relationships between publisher-queue-subscriber. Typically for small scale systems we will see multiple publishers submitting messages to a queue which has a single subscriber consuming it. For larger distributed systems it’s common to have multiple subscribers consuming from the same queue, each one of them potentially running in a different server for higher performance and availability.

Improving Execution Flow

Let’s suppose the sequence diagram from figure 2 represents a social media content “like” operation as follows:

  1. Mobile App sends a Like request providing authenticationToken and contentId parameters
  2. Web API receives the request, parses the parameters and validates the authenticationToken against the Database
  3. Upon successful validation it proceeds to persist a LikeAction entry into the Database, associating it with contentId
  4. Then it fetches information about the content author, and prepares a push notification request
  5. The push notification request is sent to an external cloud notification service for processing
  6. Upon receiving a confirmation from the external service the Web API returns a success response to the Mobile App

Notice that steps 3-6 are great candidates for being processed asynchronously, based on a few observations:

  • The mobile app response is not dependent on the result of these operations
  • A background worker executing these steps can handle execution errors independently

With that in mind we can introduce a Broker and a Worker components to the aplication, breaking the web API request execution flow into a shorter operation and a subsequent asynchronous operation. The original request will be reduced to:

  1. Mobile App sends a Like request providing authenticationToken and contentId parameters
  2. Web API receives the request, parses the parameters and validates the authenticationToken against the Database
  3. Upon successful validation it proceeds to publish a LikeAction message to the Broker
  4. Upon receiving a confirmation from the Broker the Web API returns a success response to the Mobile App

The third step above enqueues a message into the queue, which will eventually trigger the following asynchronous operation:

  1. The Broker delivers a LikeAction message to the Worker, which is subscribed to this queue
  2. The Worker persists a LikeAction entry into the Database, associating it with contentId
  3. Then it fetches information about the content author, and prepares a push notification request
  4. The push notification request is sent to an external cloud notification service for processing
  5. Upon receiving a confirmation from the external service the Worker completes processing the queue message, and stops

The resulting operation is presented below:

queue-sequence

Notice that the main web API request duration is now much shorter. One can argue that this technique should be incorporated to the system design since its conception, and applied systematically whenever the conditions above are met, resulting in a more efficient web API and more granular, independent code.


Bonus tip: Besides offloading web API requests, the usage of a queuing system also allows the distribution of CPU usage over time, reducing processing spikes and improving system stability. It’s easy to see why: message queues can handle operations sequentially that would otherwise be processed immediately, potentially at the same time.

Jul 23, 2019 - Integrating third-party modules

Third-party module integration has become a frequent task in application development, allowing developers to leverage functionality quickly for implementing what matters most, business logic.

Usually it comes in two forms:

  1. Software Packages
  2. Web-based API

A third-party software package is composed of library files and data files which are executed by applications locally, and become part of the application’s deployable image:

application-image

A web-based API (Application Programming Interface) provides a set of functionalities which, when consumed, are executed externally from the application, in another process or in a remote server. The application can consume the web API directly with the assistance of network libraries (A), but more often client side software packages are provided for handling communications between the two parties (B):

web-api

The next section covers the dependency management challenge that arises when integrating third-party modules into applications.

Dependency Management

Integrating with external modules can be treacherous, since it’s easy to fall into the trap of referencing it’s classes (or APIs) directly from business logic classes, producing tightly coupled code. One property of coupled code is that it’s resistant to change, making it difficult to deal with change requirements, package updates or replacing third-party modules altogether.

Hence, there’s a need for a module dependency management approach that protects business logic from coupling with external code, improving application’s flexibility and overall architecture. This is achieved by applying the Dependency Inversion principle, which dictates that “one should depend on abstractions, not concretions”.

What's an abstraction?

Simply put, an abstraction is a contract describing how a functionality is served, it’s an internal API. High level programming languages even have a keyword for defining abstract classes, which will not be covered in this article. Instead, we will focus on interfaces, which also allow for the abstraction of functionality implementation.

For instance, consider the following simplified interface:

public interface BankAccount
{
    string GetBankName();

    AccountNumber GetAccountNumber();

    double GetBalance();

    Statement[] GetStatements(DateTime start, DateTime end);

    void Withdraw(double amount);

    void Deposit(double amount);
}

An interface only defines methods signatures and input/output parameters, but doesn’t implement them. Even so, when properly defined, just by looking at it we naturally know what to expect when calling the interface’s methods, because the interface is implying expected behaviors.

Going further, we can extend this simple abstraction by applying the Factory pattern to deal with the problem of instantiating objects without knowing the exact class of the object that will be created:

public interface BankAccountFactory
{
    BankAccount GetBankAccount(AccountNumber accountNumber);
}

It may seem counter intuitive now how a factory class can create an objetc whose type is still not known, but the next section will shed some light into this.

What's a concrete implementation?

In this context a concrete implementation is the defacto class implementation of an interface. From the example above it’s reasonable to imagine BankAccount implementations for various banks:

public class BankOfAmericaAccount : BankAccount
{
    /* ... */
}

public class ChaseAccount : BankAccount
{
    /* ... */
}

public class WellsFargoAccount : BankAccount
{
    /* ... */
}

Each one of these illustrative classes would be dependent on a third-party module from the underlying bank for implementing interface behaviors.

Then, the factory interface implementation would be responsible for instantiating the appropriate bank account object from an AccountNumber, according to the bank it belongs, for instance:

public class USABankAccountFactory : BankAccountFactory
{
    public BankAccount GetBankAccount(AccountNumber accountNumber)
    {
        switch (accountNumber.BankCode)
        {
            case BankCode.BankOfAmerica:
                return new BankOfAmericaAccount(accountNumber);

            case BankCode.Chase:
                return new ChaseAccount(accountNumber);

            case BankCode.WellsFargo:
                return new WellsFargoAccount(accountNumber);

            default:
                throw new NotImplementedException();
        }
    }
}

How to resolve business logic abstract dependencies?

Now the dependency inversion principle kicks in. The interfaces concrete implementations are injected into business logic objects with the help of a dependency injection (DI) framework. The DI framework will incorporate all dependencies into itself, using them to construct application services, which aren’t dependent on any external module:

uml-injection

Each color in the class diagram above represents a different section of the whole system. In blue the application’s business logic. In dark yellow the interfaces concrete implementations which are dependent on third-party modules. Finally in grey the DI related classes, one from the DI framework (Injector) and the other responsible for configuring the application dependency graph (DependencyConfig).

Notice that application business logic is completely isolated from external modules. However, the application is dependent on the DI framework, and it won’t be able to instantiate service classes (such as the BankingService) without it. Some may say that the dependency on the DI framework defeats it’s very own purpose, but for large applications the benefits largely outweighs this drawback. If one is careful enough to avoid complex DI frameworks and use only what’s strictly required for isolating business logic from third-party modules then this self-inflicted dependency should not be a problem at all.

DI Framework Configuration

There are several approaches for configuring an application’s dependency graph. Two of the most common are by using XML files which maps concrete implementations to interfaces, or by defining a configuration class for mapping dependencies in code, for instance:

public class DependencyConfig
{
    public static void RegisterDependencies()
    {
        using (var injector = new Injector())
        {
            injector.AddTransient<BankAccountFactory, USABankAccountFactory>();
        }
    }
}

The “Transient” suffix means whenever a dependency is resolved a different object is instantiated. Other commonly supported life-cycle models are “Singleton” for always resolving a dependency with the same object and “Scoped” for associating the lifetime of the object with the duration of a web request or network session.

This configuration method is then called in the application’s uppermost layer (ex: presentation layer) initialization, allowing subsequent calls for creating services during runtime:

using (var injector = new Injector())
{
    var bankService = injector.GetService<BankService>();
}

The illustrative BankService will be allocated and it’s dependencies (magically) resolved:

public class BankService
{
    public BankService(BankAccountFactory factory)
    {
        this.factory = factory;
    }

    /* ... */
}

In the example above the BankService is using constructor based injection, which is one of several injection styles handled by DI frameworks.


This is one of several software engineering techniques that when combined allow for the effective and continuous development of large applications.

Stay tuned for more 🤓