MicroServices Patterns - vidyasekaran/current_learning GitHub Wiki

https://microservices.io/

Pattern and its use 1 liner

Application Patterns


  • Decompose by business capability
  • Decompose by subdomain
  • Self Contained Services - One way to implement a self-contained service is to use the CQRS pattern.
  • Service per team

Data Patterns

  • Database Architecture Patterns
  • Database per Service
  • Shared Database

Maintaining Data Consistency

UI

  • Server-Side page fragment composition
  • Client-Side UI composition

Observablity

  • Audit logging
  • Application metircs
  • Distributed tracing
  • Health check API
  • Exception tracking
  • Log Aggregation
  • Log deployments and changes

Transactional messaging

  • Transactional Outbox
  • Transaction log failing
  • Polling publisher

Reliability

  • Circuit Breaker

Communication Style

  • Messaging
  • Remote Procedure Invocation
  • Domain Specific

Cross cutting concerns

PATTERNS FOR HANDLING CROSS-CUTTING CONCERNS

In a microservice architecture, there are numerous concerns that every service must implement, including the observability patterns and discovery patterns. It must also implement the Externalized Configuration pattern, which supplies configuration parameters such as database credentials to a service at runtime. When developing a new service, it would be too time consuming to reimplement these concerns from scratch. A much better approach is to apply the Microservice Chassis pattern and build services on top of a framework that handles these concerns.

  • Microservice Chassis
  • Externalized Configuration

Security

  • Access Token

Infrastructure Patterns for Deployment

  • SideCar
  • Service Mesh
  • Service Deployment platform
  • Serverless deployment
  • Service per VM
  • Service per Container
  • Serverless deployment
  • Multiple Services per host
  • Single Service per Host

Communication Patterns -

Discovery

  • Service Registry
  • Server-side discovery
  • 3rd party registration
  • Client Side Discovery
  • Self Registration

External API

  • API Gateway
  • Backends for frontends

MicroServices Pattern with example in java book with list of patterns and its page number.

                                                                          List of Patterns

Application architecture patterns

  • Monolithic architecture (40)
  • Microservice architecture (40)
  • Decomposition patterns
  • Decompose by business capability (51)
  • Decompose by subdomain (54)

Messaging style patterns

  • Messaging (85)
  • Remote procedure invocation (72)

Reliable communications patterns

  • Circuit breaker (78)

Service discovery patterns

  • 3rd party registration (85)
  • Client-side discovery (83)
  • Self-registration (82)
  • Server-side discovery (85)

Transactional messaging patterns

  • Polling publisher (98)
  • Transaction log tailing (99)
  • Transactional outbox (98)

Data consistency patterns

  • Saga (114)
  • Business logic design patterns
  • Aggregate (150)
  • Domain event (160)
  • Domain model (150)
  • Event sourcing (184)
  • Transaction script (149)

Querying patterns

  • API composition (223)
  • Command query responsibility segregation (228)

External API patterns

  • API gateway (259)
  • Backends for frontends (265)

Testing patterns

  • Consumer-driven contract test (302)
  • Consumer-side contract test (303)
  • Service component test (335)

Security patterns

  • Access token (354)

Cross-cutting concerns patterns

  • Externalized configuration (361)
  • Microservice chassis (379)

Observability patterns

  • Application metrics (373)
  • Audit logging (377)
  • Distributed tracing (370)
  • Exception tracking (376)
  • Health check API (366)
  • Log aggregation (368)

Deployment patterns

  • Deploy a service as a container (393)
  • Deploy a service as a VM (390)
  • Language-specific packaging format (387)
  • Service mesh (380)
  • Serverless deployment (416)
  • Sidecar (410)

Refactoring to microservices patterns

  • Anti-corruption layer (447)
  • Strangler application (432

Applying the microservice architecture pattern language

Predecessor– a predecessor pattern is a pattern that motivates the need for this pattern. For example, the Microservice Architecture pattern is the predecessor to the rest of the patterns in the pattern language except the monolithic architecture pattern.

Successor– a pattern that solves an issue that is introduced by this pattern. For example, if you apply the Microservice Architecture pattern you must then apply numerous successor patterns including service discovery patterns and the Circuit Breaker pattern.

Alternative– a pattern that provides an alternative solution to this pattern. For example, the Monolithic Architecture pattern and the Microservice Architecture pattern are alternative ways of architecting an application. You pick one or the other. These relationships provide valuable guidance when using a pattern language. Applying a pattern creates issues that you must then address by applying successor patterns. The selection of patterns continuously recursively until you reach patterns with no successor. If two or more patterns are alternatives then you must typically pick just one. In many ways, this is similar to traversing a graph.

Let’s look at** how you can apply the microservice architecture pattern language to architect your application**. In this post we will look at 3 critical decisions you must make. In later posts, we will look at other important, albeit not quite as critical patterns.

Decision #1: Monolithic architecture or microservice architecture?

The first decision you must make is whether to use a Monolithic architecture pattern or the Microservice architecture pattern. If you pick the Microservice architecture pattern you must choose numerous other patterns to deal with the consequences of your decision.

As you can see, there are lots of other patterns that you must apply. Lets look at a couple of choices you must make.

Decision #2: How to decompose an application into services?

If you have decided to use the microservice architecture you must define your services. There are two main options,

Decision #3: How to maintain data consistency and perform queries?

A key feature of the microservice is the Database per Service pattern. It’s alternative, the Shared Database pattern is essentially an anti-pattern and best avoided. The Database per service pattern dramatically changes how you maintain data consistency and perform queries. You will need to use the Saga pattern. You will often need to implement queries using the Command Query Responsibility Segregation (CQRS) pattern.

Decompose by business capability** – define services corresponding to business capabilities

**Decompose by business capability Pattern:

**Problem **: How to decompose an application into services?

**Solution **: Define services corresponding to business capabilities. A business capability is a concept from business architecture modeling. It is something that a business does in order to generate value. A business capability often corresponds to a business object.

           Order Management is responsible for orders
           Customer Management is responsible for customers
           Business capabilities are often organized into a multi-level hierarchy.

Examples

The business capabilities of an online store include:

For each business capablities create equivalent service

Product catalog management ------ Product catalog management Service Inventory management ------ Inventory Management Service Order management ------ Order management Service Delivery management ------ Delivery Management Service

Resulting Context

This pattern has the following benefits:

Stable architecture since the business capabilities are relatively stable Development teams are cross-functional, autonomous, and organized around delivering business value rather than technical features Services are cohesive and loosely coupled

Issues

There are the following issues to address: How to identify business capabilities?

  • requires an understanding of the business

  • Good starting points for identifying business capabilities are:

    organization structure - different groups within an organization might correspond to business capabilities or business capability groups. **high-level domain model **- business capabilities often correspond to domain objects

Related patterns : The Decompose by subdomain pattern is an alternative pattern

Decompose by subdomain** – define services corresponding to DDD subdomains

Architecture --> organization --> process Microservices Architecture -----_enables _-----> small agile, autonomous teams ---enables---> CICD

Context

You are developing a large, complex application and want to use the microservice architecture. The microservice architecture structures an application as a set of loosely coupled services. The goal of the microservice architecture is to accelerate software development by enabling continuous delivery/deployment.

Problem

How to decompose an application into services?

Solution Define services corresponding to Domain-Driven Design (DDD) subdomains. DDD refers to the application’s problem space - the business - as the domain. A domain is consists of multiple subdomains. Each subdomain corresponds to a different part of the business.

Subdomains can be classified as follows:

Core - key differentiator for the business and the most valuable part of the application Supporting - related to what the business does but not a differentiator. These can be implemented in-house or outsourced. Generic - not specific to the business and are ideally implemented using off the shelf software

https://thedomaindrivendesign.io/domains-and-subdomains/

Core or Basic This is where we must put our best efforts, it is what makes the company work, which brings value to the business, which differentiates the company from competitors, is where the greatest focus is placed.

Auxiliary or Support It is the Domain that complements the main Domain, without it, its main Domain can not be successful, therefore, it is very important, will require internal development or outsourcing, because there is no solution ready to implement.

Generic It is typically a ready-made solution, but can also be outsourced or even developed internally. It does not bring a specific rule to your main business, ie in most cases we could hire as a service.

Examples

The subdomains of an online store include: Have a microservice for each of the sub domains

Product catalog ---- product catelog service Inventory management ---- Inventory management service Order management ---- order mgmt service Delivery management ---- delivery mgmt service

The corresponding microservice architecture would have services corresponding to each of these subdomains.

Resulting Context

This pattern has the following benefits:

Stable architecture since the subdomains are relatively stable Development teams are cross-functional, autonomous, and organized around delivering business value rather than technical features Services are cohesive and loosely coupled

Issues There are the following issues to address:

How to identify the subdomains? Identifying subdomains and hence services requires an understanding of the business. Like business capabilities, subdomains are identified by analyzing the business and its organizational structure and identifying the different areas of expertise. Subdomains are best identified using an iterative process. Good starting points for identifying subdomains are:

organization structure - different groups within an organization might correspond to subdomains high-level domain model - subdomains often have a key domain object Related patterns The Decompose by business capability pattern is an alternative pattern

Self-contained service

https://microservices.io/patterns/decomposition/self-contained-service.html

Context

Consider, the FTGO application, which is an online food delivery application. A client of application creates an order by making an HTTP POST /orders request and expects a response, say, within 600ms. Because the FTGO application uses the microservice architecture, the responsibilities that implement order creation are scattered across multiple services. The POST request is first routed to the Order Service, which must then collaborate with the following services:

Restaurant Service - knows the restaurant’s menu and prices Consumer Service - knows the state of the Consumer that places the order Kitchen Service - creates a Ticket, which tells the chef what to cook Accounting Service - authorizes the consumer’s credit card The Order Service could invoke each of these services using synchronous request/response. It might, for example, implement the inter-service communication using REST or gRPC.

However, a key drawback of using synchronous request/response is that it reduces availability. That’s because if any of the Order Sevice’s collaborators are unavailable, it will not be able to create the order and must return an error to the client.

An alternative approach is to eliminate all synchronous communication between the Order Service and its collaborators by using the CQRS and Saga patterns. The Order Service can use the CQRS pattern to maintain a replica of the restaurant menu’s and there by eliminate the need to synchronously fetch data from the Restaurant Service. It can validate the order asynchronously by using the Saga pattern. The Order Service creates an Order in a PENDING state and sends back a response to the POST /order. It then completes the creation of the order by communicating asynchronously with the other services.

Problem How should a service collaborate with other services when handling a synchronous request?

Forces The microservice architecture often distributes the responsibility of handling a request amongst multiple services An operation is typically required to be highly available with a low response time The availability of an operation is the product of the availabilities of the services that are invoked while handling a request: serviceAvailabilitynumberOfSynchronouslyCollaboratingServices A service can retry a request to a failed collaborator but this increases response time.

Solution Design a service so that it can respond to a synchronous request without waiting for the response from any other service.

One way to make a service self-contained is to implement needed functionality as a service module rather than a separate service. We could, for example, merge the Order Service and Restaurant Service.

Example

The Order Service in the FTGO application described earlier is an example of a self-contained service. The createOrder() operation, for example, queries a CQRS replica of data owned by the Restaurant Service to validate and price the order, and then initiates a saga to finish the creation of the order.

Resulting Context This pattern has the following benefits:

Improved availability and response time This pattern has the following drawbacks:

Increased cost and complexity of using CQRS Increased complexity of using sagas Less straightforward API when using sagas Larger service due to functionality being implemented in the service instead of as a separate service

Related patterns

Microservice architecture - creates the need for this pattern

Saga - used by a service self-contained to asynchronously maintain data consistency CQRS - used by a service self-contained to maintain a replica of data owned by other services

Completed Patterns

Motivating Pattern ----------> Solution Pattern Solution A <----------------> Solution B General <-------------------- Specific

Application Architecture Patterns

Microservice architecture - https://microservices.io/patterns/microservices.html

Decomposition Patterns

 > Application Patterns
 Decompose by business capability
 Decompose by subdomain
 Self Contained Services
 Service per team

Data Patterns

Database Architecture Patterns (Database per service and Shared DB)**

Database per Service ---> API Composition <----> CQRS (**API Composition and CQRS , SAGA are 3 solutions for db per service)

For Database per Service we have 2 solutions i.e API Composition and CQRS which are the 2 Querying patterns.

Shared Database.

Maintaining Data Consistency

  • SAGA

  • Domain Event

  • Aggregate

  • Domain Event

UI

  • Server-Side page fragment composition

  • Client-Side UI composition

Observablity

  • Audit logging

  • Application metircs

  • Distributed tracing

  • Health check API

  • Exception tracking

  • Log Aggregation

  • Log deployments and changes

Transactional messaging

  • Transactional Outbox

  • Transaction log failing

  • Polling publisher

Reliability

Circuit Breaker

Communication Style

  • Messaging

  • Remote Procedure Invocation

  • Domain Specific

Cross cutting concerns

Microservice Chassis

Externalized Configuration

Security

Access Token

Infrastructure Patterns

Deployment

SideCar

Service Mesh

Service Deployment platform

Serverless deployment

Service per VM

Service per Container

Serverless deployment

Multiple Services per host

Single Service per Host

Communication Patterns

Discovery

Service Registry

Server-side discovery

3rd party registration

Client Side Discovery

Self Registration

External API

API Gateway

Backends for frontends

Refactoring to microservicesnew

Strangler Application

Anti-corruption layer - https://microservices.io/patterns/refactoring/anti-corruption-layer.html

1. Pattern: Decompose by business capability

Problem How to decompose an application into services?

Solution Define services corresponding to business capabilities.

2. Pattern: Decompose by subdomain

Problem How to decompose an application into services?

Solution

Define services corresponding to Domain-Driven Design (DDD) subdomains. DDD refers to the application’s problem space - the business - as the domain. A domain is consists of multiple subdomains. Each subdomain corresponds to a different part of the business.

Subdomains can be classified as follows:

Core - key differentiator for the business and the most valuable part of the application Supporting - related to what the business does but not a differentiator. These can be implemented in-house or outsourced. Generic - not specific to the business and are ideally implemented using off the shelf software

How to identify sub domains? organization structure - different groups within an organization might correspond to subdomains high-level domain model - subdomains often have a key domain object

Pattern: Strangler application

https://microservices.io/patterns/refactoring/strangler-application.html

Saga MicroServices Pattern

https://www.youtube.com/watch?v=WnZ7IcaN_JA

2 Types of

  • Choreography (Events based -evey ms registered

CONS

  • Can end up in deadlock if one ms fails

  • lesser events better

  • difficult to test

  • Orchestration (Orchestrator is central and it control whole flow)

CONS

Single server so it need to have a mapping of each service and it knows all service - tightly coupled breaks MS.

Strangler Application Pattern

https://www.youtube.com/watch?v=8h3moilCpQ4 GitHub: https://github.com/TechPrimers or https://techprimers.github.io/

Modernizing app by incrementally developing a new (strangler) application around the legacy application. The strangler application has a microservice architecture.

GitHub: https://github.com/TechPrimers or https://techprimers.github.io/

Pattern to migrate monoliths to Microservices

Monolith to modular monoliths - easy to interact within app - easy to navigate in project and we can refactor it is easy to migrate modular monoliths to microservices than non modular microservice.

We can take each module and migrate into a strangler application

what is strangler application pattern? -

monolith to ms case study using an ecomm app

Case Study Ecommerce app

Has - UI, Authentication, Products, Carts, Payment, Orders

  1. Segregate - Payments first as a seperate microservice and have a database for it and invoke it from Ecommerence monolith application.

  2. Segregate - Orders and have it as a seperate microservice and have a database for it and invoke it from Ecomm monolith app.

3)Segregate - Authentication -

when all modules of monolith is segregated then Auth service needs to be used by all services and UI need to communicate with all microservice.

Add Discovery Service (redirect traffice from UI)

Include API Gateway

UI --> API Gateway --> DiscoveryService ---> order --DB ---> product --db

                      Authentication

Designing business logic in a microservice architecture

The heart of an enterprise application is the business logic, which implements the business rules for business logic you can apply object-oriented design principles, or procedural Transcription script pattern. Developing complex business logic is even more challenging in a microservice architecture where the business logic is spread over multiple services.

You need to address two key challenges

  1. First, a typical domain model is a tangled web of interconnected classes. Although this isn’t a problem in a monolithic application, in a microservice architecture, where classes are scattered around different services, you need to **eliminate object references **that would otherwise span service boundaries.

  2. The second challenge is designing business logic that works within the transaction management constraints of a microservice architecture. Your business logic can use ACID transactions within services, but as described in chapter 4, it must use the Saga pattern to maintain data consistency across services.

Fortunately, we can address these issues by using the Aggregate pattern from DDD. The Aggregate pattern structures a service’s business logic as a collection of aggregates. An aggregate is a cluster of objects that can be treated as a unit. There are two reasons why aggregates are useful when developing business logic in a microservice architecture:

Aggregates avoid any possibility of object references spanning service boundaries, because an inter-aggregate reference is a primary key value rather than an object reference.  Because a transaction can only create or update a single aggregate, aggregates fit the constraints of the microservices transaction model.

Business logic organization patterns

Figure 5.1 shows the architecture of a typical service. As described in chapter 2, the business logic is the core of a hexagonal architecture. Surrounding the business logic are the inbound and outbound adapters. An inbound adapter handles requests from clients and invokes the business logic. An outbound adapter, which is invoked by the business logic, invokes other services and applications.

This service consists of the business logic and the following adapters:

 REST API adapter—An inbound adapter that implements a REST API which invokes the business logic  OrderCommandHandlers—An inbound adapter that consumes command messages from a message channel and invokes the business logic  Database Adapter—An outbound adapter that’s invoked by the business logic to access the database  Domain Event Publishing Adapter—An outbound adapter that publishes events to a message broker

The key decision you must make when developing business logic is whether to use an object-oriented approach or a procedural approach. There are two main patterns for organizing business logic: the procedural Transaction script pattern, and the object-oriented Domain model pattern.

Designing business logic using the Transaction script pattern

Figure 5.2 Organizing business logic as transaction scripts. In a typical transaction script–based design, one set of classes implements behavior and another set stores state. The transaction scripts are organized into classes that typically have no state. The scripts use data classes, which typically have no behavior.

Pattern: Transaction script - Organize the business logic as a collection of procedural transaction scripts, one for each type of request.

Designing business logic using the Domain model pattern

The simplicity of the procedural approach can be quite seductive. You can write code without having to carefully consider how to organize the classes. The problem is that if your business logic becomes complex, you can end up with code that’s a nightmare to maintain.

Pattern: Domain model : Organize the business logic as an object model consisting of classes that have state and behavior.

Figure 5.3 Organizing business logic as a domain model. The majority of the business logic consists of classes that have state and behavior and some classes have only state.

5.1.3 About Domain-driven design

DDD also has some tactical patterns that are building blocks for domain models.

Each pattern is a role that a class plays in a domain model and defines the characteristics of the class. The building blocks that have been widely adopted by developers include the following:

Entity—An object that has a persistent identity. Two entities whose attributes have the same values are still different objects. In a Java EE application, classes that are persisted using JPA @Entity are usually DDD entities.

Value object—An object that is a collection of values. Two value objects whose attributes have the same values can be used interchangeably. An example of a value object is a Money class, which consists of a currency and an amount.

Factory—An object or method that implements object creation logic that’s too complex to be done directly by a constructor. It can also hide the concrete classes that are instantiated. A factory might be implemented as a static method of a class.

Repository—An object that provides access to persistent entities and encapsulates the mechanism for accessing the database.

ServiceAn object that implements business logic that doesn’t belong in an entity or a value object.

Designing a domain model using the DDD aggregate pattern

Figure 5.4 A traditional domain model is a web of interconnected classes. It doesn’t explicitly specify the boundaries of business objects, such as Consumer and Order

This example has several classes corresponding to business objects: Consumer, Order,Restaurant, and Courier. But interestingly, the explicit boundaries of each business object are missing from this kind of traditional domain model. It doesn’t specify, for example, which classes are part of the Order business object. This lack of boundaries can sometimes cause problems, especially in microservice architecture.

The problem with fuzzy boundaries

You would certainly load or delete the Order object. But in reality there’s more to an Order than simply the Order object. There are also the order line items, the payment information, and so on.

Besides a conceptual fuzziness, the lack of explicit boundaries causes problems when updating a business object. A typical business object has invariants, business rules that must be enforced at all times. An Order has a minimum order amount, for example. The FTGO application must ensure that any attempt to update an order doesn’t violate an invariant such as the minimum order amount. The challenge is that in order to enforce invariants, you must design your business logic carefully.

For example, let’s look at how to ensure the order minimum is met when multiple consumers work together to create an order. Two consumers—Sam and Mary—are working together on an order and simultaneously decide that the order exceeds their budget.** Sam reduces the quantity of samosas, and Mary reduces the quantity of naan bread. From the application’s perspective, both consumers retrieve the order and its line items from the database. Both consumers then update a line item to reduce the cost of the order. From each consumer’s perspective the order minimum is preserved. Here’s the sequence of database transactions.**

Consumer - Mary

BEGIN TXN SELECT ORDER_TOTAL FROM ORDER WHERE ORDER ID = X SELECT * FROM ORDER_LINE_ITEM WHERE ORDER_ID = X ... END TXN

BEGIN TXN UPDATE ORDER_LINE_ITEM SET VERSION=..., QUANTITY=... WHERE VERSION = AND ID = ... END TXN

Consumer - Sam

BEGIN TXN SELECT ORDER_TOTAL FROM ORDER WHERE ORDER ID = X SELECT * FROM ORDER_LINE_ITEM WHERE ORDER_ID = X ... END TXN Verify minimum is met

BEGIN TXN UPDATE ORDER_LINE_ITEM SET VERSION=..., QUANTITY=...WHERE VERSION = AND ID = ... END TXN

Each consumer changes a line item using a sequence of two transactions. The first transaction loads the order and its line items. The UI verifies that the order minimum is satisfied before executing the second transaction. The second transaction updates the line item quantity using an optimistic offline locking check that verifies that the order line is unchanged since it was loaded by the first transaction.

In this scenario, Sam reduces the order total by $X and Mary reduces it by $Y. As a result, the Order is no longer valid, even though the application verified that the order still satisfied the order minimum after each consumer’s update. As you can see, directly updating part of a business object can result in the violation of the business rules. DDD aggregates are intended to solve this problem.

Aggregates have explicit boundaries

An aggregate is a cluster of domain objects within a boundary that can be treated as a unit. It consists of a root entity and possibly one or more other entities and value objects. Many business objects are modeled as aggregates.

Pattern: Aggregate

Organize a domain model as a collection of aggregates, each of which is a graph of objects that can be treated as a unit.

Figure 5.5 shows the Order aggregate and its boundary. An Order aggregate consists of an Order entity, one or more OrderLineItem value objects, and other value objects such as a delivery Address and PaymentInformation. (Microservices pattern page - 154)

Aggregates decompose a domain model into chunks, which are individually easier to understand. They also clarify the scope of operations such as load, update, and delete. These operations act on the entire aggregate rather than on parts of it. An aggregate is often loaded in its entirety from the database, thereby avoiding any complications of lazy loading. Deleting an aggregate removes all of its objects from a database.

AGGREGATES ARE CONSISTENCY BOUNDARIES

Updating an entire aggregate rather than its parts solves the consistency issues, such as the example described earlier. Update operations are invoked on the aggregate root, which enforces invariants. Also, concurrency is handled by locking the aggregate root using, for example, a version number or a database-level lock. For example, instead of updating line items’ quantities directly, a client must invoke a method on the root of the Order aggregate, which enforces invariants such as the minimum order amount. Note, though, that this approach doesn’t require the entire aggregate to be updated in the database. An application might, for example, only update the rows corresponding to the Order object and the updated OrderLineItem.

Note, though, that this approach doesn’t require the entire aggregate to be updated in the database. An application might, for example, only update the rows corresponding to the Order object and the updated OrderLineItem.

IDENTIFYING AGGREGATES IS KEY

In DDD, a key part of designing a domain model is identifying aggregates, their boundaries, and their roots. The details of the aggregates’ internal structure is secondary. The benefit of aggregates, however, goes far beyond modularizing a domain model. That’s because aggregates must obey certain rules.

Aggregate rules

DDD requires aggregates to obey a set of rules. These rules ensure that an aggregate is a self-contained unit that can enforce its invariants. Let’s look at each of the rules.

RULE #1: REFERENCE ONLY THE AGGREGATE ROOT

The previous example illustrated the perils of updating OrderLineItems directly. The goal of the first aggregate rule is to eliminate this problem. It requires that the root entity be the only part of an aggregate that can be referenced by classes outside of the aggregate. A client can only update an aggregate by invoking a method on the aggregate root. A service, for example, uses a repository to load an aggregate from the database and obtain a reference to the aggregate root. It updates an aggregate by invoking a method on the aggregate root. This rule ensures that the aggregate can enforce its invariant.

RULE #2: INTER-AGGREGATE REFERENCES MUST USE PRIMARY KEYS

Another rule is that aggregates reference each other by identity (for example, primary key) instead of object references. For example, as figure 5.6 shows, an Order references its Consumer using a consumerId rather than a reference to the Consumer object. Similarly, an Order references a Restaurant using a restaurantId.

Figure 5.6 References between aggregates are by primary key rather than by object reference. The Order aggregate has the IDs of the Consumer and Restaurant aggregates. Within an aggregate, objects have references to one another.

This approach is quite different from traditional object modeling, which considers foreign keys in the domain model to be a design smell. It has a number of benefits. The use of identity rather than object references means that the aggregates are loosely coupled. It ensures that the aggregate boundaries between aggregates are well defined and avoids accidentally updating a different aggregate. Also, if an aggregate is part of another service, there isn’t a problem of object references that span services. This approach also simplifies persistence since the aggregate is the unit of storage. It makes it easier to store aggregates in a NoSQL database such as MongoDB. It also eliminates the need for transparent lazy loading and its associated problems. Scaling the database by sharding aggregates is relatively straightforward.

RULE #3: ONE TRANSACTION CREATES OR UPDATES ONE AGGREGATE

Another rule that aggregates must obey is that a transaction can only create or update a single aggregate. When I first read about it many years ago, this rule made no sense! At the time, I was developing traditional monolithic applications that used an RDBMS, so transactions could update multiple aggregates. Today, this constraint is perfect for the microservice architecture. It ensures that a transaction is contained within a service. This constraint also matches the limited transaction model of most NoSQL databases. This rule makes it more complicated to implement operations that need to create or update multiple aggregates. But this is exactly the problem that sagas (described in chapter 4) are designed to solve. Each step of the saga creates or updates exactly one aggregate. Figure 5.7 shows how this works

Aggregate granularity

When developing a domain model, a key decision you must make is how large to make each aggregate. On one hand, aggregates should ideally be small. Because updates to each aggregate are serialized, more fine-grained aggregates will increase the number of simultaneous requests that the application can handle, improving scalability. It will also improve the user experience because it reduces the chance of two users attempting conflicting updates of the same aggregate. On the other hand, because an aggregate is the scope of transaction, you may need to define a larger aggregate in order to make a particular update atomic.

For example, earlier I mentioned how in the FTGO application’s domain model Order and Consumer are separate aggregates. An alternative design is to make Order part of the Consumer aggregate. Figure 5.8 shows this alternative design.

Figure 5.8 An alternative design defines a Customer aggregate that contains the Customer and Order classes. This design enables an application to atomically update a Consumer and one or more of its Orders.

A benefit of this larger Consumer aggregate is that the application can atomically update a Consumer and one or more of its Orders. A drawback of this approach is that it reduces scalability. Transactions that update different orders for the same customer would be serialized. Similarly, two users would conflict if they attempted to edit different orders for the same customer.

Another drawback of this approach in a microservice architecture is that it is an obstacle to decomposition. The business logic for Orders and Consumers, for example, must be collocated in the same service, which makes the service larger. Because of these issues, making aggregates as fine-grained as possible is best.

Designing business logic with aggregates

In a typical (micro)service, the bulk of the business logic consists of aggregates. The rest of the business logic resides in the domain services and the sagas. The sagas orchestrate sequences of local transactions in order to enforce data consistency The services are the entry points into the business logic and are invoked by inbound adapters. A service uses a repository to retrieve aggregates from the database or save aggregates to the database. Each repository is implemented by an outbound adapter that accesses the database. Figure 5.9 shows the aggregate-based design of the business logic for the Order Service. (Page 159). The business logic consists of the Order aggregate, the OrderService service class, the OrderRepository, and one or more sagas. The OrderService invokes the OrderRepository to save and load Orders. For simple requests that are local to the service, the service updates an Order aggregate If an update request spans multiple services, the OrderService will also create a saga, as described in chapter 4. We’ll take a look at the code—but first, let’s examine a concept that’s closely related to aggregates: domain events.

Publishing domain events

Pattern: Domain event

An aggregate publishes a domain event when it’s created or undergoes some other significant change.

Why publish change events?

Domain events are useful because other parties—users, other applications, or other components within the same application—are often interested in knowing about an aggregate’s state changes. Here are some example scenarios:

Maintaining data consistency across services using choreography-based sagas.

Notifying a service that maintains a replica that the source data has changed. This approach is known as Command Query Responsibility Segregation (CQRS), and it’s described in chapter 7.

What is a domain event?

A domain event is a class with a name formed using a past-participle verb. It has properties that meaningfully convey the event. Each property is either a primitive value or a value object. For example, an OrderCreated event class has an orderId property.

A domain event typically also has metadata, such as the event ID, and a timestamp. It might also have the identity of the user who made the change, because that’s useful for auditing. The metadata can be part of the event object, perhaps defined in a superclass.

The DomainEvent interface is a marker interface that identifies a class as a domain event. OrderDomainEvent is a marker interface for events, such as OrderCreated, which are published by the Order aggregate. The DomainEventEnvelope is a class that contains event metadata and the event object. It’s a generic class that’s parameterized by the domain event type.

Event enrichment

Let’s imagine, for example, that you’re writing an event consumer that processes Order events. The OrderCreated event class shown previously captures the essence of what has happened. But your event consumer may need the order details when processing an OrderCreated event. One option is for it to retrieve that information from the OrderService. The drawback of an event consumer querying the service for the aggregate is that it incurs the overhead of a service request. An alternative approach known as event enrichment is for events to contain information that consumers need. It simplifies event consumers because they no longer need to request that data from the service that published the event. In the OrderCreated event, the Order aggregate can enrich the event by including the order details. The following listing shows the enriched event.

Identifying domain events

Figure 5.10 The result of an event-storming workshop that lasted a couple of hours. The sticky notes are events, which are laid out along a timeline; commands, which represent user actions; and aggregates, which emit events in response to a command.

1 Brainstorm events—Ask the domain experts to brainstorm the domain events. Domain events are represented by orange sticky notes that are laid out in a rough timeline on the modeling surface.

2 Identify event triggers—Ask the domain experts to identify the trigger of each event, which is one of the following: – User actions, represented as a command using a blue sticky note – External system, represented by a purple sticky note – Another domain event – Passing of time

3 Identify aggregates—Ask the domain experts to identify the aggregate that consumes each command and emits the corresponding event. Aggregates are represented by yellow sticky notes.

Generating and publishing domain events

GENERATING DOMAIN EVENTS

Conceptually, domain events are published by aggregates. An aggregate knows when its state changes and hence what event to publish. An aggregate could invoke a messaging API directly. The drawback of this approach is that because aggregates can’t use dependency injection, the messaging API would need to be passed around as a method argument. That would intertwine infrastructure concerns and business logic,which is extremely undesirable.

Consuming domain events

Domain events are ultimately published as messages to a message broker, such as Apache Kafka. A consumer could use the broker’s client API directly.

⚠️ **GitHub.com Fallback** ⚠️