Services: add layers

Services: add layers #

The most common modifications to a system of Services involve supplementary system-wide layers which compensate for the inability of the Services to share anything among themselves:

Those layers may also be merged into Combined Components:

Add a Middleware #

Services add Middleware

Patterns: Middleware, Services.

Goal: take care of scaling, recovery, and interservice communication without programming it.

Prerequisite: communication between the services is uniform.

Distributed systems may fail in a zillion ways. You want to ruminate neither on that nor on heisenbugs. And you probably want to have a framework for scaling the services and restarting them after failure. Get a third-party Middleware! Let your programmers write the business logic, not infrastructure.

Pros:

  • You don’t invest your time in infrastructure.
  • Scaling and error recovery are made easy.

Cons:

  • There may be a performance penalty which becomes worse for uncommon patterns of communication.
  • The Middleware may be a single point of failure.

Further steps:

Use a Service Mesh #

Multifunctional - Service Mesh

Patterns: Service Mesh (Mesh, Middleware), Sidecar (Proxy), Services.

Goal: support dynamic scaling and interservice communication out of the box; share libraries among the services.

Prerequisite: service instances are mostly stateless.

The Microservices architecture boasts dynamic scaling under load thanks to its Mesh-based Middleware. It also allows for the services to share libraries in Sidecars [DDS] – additional containers co-located with each service instance – to avoid duplication of generic code among the services.

Pros:

  • Dynamic scaling and error recovery.
  • Available out of the box.
  • Provides a way to implement shared aspects (cross-cutting concerns) once and use the resulting libraries in every service.

Cons:

  • Performance degrades because of the complex distributed infrastructure.
  • You may suffer vendor lock-in.

Use a Shared Repository #

Services to Shared Database

Patterns: Shared Repository, Services.

Goal: let the services share data, don’t invest in operating multiple databases.

Prerequisite: the services use a uniform approach to persisting their data.

You don’t really need every service to have a private database. A shared one is enough in many cases.

Pros:

  • It is easy for the services to share and synchronize data.
  • Lower operational complexity.

Cons:

  • All the services depend on the database schema which becomes hard to alter.
  • The single database will limit performance of the system.
  • It may also become a single point of failure.

Further steps:

Add a Proxy #

Services add Proxy

Patterns: Proxy, Services.

Goal: use a common infrastructure component on behalf of your entire system.

Prerequisite: the system serves its clients in a uniform way.

Putting a generic component between the system and its clients helps the programmers concentrate on business logic rather than protocols, infrastructure or even security.

Pros:

  • You get a choice of generic functionality without investing development time.
  • It is an additional layer that isolates your system from both clients and attackers.

Cons:

  • There is a latency penalty caused by the extra network hop.
  • Each Proxy may be a single point of failure or at least needs some admin oversight.

Further steps:

  • You can always add another kind of Proxy.
  • If there are multiple clients that differ in their protocols, you can employ a stack of Proxies per client, resulting in Backends for Frontends.

Use an Orchestrator #

Services use Orchestrator

Patterns: Orchestrator, Services.

Goal: have the high-level logic of use cases distilled as intelligible code.

Prerequisite: the use cases comprise sequences of high-level steps (which is very likely to be true for a system of subdomain services).

When a use case jumps over several services in a dance of choreography, there is no easy way to understand it as there is no single place to see it in the code. It may be even worse with Pipelined systems where use cases are embodied in the structure of event channels between the components.

Extract the high-level business logic from the choreographed services or their interconnections and put it into a dedicated component.

Pros:

  • You are not limited in the number and complexity of use cases anymore.
  • Global use cases become much easier to debug.
  • You have a new team dedicated to the interaction with customers, freeing the other teams to study their parts of the domain or work on improvements.
  • Many changes in the high-level logic can be implemented and deployed without touching the main services.
  • The extra layer decouples the main services from the system’s clients and from each other.

Cons:

  • There is a performance penalty because the number of messages per use case doubles.
  • The Orchestrator may become a single point of failure.
  • Some flexibility is lost as the Orchestrator couples qualities of the services.

Further steps:

  • If there are several clients that strongly vary in workflows, you can apply Backends for Frontends with an Orchestrator per client.
  • If the Orchestrator grows too large, it can be divided into layers, services or both, the latter option resulting in a Top-Down Hierarchy.
  • The Orchestrator can be scaled and can have its own database.

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