Embracing Microservices : Composition Patterns

Overview

Now it´s time to discuss the concept of composing microservices. Composing your microservices architecture involves combining various components in identifiable patterns, which we refer to as composition patterns. These patterns help define how communication and interactions occur within our microservices architecture. They determine how different components of your microservices communicate with each other. Composition patterns also dictate the sequence in which the components are used within your architecture.

It is important to note that our microservices architecture can include several composition patterns. Some of these patterns are intentionally introduced to incorporate specific behaviors into your architecture. Others may be introduced due to the technology you are using in your microservices architecture.

In this article, we will explore different composition patterns, starting with the broker composition pattern. This pattern is introduced into your architecture based on a specific technology, such as a message broker. The message broker enables synchronous communication in your microservices architecture, allowing you to delegate tasks and forget about them while they are processed in the background. It follows a “fire and forget” approach.

Broker composition pattern is significant because it is introduced by a technology choice and because we aim to incorporate specific communication behaviors into our microservices architecture. Additionally, there are other composition patterns, such as the Aggregate, Chained, Branch, and Proxy, which are introduced to address communication behaviors and transaction handling within our architecture.

Broker Composition Pattern

You typically incorporate this composition into your microservices architecture when you want to enable asynchronous communication using a message broker. Asynchronous communication is useful when you have tasks that need to be processed in the background without holding up your main application. One way to achieve this is by utilizing a message broker, which receives tasks and places them on an exchange and a queue. Specific services within your architecture listen for these tasks, pick them up, and process them in the background.

Let’s take a look to below diagram to understand how the broker composition pattern works with a message broker.

On the left, a user makes a request to our website. A load balancer determines which instance of your website or service will handle the request. This decision is transparent to the user as it is managed by the load balancer. The user’s request might be directed to a backend microservices API, which performs the task in the background. This separation allows decoupling of logic from the website’s user interface, making it usable by other applications.

The request is passed to the microservices API in the background through the load balancer, which can reach any of the stateless microservices instances. This is where the broker composition pattern comes into play. The background microservices API may choose to place the task onto an exchange in the queue for processing in the background. The message broker handles and manages the exchange and queue, ensuring that the task remains in the queue until a service is available to process it.

Also important to note that once the task is placed in the queue, control is returned to the API and the website. This exemplifies how asynchronous communication functions within a microservices architecture. By offloading the task for background processing, the front-end application gains performance since it no longer needs to wait for the task to complete. The specific method for handling a task depends on how you choose to process it. You may employ a pattern where multiple services process the same task in different ways, or you may have a task that is exclusively processed by one service, with competing instances contending for the task to enhance performance.

For more information about the Async models please take a look to : Embracing Microservices : Async Architecture

We can incorporate the broker composition pattern in different parts of our microservices architecture where we need to introduce the asynchronous communication behavior and process tasks in the background using various methods for task retrieval from the queue

Chained Composition Pattern

Now let’s examine the chained composition pattern, which is a commonly used pattern in microservices architecture. It is primarily based on the use of request-response interactions. In this pattern, one microservice makes a request to another service, and that service, in turn, makes additional calls to other services to fulfill the request. The initiating service must wait for responses from all the other services before it can receive the final response itself. As a result, the request becomes essentially synchronous, where you send a request and wait for the response. However, the response may be delayed because the service you called relies on responses from other services.

This pattern gets its name from the concept of a chain, where multiple services communicate with each other to process a single request. Let’s consider an example: a user visits your website using their browser and places an order. The order is handled by the order service. Your website’s application server makes a call to the order service to process the order and receive a response. However, the order service needs to interact with the product service, which, in turn, relies on the stock service to fulfill different parts of the request. Meanwhile, your website application has to wait for the final response. This chained composition pattern between the website, order service, product service, and stock service introduces synchronous communication in your architecture, potentially leading to delayed responsiveness of your application to the user.

To mitigate this issue, it is important to keep the chain small and aim for short, quick responses. In situations where long chains are unavoidable, it is recommended to use other composition patterns to break down the chained composition. The goal is to avoid long chains that introduce lengthy synchronous request-response cycles and instead utilize asynchronous communication whenever possible within your microservices architecture.

Some Benefits

There are several advantages to this approach:

  1. Separation of Concerns: Each service in the chain is responsible for a specific piece of functionality. This makes it easier to understand, develop, and maintain individual services.
  2. Scalability: Because each service is separate, they can be scaled independently. If one service in the chain is a performance bottleneck, you can scale up just that service without having to scale up the others.
  3. Flexibility: It’s easy to add, remove, or rearrange services in the chain. This makes the system as a whole more flexible and adaptable to changing requirements.
  4. Fault Isolation: If a service fails, it won’t bring down the entire system. You can handle failures at the level of individual services, which can help to improve the overall reliability of your application.

By managing and optimizing the chained composition pattern, you can enhance the overall performance and responsiveness of your microservices architecture.

Drawback and challeges

  1. Complexity: The chaining together of multiple services can add complexity to the system, both in terms of development and runtime management.
  2. Latency: Each service in the chain adds its own processing time and network latency to the overall processing time for a request.
  3. Debugging and Tracing: When something goes wrong, it can be difficult to figure out which service in the chain is at fault. This requires robust logging and tracing strategies to mitigate.
  4. Data Consistency: If a service further down the chain fails after a service earlier in the chain has already made changes, it can be challenging to ensure data consistency and manage rollbacks.

Aggregate Composition Pattern

The next composition pattern in the list is the aggregate composition pattern. The main characteristic of this composition pattern is the introduction of an aggregator component, which combines data and functionality from multiple other services. The aggregator collects data from various services and transforms it according to its own requirements. For instance, it might aggregate data for multiple pages or create a new data extract based on information obtained from different services.

In this example, a user connects to your web application through a browser, with a load balancer managing the incoming requests (which is transparent to the user). The aggregator in this scenario is the web app . It communicates with several other microservices in the background to retrieve data, which is then used to construct the page that will be sent back to the user’s browser. The data gathered from the order service, product service, and stock service is combined and featured on the same page. The web app acts as the aggregator by gathering data from multiple services, potentially transforming and consolidating it based on its own business logic.

The key aspect to remember about the aggregate composition pattern is that it enables a service within your microservices architecture to consolidate data from different sources. This consolidated data can then be accessed from a single location in the desired format. If the aggregation process is time-consuming, the service can be called using asynchronous communication, and the data can be delivered back to the web application using the callback address pattern.

Benefits

  1. Simplicity for clients: Client applications only need to interact with a single service (the aggregator), rather than needing to interact with multiple services.
  2. Decoupling: Changes in individual services do not directly affect the client-side applications as they interact only with the aggregator service.
  3. Performance Optimization: The Aggregator can optimize communication between services. It can parallelize requests to different services, reducing the overall response time.
  4. Data Consistency: The Aggregator can provide a consistent response format, even if the underlying services have different data formats.

Drawbacks

  1. Increased Complexity: The aggregator service can become complex, as it must handle communication with multiple services, each of which may have its own requirements.
  2. Single Point of Failure: If the aggregator service fails, all client requests that depend on it will also fail. Therefore, it is crucial to design the aggregator service with high availability and fault tolerance.
  3. Latency: If not carefully designed, the aggregator could introduce additional latency into the system, especially if it serially communicates with multiple services.
  4. Overloaded Aggregator: If there are a large number of services or high traffic, the aggregator service could potentially become a bottleneck in the system. Therefore, the aggregator service must be designed to scale to handle high loads.

To mitigate these drawbacks, it’s important to design the aggregator service with scalability, fault tolerance, and high availability in mind. Use asynchronous communication where possible, and consider using caching to improve performance.

Batch Composition Pattern

Batch Composition pattern is another pattern used in microservice architectures. It’s especially useful when a client needs to retrieve data from multiple services, but doesn’t need real-time responses.

In the Batch Composition pattern, a client sends a single “batch” request to an aggregator service. This request includes multiple sub-requests, each for a different service. The aggregator service sends each of these sub-requests to the appropriate service, collects the responses, and then sends a single response back to the client with all the collected data.

In the branch composition pattern, when the aggregator receives a request from the user, it divides that request into multiple requests to be handled by different services, each performing specific functions.The purpose of this pattern is for the aggregator to take one task and split it into multiple tasks, serviced by different services. This approach is beneficial when different parts of a task require distinct behaviors.

Here’s an example: imagine you have an e-commerce application, and you want to display order details for a user. These details might include information from several different services, like the user service, the order service, and the product service. Instead of making separate requests to each of these services, you make a single batch request to the aggregator service, which then makes the individual requests for you.

Benefits

  1. Reduced Network Overhead: Because the client is making a single request instead of multiple requests, you’re reducing the network overhead.
  2. Simplicity for Clients: The client doesn’t need to know about all the individual services. It only needs to interact with the aggregator service.
  3. Asynchronous Processing: The aggregator service can process the individual requests in parallel, which can lead to performance improvements for large batch requests.

Drawbacks

  1. Increased Complexity: The aggregator service can become quite complex, as it needs to manage multiple requests, potentially in parallel.
  2. Error Handling: Handling errors can be more challenging, because a failure in one sub-request doesn’t necessarily mean the whole request should fail.
  3. Latency: For large batch requests, the client might have to wait for the slowest sub-request to complete before it gets a response.

By employing the branch composition pattern, your microservices architecture can efficiently handle complex tasks, leveraging different communication mechanisms to ensure timely responses and efficient background processing.

proxy Composition Pattern

Finally we review the Proxy composition pattern which is particularly useful when we need to expose certain functionalities within your system to external customers and third parties. Instead of exposing all the services within your architecture directly to these external entities, you have a single service that acts as a proxy for all the other services. This proxy service is the one exposed to the client or third party. By doing so, you can focus on building robust security and authentication measures into the proxy service or API. It provides a centralized access point for your system.

The proxy acts as a middleman between clients and services. Also, the Proxy Composition pattern can be particularly useful when you have complex routing rules that determine which service should handle a request, or when you need to aggregate responses from multiple services.

Benefits

  1. Abstraction: The client doesn’t need to know about the individual microservices. It only interacts with the proxy, which simplifies the client’s design.
  2. Centralized Routing Logic: The routing logic is centralized in the proxy, making it easier to manage and update.
  3. Load Balancing and Service Discovery: The proxy can handle load balancing and service discovery, distributing requests evenly across available service instances and finding the appropriate service instance to handle a request.
  4. Resilience: The proxy can implement resilience patterns, such as timeouts, retries, and circuit breakers, protecting the client from issues with the services.

Drawbacks

  1. Single Point of Failure: If the proxy service fails, all client requests that go through it will also fail. Therefore, it’s crucial to design the proxy service with high availability and fault tolerance.
  2. Increased Latency: The proxy service can add additional latency to requests, as they have to go through an extra network hop.
  3. Complexity: The proxy service can become complex, especially if it’s responsible for advanced features like load balancing, service discovery, and resilience.

To mitigate these drawbacks, it’s important to design the proxy service with scalability, fault tolerance, and high availability in mind. You can use techniques like horizontal scaling, redundancy, and health checks to ensure the proxy remains available and performant. Also, consider using a mature, proven technology for your proxy, such as an API Gateway, to reduce the complexity of building and managing the proxy service.

That´s all for today, happy building !