Layered Services

Layered Services

A diagram for Layered Services, in abstractness-subdomain-sharding coordinates.

Cut the cake. Divide each service into layers.

Structure: Subdomain services divided into layers.

Type: Implementation of Services, Pipeline or Monolith.

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 use of scaled stateless services and load balancers in Layered Services.
  • 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].
Data streams in Three-Layered Services: from data layer to data layer, from domain layer to data layer, and between two domain-level components.

Variants#

Layered Services vary in the number of layers and in the layer through which the services communicate:

Orchestrated Three-Layered Services#

In three-layered services the application layer of every service calls the domain layer of its service and the application layers of other 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.

In Layered Services only the application layers of the services are interdependent.

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:

Evolutions#

Orchestrated Layered Services may become coupled, which is resolved either by merging their layers:

Diagrams for Three-Layered Services with partially merged application layer, partially merged databases and shared databases, and a Sandwich.

or by building derived datasets:

  • A CQRS View inside a service aggregates any events from other services which its owner is interested in.
  • A dedicated Query Service captures the whole system’s state by subscribing to events from all the services.
Diagrams for Three-Layered Services employing CQRS views and a Query Service.

If the services become too large:

The domain layer of a large three-layered service is split into sub-subdomain components, resulting in a Sandwich Cell.

Choreographed Two-Layered Services#

The domain-level components of two-layered services participate in multiple pipelines and access their service's databases.

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:

Diagrams for Two-Layered Services with partially merged domain layer, partially merged databases, and shared databases.

CQRS Views or Query Services are also an option:

Diagrams for Two-Layered Services employing CQRS views and a Query Service.

An overgrown service can be:

  • Split in two
The domain layer of a large two-layered service is split in half.

Command Query Responsibility Segregation (CQRS)#

Write requests from a client go to the write backend and OLTP database which feeds OLAP databases. Read requests go to the scaled read backend and the scaled OLAP database.

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?

In CQRS data streams from the client to the write backend, then to the OLTP database, to the OLAP database, to the read backend and, finally, returns to the client.

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:

In CQRS the OLAP databases receive data from the OLTP database or from events originating in the write backend. Alternatively, the read and write backends may share a 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:

In CQRS each service depends on its database while the OLAP database depends on the source of its event feed.

Relations#

CQRS:

Evolutions#

Diagrams of CQRS behind an API Gateway, with a single backend, with multiple OLAP databases, with layered backends, Cells for backends, and Data Grid for a database.

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.

CC BY Denys Poltorak. Editor: Lars Noodén. Download from Leanpub or GitHub. Made with odt2wiki and Hugo Book.