Tech

Share microservices design patterns

Photo of Prashant Harbola Written by Prashant Harbola,   Nov 24, 2022

What are microservices 

Microservices is an architectural style that structures an application as a collection of services that are

  • Highly maintainable and testable
  • Loosely coupled
  • Independently deployable
  • Organised around business capabilities
  • Owned by a small team

 

What are design patterns – A definition 

A design pattern is a general repeatable solution to a commonly occurring problem in software design. A design pattern isn't a finished design that can be transformed directly into code. It is a description or template for solving a challenge, and it can be applied in many different situations.

 

Microservices Design Patterns 

Microservice architectures should be evolutionary; hence, a robust design is of utmost significance. Below are some of the common microservices design patterns. Each of these patterns cannot be seen as better or worse than another but rather as choosing a specific set of trade-offs that prioritise different things along the way. They all obey the microservices architectural goals (speed, scalability, cohesion) but arrive at them differently. The intended state is to design and implement a microservices architecture using a mixture of these patterns rather than choose one and migrate towards it.

 

Introductory patterns

Fine-grained SOA

Fine-grained SOA is arguably the “big bang” of microservices. In most cases, this pattern is applied as an extension of service-orient-ed integration, where the point of each service is to provide connectivity to external systems. Fine-grained SOA reduces the issues experienced with SOA and applies the same principles but breaks down the design into smaller, more fine-grained pieces.

fine-graned soa

Key characteristics:

  • It works well at a low scale, but pain emerges at a high scale.
  • It tends to be integration-centric, with each microservice depending on external systems.
  • There is inefficient inter-process communication to achieve a high speed of change.
  • Data consistency and state management are poor but allow existing systems to be leveraged.
  • This forms tight dependencies to those external stores, making the speed of change drag and making the cohesion of the system reflect the internal state of those applications
Layered APIs 

MuleSoft advocates an approach termed “API-led connectivity.” Simply speaking, this can be thought of as a layered approach to API design (at least for the purposes of this paper). System APIs expose systems, Process APIs orchestrate them, and Experience APIs provide end-user experiences. This approach is well-aligned with fine-grained SOA, and, often, the two either can co-exist or else fine-grained, layered APIs become an evolutionary pattern that follows fine-grained SOA.

This approach gives some structure to a fine-grained API approach, allowing some ability to consistently manage and reason with the APIs or microservices. However, there are some issues with this approach that are similar to fine-grained SOA. By creating layers of microservices that are grouped by purpose (systems, processes or domain models, and experience), you can manage the complexity of the architecture more easily.

soa architechture

Where fine-grained SOA makes a single network call, now you must make multiple calls through the layers. This can be inefficient from the perspective of “network hops,” however, the existence of layers does not mandate them. Ultimately, this is a good pattern for most enterprises, but there will be pain along the way. However, this pain exhibits mostly at large scale

Impacts:

  • The ability to change rapidly is improved through standardisation and further decomposition.
  • The number of inter-process calls can be increased because of the more specialised microservices structures.
  • It can theoretically improve scalability.
  • Allows high degrees of reuse.
  • There is high cohesion due to the structured architectural approach.

 

Managed State Patterns

The following patterns are all focused on managing the state. The state is ultimately one of the most challenging aspects of a distributed architecture because traditional system design favours consistent data queries and mutations, even though consistency is difficult in a sufficiently distributed architecture.

 

Message-oriented state management over Layered APIs

This is usually the first pattern implemented to avoid the side effects of accessing and mutating the state. By providing an asynchronous queue as the primary mechanism to communicate state changes (by command or event) or to query other microservices, we allow each microservice the time necessary to converge events and therefore provide a consistent external view. Using a message queue allows the state to be asynchronously and reliably sent to different locations.

Application:

When a change in data occurs, it is sent as a message over a queue or ESB to any other microservice or store that needs to be notified of the change.

message flow

Impacts:

  • It can increase complexity by providing a new way for the state to change and move.
  • It does not offer any standard patterns, so the implementation can be inconsistent unless standards are agreed upon and applied.
  • It does not provide any specific opinions on how to deal with data conflicts or rebuild the state in the case of failures or outages.

 

Event Driven state management over Layered APIs

Event-driven architectures are nothing new. Mule ESB, for example, was initially designed as an event-driven system. But when overlaid on microservice patterns, they provide some powerful abstractions. Event-driven systems usually use a queue of some kind (like message-oriented systems)but enforce a standard around the design and behaviour of what is passed over the queue, specifically, the concept of an event.

Event Driven state management over Layered APIs

To ensure data integrity, there is a need to replicate key business events to synchronise between microservices or data stores. Use a common event abstraction to represent the unit of change in the architecture.

Application:

When something changes in the business, an event encapsulating it in the past tense is sent to interested parties. Changes in the business are the product of these events being sent and processed.

 

Isolating state and Layered APIs

An alternative to coalescing the exchange pattern of micro-services architecture (for example, into events) is to coalesce the internal consistency of each microservice. Rather than expect consistency in the interchange, expect consistency at the time of the query.

This is done most commonly by isolating state or, in other words, “each microservice contains its own state.” In this pattern, each microservice contains an internal data store that it constantly reconciles with external stores (be they an event log or an enterprise asset) to become the “single source of truth.” This pattern can also be thought of as something of a “distributed database,” with each microservice almost representing a column in a traditional RDBMS design.

Isolating state and Layered APIs

Microservices contain a data store that is the source of truth for the entity they represent. For example, a “product” microservice could contain a MySQL database that contains all information about the product and is the only way to query or update that concept in the organisation.

 

Replicating state in Layered APIs (Event Sourcing)

Replicating state is essentially the antidote to the problems that emerge from isolating state; specifically, that consistency is required. A simple example is if we imagine a Catalog, Pricing, and Currency micro-service. If each of those contains an isolated state of each thing, they become interdependent. And failure or change in one can cause the function of the other(s) to fail.

This problem is addressed by replicating the state; in other words, by providing a single place to store all state mutations so that each isolated micro-service can rebuild its internal state. Often, this is coexistent with event sourcing, where event-driven microservices communicate exclusively via an immutable event log — providing a separate single source of truth that is consistent but difficult to query. The microservices that provide the ability to query state, therefore, have done the work of “materialising a view” of the event log.

Replicating state in Layered APIs

Application:

Send all changes as events to a permanent Event Log. When needing to query data, build a materialised view by computing all the changes from the event log. This is often streamlined by creating snapshots along the way of the views so that full re-computation is not required every time.

Impacts:

  • It creates a very cohesive architecture.
  • It is extremely scalable due to the inherent Command-Query Request Separation in the design.
  • It can be difficult to visualise and understand logical dependencies (physical dependencies have been explicitly reduced).

 

Best practices for establishing micro-services patterns 

To make the architecture work and get microservices functional, there are several foundational best practices an organisation needs to adopt. Some of these best practices are outlined below.

Containerisation

The use of containers to structure, isolate, and manage units of deployment is generally useful, even when applied to monolithic software.

Minimise Dependencies

When you’re deploying many changes frequently, it is important to ensure that your component has minimal dependencies on external systems (for example, by using queues for communication rather than synchronous request/reply patterns.

Monitor Everything

A sufficiently complex infrastructure requires visibility. Microservices practitioners learn the hard way that it’s essential to have an effective monitoring solution baked into their software

Focused domain-driven design(DDD)

it’s inarguable that the practices proposed by DDD are of high value and make a lot of sense to apply in general.

 

Conclusion: establish a microservices pattern that’s right for you

Since each organisation's requirements are unique, while designing microservices, instead of adopting microservices as a singular approach, we should rather consider micro-services as a series of overlapping patterns that you can pick and choose depending on your organisation’s needs. There’s no need to adopt every aspect of microservices all at once; it can be adopted in a pragmatic way in a manner that makes sense for your organisation.

The important thing is to remember that microservices are not a cure-all that will solve all of your problems. It is an architecture designed to overcome obstacles that, when deployed correctly, will produce certain desired results.

We’d love to hear your opinion on this post