16 Views How to Implement Cooperation of Services and Data in Various Layers of Microservices

16 Views - How to Implement Cooperation of Services and Data in Various Layers of Microservices #

Hello, I am Ou Chuangxin.

In the DDD layered architecture and microservice code model, we divide the domain objects into layers based on the attributes and dependencies of the domain objects, and define corresponding code objects and directory structures. The layered architecture determines the overall structure of the microservice, and the main objects within the microservice include services and entities, which collaborate to complete the business logic.

So, how do these services and entities collaborate in the layers of the microservice during runtime? Today, we will dissect microservices based on the DDD layered architecture to see what its internal structure looks like.

Collaboration of Services #

1. Types of Services #

Let’s first review the services in the layered architecture. In the microservices designed according to the layered architecture, there are facade services, application services, domain services, and infrastructure services. The main functions and responsibilities of each layer of services are as follows.

Facade Service: Located in the user interface layer, it includes both interface and implementation parts. It is used to handle user’s Restful requests, parse the configuration files input by users, etc., and pass the data to the application layer. Alternatively, after obtaining the data from the application layer, it assembles the Data Objects (DO) into Data Transfer Objects (DTO) and transfers the data to the frontend application.

Application Service: Located in the application layer. It represents the application and user behavior, responsible for the composition, coordination, and forwarding of services, as well as the execution order of business use cases and the assembly of results. It provides coarse-grained services to the outside.

Domain Service: Located in the domain layer. Domain services encapsulate the core business logic that requires cooperation between multiple entities. They combine or orchestrate the business logic of multiple entities or encapsulate entity methods in a strict layered architecture to provide domain services for the application layer.

Infrastructure Service: Located in the infrastructure layer. It provides basic resource services (such as databases, caches, etc.) to decouple various layers and reduce the impact of external resource changes on business application logic. Infrastructure services mainly include repository services, which provide basic resource services through dependency inversion. Both domain services and application services can call the repository service interface to achieve data persistence.

2. Service Invocation #

Let’s take a look at the following diagram. The service invocation in microservices includes three main scenarios: intra-layer service invocation, inter-service invocation, and domain event-driven.

img

Intra-Layer Service Invocation in Microservices

In the microservices architecture, the front-end application is often deployed independently using the front-end and back-end separation design pattern. The front-end application calls the Facade service published on the API gateway, which directs to the application service. The application service serves as the organizer and orchestrator of services, and it has two paths for service invocation:

The first one is when the application service calls and assembles the domain service. At this time, the domain service assembles the entities and entity methods to implement the core domain logic. The domain service obtains persistent data objects through the repository service to complete entity data initialization.

The second one is when the application service directly calls the repository service. This is mainly for accessing basic layer data such as caches and files. This type of data mainly involves query operations, without much domain logic, and does not involve the domain layer or the persistence of database objects.

Inter-Service Invocation in Microservices

The application services between microservices can be accessed directly or through the API gateway. As cross-service operations, when performing data creation and modification operations, you need to pay attention to distributed transactions to ensure data consistency.

Domain Event-Driven

Domain event-driven involves events within a microservice and between microservices (refer to Lecture 06). Within a microservice, asynchronous processing between aggregates is achieved through the event bus (EventBus). Between microservices, it is achieved through message middleware. The asynchronous domain event-driven mechanism is an indirect way of accessing services.

After the application service completes the business logic processing and a domain event occurs, you can call the event publishing service to complete the event publishing.

When receiving the subscribed topic data, the event subscription service will call the event processing domain service to perform further business operations.

3. Service Encapsulation and Composition #

Let’s take a look at the following diagram. Microservices are encapsulated, composed, and exposed from the domain layer upwards.

img

Infrastructure Layer

The service form in the infrastructure layer is mainly the repository service. The repository service includes both interface and implementation parts. The repository interface service is called by the application layer or the domain layer, and the repository implementation service completes the persistence or data initialization of domain objects.

Domain Layer

The domain layer implements the core business logic, expressing the business concepts, business states, and business rules of the domain model. The main service forms are entity methods and domain services.

The entity adopts the rich model, and all business logic related to the entity is implemented internally in methods of the entity class. The entity is the atomic business logic unit of microservices. When designing, we mainly consider the attributes and business behaviors of the entity itself, which are the core basic capabilities of the domain model. We do not need to consider external operations and business processes too much in order to ensure the stability of the domain model. DDD advocates for rich domain models, aiming to attribute business logic to entity objects as much as possible. For parts that cannot be attributed, they should be designed as domain services. Domain services assemble and coordinate multiple entities or entity methods to implement complex core business logic that spans across multiple entities.

In a strict layered architecture, if a method of a single entity needs to be exposed to the application layer, it needs to be encapsulated by a domain service before it can be exposed to the application service.

Application Layer

The application layer is responsible for expressing application and user behaviors. It is responsible for service composition, orchestration, and forwarding, handling the execution sequence of business use cases and assembling their results, coordinating services and data between different aggregates, and publishing and subscribing to events between microservices.

Through application services, the internal functionality of microservices can be exposed externally, thus hiding the complexity of core business logic and internal implementation mechanisms in the domain layer. The main forms of services in the application layer are application services, event publishing and subscribing services.

Application services are used for composition and orchestration. They mainly come from domain services and can also be application services of external microservices. In addition to completing service composition and orchestration, application services can also perform functions such as security authentication, permission verification, preliminary data validation, and distributed transaction control.

To achieve decoupling between aggregates within microservices, service invocations and data interactions between aggregates should be performed through application services. In principle, direct invocation of domain services between aggregates and association of data tables between aggregates should be prohibited.

User Interface Layer

The user interface layer is the bridge between the front-end application and the microservices in terms of service access and data exchange. It handles Restful requests sent by the front-end and parses user input configuration files, then passes the data to the application layer. It also assembles the data obtained from the application service to provide data services to the front-end. The main form of service is the Facade service.

The Facade service consists of two parts: interface and implementation. It serves to direct services, convert and assemble DO (Domain Object) and DTO (Data Transfer Object) data, and perform data conversion and exchange between the front-end and the application layer.

4. Service Dependency in Two Layered Architectures #

Now let’s review the DDD layered architecture. A key principle of layered architecture is that each layer can only have dependencies on the layer directly below it.

Based on the degree of dependency tightness, layered architecture can be divided into two types: strict layered architecture and loose layered architecture. In a strict layered architecture, any layer can only have dependencies on the layer directly below it. In a loose layered architecture, any layer can have dependencies on any layer below it.

Now let’s analyze and compare these two types of layered architectures in detail.

Service Dependency in Loose Layered Architecture

Let’s take a look at the following diagram. In a loose layered architecture, the entity methods and domain services in the domain layer can be directly exposed to the application layer and the user interface layer. The service dependency in a loose layered architecture does not require encapsulation at each level and can be quickly exposed to the upper layers.

However, it has some problems. Firstly, it is easy to expose the implementation logic of the core business in the domain layer. Secondly, when the entity methods or domain services undergo service changes, it is difficult to identify which upper-layer services called and composed them, making it inconvenient to notify all service consumers.

img

Let’s take another look at this diagram. In a loose layered architecture, after the methods of entity A are composed in the application layer, they are exposed to the user interface layer through the aFacade service. The domain service abDomainService directly exposes itself to the user interface layer through the abFacade service, bypassing the application layer. In a loose layered architecture, any lower-level service can be exposed to any upper-level service.

img

Service Dependency in Strict Layered Architecture

Let’s take a look at the following diagram. In a strict layered architecture, each layer can only provide services to the immediately higher layer. Although entities, entity methods, and domain services are all in the domain layer, entities and entity methods can only be exposed to domain services, and domain services can only be exposed to application services.

In a strict layered architecture, if a service needs to be invoked across layers, the lower-level service needs to be encapsulated by the upper-level service before it can provide cross-layer services. For example, entity methods need to be encapsulated into domain services in order to provide services to application services.

This is because encapsulation allows you to avoid exposing the implementation logic of core business to the outside world. By encapsulating entities and methods into domain services, you can also prevent excessive accumulation of core business logic that should belong to the domain layer in the application layer, avoiding the bloating of the application layer. Additionally, when a service undergoes changes, since the service is only called and composed by the immediately higher-level services, you only need to notify the immediately higher-level services, resulting in better manageability compared to loose layered architecture.

img

Let’s take another look at this diagram. The method of entity A needs to be encapsulated into the aDomainService domain service before it can be exposed to the aAppService application service. The abDomainService domain service composes and encapsulates the methods of entities A and B, and then exposes itself to the abAppService application service.

img

Data Object Views #

In DDD, there are many data objects distributed across different layers. They have different forms at different stages. You can review [Lecture 04], which explains in detail.

Let’s first take a look at what types of data objects exist within a microservice and how they collaborate and transform.

Persistent Object (PO) is a data entity that maps one-to-one with the database structure and serves as the carrier of data persistence.

Domain Object (DO) is the entity at runtime of a microservice and serves as the carrier of core business.

Data Transfer Object (DTO) is used for data assembly and transfer between the front-end and the application layer or microservice, serving as the carrier for data exchange between applications.

View Object (VO) is used to encapsulate the data of a specific page or component in the presentation layer.

Let’s examine the responsibilities and conversion processes of data objects in various layers of a microservice using the following diagram.

img

Infrastructure Layer

The main objects in the infrastructure layer are PO objects. We need to establish the mapping relationship between DO and PO. When DO data needs to be persisted, the repository service converts the DO object into a PO object and performs the database persistence operation. When DO data needs to be initialized, the repository service retrieves data from the database to form a PO object, and then converts the PO object into a DO object to complete the data initialization.

In most cases, DO and PO have a one-to-one correspondence. However, there may be cases where DO and PO have a many-to-many relationship, in which case data needs to be reorganized during the conversion between DO and PO.

Domain Layer

The main objects in the domain layer are DO objects. DO is the carrier of data and business behavior for entities and value objects, bearing the basic core business logic. By converting between DO and PO, we can achieve data persistence and initialization.

Application Layer

The main objects in the application layer are DO objects. If an application service needs to call another microservice, the DO object will be converted into a DTO object to accomplish data assembly and transfer across microservices. The user interface layer first converts DTO to DO, and then the application service receives DO for business processing. If there is a one-to-many relationship between DTO and DO, data reorganization is required during the conversion of DO.

User Interface Layer

The user interface layer completes the mutual conversion between DO and DTO to accomplish data interaction and transformation between the microservice and the front-end application. The Facade service assembles multiple DO objects and converts them into DTO objects, completing data conversion and transfer to the front-end application.

Front-end Application

The main object in the front-end application is the VO object. The presentation layer uses VO for interface display, and data interaction between the user interface layer and the application layer adopts DTO objects.