Microservice Patterns

H15 tháng 6, 2024

Event-Driven Architecture (EDA)

Event-Driven Architecture (EDA) is about using events as a way to communicate within a system. Generally, leveraging a message broker to publish and consume events asynchronously. The publisher is unaware of who is consuming an event and the consumers are unaware of each other. Event-Driven Architecture is simply a way of achieving loose coupling between services within a system.

What is an event?

An event is a data point that represents state changes in a system. It doesn't specify what should happen and how the change should modify the system, it only notifies the system of a particular state change. When a user makes an action, they trigger an event.

Components

Event-driven architectures have three key components:

  • Event producers: Publishes an event to the router.
  • Event routers: Filters and pushes the events to consumers.
  • Event consumers: Uses events to reflect changes in the system.

Note: Dots in the diagram represents different events in the system.

Patterns

There are several ways to implement the event-driven architecture, and which method we use depends on the use case but here are some common examples:

Note: Each of these methods is discussed separately.

Advantages

Let's discuss some advantages:

  • Decoupled producers and consumers.
  • Highly scalable and distributed.
  • Easy to add new consumers.
  • Improves agility.

Challenges

Here are some challenges of event-drive architecture:

  • Guaranteed delivery.
  • Error handling is difficult.
  • Event-driven systems are complex in general.
  • Exactly once, in-order processing of events.

Use cases

Below are some common use cases where event-driven architectures are beneficial:

  • Metadata and metrics.
  • Server and security logs.
  • Integrating heterogeneous systems.
  • Fanout and parallel processing.

Examples

Here are some widely used technologies for implementing event-driven architectures:

Event Sourcing

Instead of storing just the current state of the data in a domain, use an append-only store to record the full series of actions taken on that data. The store acts as the system of record and can be used to materialize the domain objects.

This can simplify tasks in complex domains, by avoiding the need to synchronize the data model and the business domain, while improving performance, scalability, and responsiveness. It can also provide consistency for transactional data, and maintain full audit trails and history that can enable compensating actions.

Event sourcing vs Event-Driven Architecture (EDA)

Event sourcing is seemingly constantly being confused with Event-driven Architecture (EDA). Event-driven architecture is about using events to communicate between service boundaries. Generally, leveraging a message broker to publish and consume events asynchronously within other boundaries.

Whereas, event sourcing is about using events as a state, which is a different approach to storing data. Rather than storing the current state, we're instead going to be storing events. Also, event sourcing is one of the several patterns to implement an event-driven architecture.

Advantages

Let's discuss some advantages of using event sourcing:

  • Excellent for real-time data reporting.
  • Great for fail-safety, data can be reconstituted from the event store.
  • Extremely flexible, any type of message can be stored.
  • Preferred way of achieving audit logs functionality for high compliance systems.

Disadvantages

Following are the disadvantages of event sourcing:

  • Requires an extremely efficient network infrastructure.
  • Requires a reliable way to control message formats, such as a schema registry.
  • Different events will contain different payloads.

Command and Query Responsibility Segregation (CQRS)

Command Query Responsibility Segregation (CQRS) is an architectural pattern that divides a system's actions into commands and queries. It was first described by Greg Young.

In CQRS, a command is an instruction, a directive to perform a specific task. It is an intention to change something and doesn't return a value, only an indication of success or failure. And, a query is a request for information that doesn't change the system's state or cause any side effects.

The core principle of CQRS is the separation of commands and queries. They perform fundamentally different roles within a system, and separating them means that each can be optimized as needed, which distributed systems can really benefit from.

CQRS with Event Sourcing

The CQRS pattern is often used along with the Event Sourcing pattern. CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores.

When used with the Event Sourcing pattern, the store of events is the write model and is the official source of information. The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views.

Advantages

Let's discuss some advantages of CQRS:

  • Allows independent scaling of read and write workloads.
  • Easier scaling, optimizations, and architectural changes.
  • Closer to business logic with loose coupling.
  • The application can avoid complex joins when querying.
  • Clear boundaries between the system behavior.

Disadvantages

Below are some disadvantages of CQRS:

  • More complex application design.
  • Message failures or duplicate messages can occur.
  • Dealing with eventual consistency is a challenge.
  • Increased system maintenance efforts.

Use cases

Here are some scenarios where CQRS will be helpful:

  • The performance of data reads must be fine-tuned separately from the performance of data writes.
  • The system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.
  • Integration with other systems, especially in combination with event sourcing, where the temporal failure of one subsystem shouldn't affect the availability of the others.
  • Better security to ensure that only the right domain entities are performing writes on the data.

API Gateway

The API Gateway is an API management tool that sits between a client and a collection of backend services. It is a single entry point into a system that encapsulates the internal system architecture and provides an API that is tailored to each client. It also has other responsibilities such as authentication, monitoring, load balancing, caching, throttling, logging, etc.

Why do we need an API Gateway?

The granularity of APIs provided by microservices is often different than what a client needs. Microservices typically provide fine-grained APIs, which means that clients need to interact with multiple services. Hence, an API gateway can provide a single entry point for all clients with some additional features and better management.

Features

Below are some desired features of an API Gateway:

Advantages

Let's look at some advantages of using an API Gateway:

  • Encapsulates the internal structure of an API.
  • Provides a centralized view of the API.
  • Simplifies the client code.
  • Monitoring, analytics, tracing, and other such features.

Disadvantages

Here are some possible disadvantages of an API Gateway:

  • Possible single point of failure.
  • Might impact performance.
  • Can become a bottleneck if not scaled properly.
  • Configuration can be challenging.

Backend For Frontend (BFF) pattern

In the Backend For Frontend (BFF) pattern, we create separate backend services to be consumed by specific frontend applications or interfaces. This pattern is useful when we want to avoid customizing a single backend for multiple interfaces. This pattern was first described by Sam Newman.

Also, sometimes the output of data returned by the microservices to the front end is not in the exact format or filtered as needed by the front end. To solve this issue, the frontend should have some logic to reformat the data, and therefore, we can use BFF to shift some of this logic to the intermediate layer.

The primary function of the backend for the frontend pattern is to get the required data from the appropriate service, format the data, and sent it to the frontend.

GraphQL performs really well as a backend for frontend (BFF).

When to use this pattern?

We should consider using a Backend For Frontend (BFF) pattern when:

  • A shared or general purpose backend service must be maintained with significant development overhead.
  • We want to optimize the backend for the requirements of a specific client.
  • Customizations are made to a general-purpose backend to accommodate multiple interfaces.

Examples

Following are some widely used gateways technologies: