Layered Services #
Cut the cake. Divide each service into layers.
Variants:
- Orchestrated Three-Layered Services,
- (Pipelined) Choreographed Two-Layered Services,
- (Pipelined) Command Query Responsibility Segregation (CQRS) [MP, LDDD].
Structure: Subdomain services divided into layers.
Type: Implementation of Services, Pipeline or Monolith, correspondingly.
Layered Services is an umbrella architecture for common implementations of systems of Services. It does not introduce any special features as layers are completely encapsulated by the service which they belong to. Still, as the services may communicate at different layers, there are a couple of things to learn by exploring the subject matter.
Performance #
Layered Services are similar to Services performance-wise: use cases that involve a single service are the fastest, those that need to synchronize states of multiple services are the slowest.
Remarkable features of Layered Services include:
- Independent scaling of layers of the services. It is common to have multiple instances (with the number varying from service to service and changing dynamically under load) of the layers that contain business logic while the corresponding data layers (databases) are limited to a single instance.
- The option to establish additional communication channels between lower layers in order to drive CQRS databases (read/write replicas of the same database) or CQRS Views (cached subsets of data from other services) [MP].
Variants #
Layered Services vary in the number of layers and in the layer through which the services communicate:
Orchestrated Three-Layered Services #
Probably the most common backend architecture has three layers: application, domain, and infrastructure [DDD]. The application layer orchestrates the domain layer.
If such an architecture is divided into services, each of them receives a part of every layer, including application, which means that now there are as many Orchestrators as services. Each Orchestrator implements the API of its service by integrating (calling or messaging into) the domain layer of its service and APIs of other services, which makes all the Orchestrators interdependent:
Dependencies #
The upper (application) layer of each service orchestrates both its middle (domain) layer and the upper layers of other services, resulting in mutual orchestration and interdependencies.
The good thing is that the majority of the code belongs to the domain layer which depends only on its databases. The bad thing is that changes in the application of one service may affect the application layers of all of the other services.
Relations #
Three-layered services:
- Implement Services.
- Are derived from Layers and Services.
- Have multiple Integration (sub)Services (Orchestrators).
Evolutions #
Orchestrated Layered Services may become coupled, which is resolved either by merging their layers:
- A part of or the whole application layer can be merged into a shared Orchestrator.
- Some or all the databases can be united into a Shared Database or shared as Polyglot Persistence.
or by building derived datasets:
- A CQRS View [MP] inside a service aggregates any events from other services which its owner is interested in.
- A dedicated Query Service [MP] captures the whole system’s state by subscribing to events from all the services.
If the services become too large:
- The middle layer can be split into Cells.
Choreographed Two-Layered Services #
If there is no orchestration, there is no role for the application layer. Choreographed systems are made up of services that implement individual steps of request processing. The sequence of actions (integration logic) which three-layered systems put in the Orchestrators now moves to the graph of event channels between the services. This means that with choreography the high-level part of the business logic (use cases) exists outside of the code of the constituent services.
Dependencies #
Dependencies are identical to those of a Pipeline or choreographed Services except that each service also depends on its database.
Relations #
Two-layered services:
Evolutions #
If Choreographed Layered Services become coupled:
- The business logic of two or more services can be merged together, resulting in Polyglot Persistence.
- Some databases can be united into a Shared Database or shared as Polyglot Persistence.
CQRS Views [MP] or Query Services [MP] are also an option:
An overgrown service can be:
- Split in two
Command Query Responsibility Segregation (CQRS) #
Command Query Responsibility Segregation (CQRS) [MP, LDDD] is, essentially, the division of a layered application or a service into two (rarely more) services, one of which is responsible for write access (handling commands) to the domain data while the other(s) deal with read access (queries), thus creating a data pipeline (see the diagram below). Such an architecture makes sense when the write and read operations don’t rely on a common vision (model) of the domain, for example, writes are individual changes (OLTP) that require cross-checks and validation of input while reads show aggregated data (OLAP) and may take long time to complete (meaning that forces for the read and write paths differ). If there is nothing to share in the code, why not separate the implementations?
This separation brings in the pros and cons of Services: commands and queries may differ in technologies (including database schemas or even types), forces, and teams at the expense of consistency (database replication delay) and increasing the system’s complexity. In addition, for read-heavy applications the read database(s) is easy to scale.
CQRS has several variations:
- The database may be shared, commands and queries may use dedicated databases, or the read service may maintain a Memory Image / Materialized View [DDIA] fed by events from the write service (as in other kinds of Layered Services).
- Data replication may be implemented as a pipeline between the databases (based on nightly snapshots or log-based replication) or a direct event feed from the OLTP code to the OLAP database.
It is noteworthy that while ordinary Layered Services usually communicate through their upper-level components that drive use cases, a CQRS system is held together by spreading data changes through its lowest layer.
Examples: Martin Fowler has a short article and Microsoft a longer one.
Dependencies #
Each backend depends on its database (its technology and schema). The OLTP to OLAP data replication requires an additional dependency that corresponds to the way the replication is implemented:
Relations #
CQRS:
- Implements Monolith (a whole system or a single service).
- Is derived from Layers and Pipeline.
- Is a development of Polyglot Persistence.
Evolutions #
- You will usually need a Reverse Proxy or an API Gateway to segregate commands from queries.
- If the commands and queries become intermixed, the business logic can be merged together but the databases are left separate, resulting in Polyglot Persistence.
- Both read and write backends can be split into Layers or Services (yielding Cells).
- Applying Space-Based Architecture may further improve performance.
- Multiple schemas or even kinds of OLAP databases can be used simultaneously (Polyglot Persistence).
Summary #
Layered Services is an umbrella pattern that conjoins:
- Three-Layered Services where each service orchestrates other services.
- Two-Layered Services that form a Pipeline.
- CQRS that separates read and write request processing paths.