Microservice Architecture and its 10 Most Important Design Patterns
By: Md Kamaruzzaman
Tags: Software Development, Software Architecture, Programming, Design Patterns, Microservices

Design Patterns for Microservice Architecture

Database per Microservice

Once a company replaces the large monolithic system with many smaller microservices, the most important decision it faces is regarding the Database. In a monolithic architecture, a large, central database is used. Many architects favor keeping the database as it is, even when they move to microservice architecture. While it gives some short-term benefit, it is an anti-pattern, especially in a large-scale system, as the microservices will be tightly coupled in the database layer. The whole object of moving to microservice will fail (e.g., team empowerment, independent development).

A better approach is to provide every Microservice its own Data store, so that there is no strong-coupling between services in the database layer. Here I am using the term database to show a logical separation of data, i.e., the Microservices can share the same physical database, but they should use separate Schema/collection/table. It will also ensure that the Microservices are correctly segregated according to the Domain-Driven-Design.

Database per Microservice by Md Kamaruzzaman

Pros

Complete ownership of Data to a Service.

Loose coupling among teams developing the services.

Cons

Sharing data among services becomes challenging.

Giving application-wide ACID transactional guarantee becomes a lot harder.

Decomposing the Monolith database to smaller parts need careful design and is a challenging task.

When to use Database per Microservice

In large-scale enterprise applications.

When the team needs complete ownership of their Microservices for development scaling and development velocity.

When not to use Database per Microservice

In small-scale applications.

If one team develops all the Microservices.

Enabling Technology Examples

All SQL, NoSQL databases offer logical separation of data (e.g., separate tables, collections, schemas, databases).

Further Reading

Event Sourcing

In a Microservice Architecture, especially with Database per Microservice, the Microservices need to exchange data. For resilient, highly scalable, and fault-tolerant systems, they should communicate asynchronously by exchanging Events. In such a case, you may want to have Atomic operations, e.g., update the Database and send the message. If you have SQL databases and want to have distributed transactions for a high volume of data, you cannot use the two-phase locking (2PL) as it does not scale. If you use NoSQL Databases and want to have a distributed transaction, you cannot use 2PL as many NoSQL databases do not support two-phase locking.

In such scenarios, use Event based Architecture with Event Sourcing. In traditional databases, the Business Entity with the current “state” is directly stored. In Event Sourcing, any state-changing event or other significant events are stored instead of the entities. It means the modifications of a Business Entity is saved as a series of immutable events. The State of a Business entity is deducted by reprocessing all the Events of that Business entity at a given time. Because data is stored as a series of events rather than via direct updates to data stores, various services can replay events from the event store to compute the appropriate state of their respective data stores.

Event Sourcing by Md Kamaruzzaman

Pros

Provide atomicity to highly scalable systems.

Automatic history of the entities, including time travel functionality.

Loosely coupled and event-driven Microservices.

Cons

Reading entities from the Event store becomes challenging and usually need an additional data store ( CQRS pattern)

pattern) The overall complexity of the system increases and usually need Domain-Driven Design.

The system needs to handle duplicate events (idempotent) or missing events.

Migrating the Schema of events becomes challenging.

When to use Event Sourcing

Highly scalable transactional systems with SQL Databases.

Transactional systems with NoSQL Databases.

Highly scalable and resilient Microservice Architecture.

Typical Message Driven or Event-Driven systems (e-commerce, booking, and reservation systems).

When not to use Event Sourcing

Lowly scalable transactional systems with SQL Databases.

In simple Microservice Architecture where Microservices can exchange data synchronously (e.g., via API).

Enabling Technology Examples

Event Store: EventStoreDB, Apache Kafka, Confluent Cloud, AWS Kinesis, Azure Event Hub, GCP Pub/Sub, Azure Cosmos DB, MongoDB, Cassandra. Amazon DynamoDB,

Frameworks: Lagom, Akka, Spring, akkatecture, Axon, Eventuate

Further Reading

Command Query Responsibility Segregation (CQRS)

If we use Event Sourcing, then reading data from the Event Store becomes challenging. To fetch an entity from the Data store, we need to process all the entity events. Also, sometimes we have different consistency and throughput requirements for reading and write operations.

In such use cases, we can use the CQRS pattern. In the CQRS pattern, the system's data modification part (Command) is separated from the data read (Query) part. CQRS pattern has two forms: simple and advanced, which lead to some confusion among the software engineers.

In its simple form, distinct entity or ORM models are used for Reading and Write, as shown below:

CQRS (simple) by Md Kamaruzzaman

It helps to enforce the Single Responsibility Principle and Separation of Concern, which lead to a cleaner design.

In its advanced form, different data stores are used for reading and write operations. The advanced CQRS is used with Event Sourcing. Depending on the use case, different types of Write Data Store and Read Data store are used. The Write Data Store is the “System of Records,” i.e., the entire system's golden source.

CQRS (advanced) by Md Kamaruzzaman

For the Read-heavy applications or Microservice Architecture, OLTP database (any SQL or NoSQL database offering ACID transaction guarantee) or Distributed Messaging Platform is used as Write Store. For the Write-heavy applications (high write scalability and throughput), a horizontally write-scalable database is used (public cloud global Databases). The normalized data is saved in the Write Data Store.

NoSQL Database optimized for searching (e.g., Apache Solr, Elasticsearch) or reading (Key-Value data store, Document Data Store) is used as Read Store. In many cases, read-scalable SQL databases are used where SQL query is desired. The denormalized and optimized data is saved in the Read Store.

Data is copied from the Write store to the read store asynchronously. As a result, the Read Store lags the Write store and is Eventual Consistent.

Pros

Faster reading of data in Event-driven Microservices.

High availability of the data.

Read and write systems can scale independently.

Cons

Read data store is weakly consistent (eventual consistency)

The overall complexity of the system increases. Cargo culting CQRS can significantly jeopardize the complete project.

When to use CQRS

In highly scalable Microservice Architecture where event sourcing is used.

In a complex domain model where reading data needs query into multiple Data Store.

In systems where read and write operations have a different load.

When not to use CQRS

In Microservice Architecture, where the volume of events is insignificant, taking the Event Store snapshot to compute the Entity state is a better choice.

In systems where read and write operations have a similar load.

Enabling Technology Examples

Write Store: EventStoreDB, Apache Kafka, Confluent Cloud, AWS Kinesis, Azure Event Hub, GCP Pub/Sub, Azure Cosmos DB, MongoDB, Cassandra. Amazon DynamoDB

Read Store: Elastic Search, Solr, Cloud Spanner, Amazon Aurora, Azure Cosmos DB, Neo4j

Frameworks: Lagom, Akka, Spring, akkatecture, Axon, Eventuate

Further Reading

Saga

If you use Microservice Architecture with Database per Microservice, then managing consistency via distributed transactions is challenging. You cannot use the traditional Two-phase commit protocol as it either does not scale (SQL Databases) or is not supported (many NoSQL Databases).

You can use the Saga pattern for distributed transactions in Microservice Architecture. Saga is an old pattern developed in 1987 as a conceptual alternative for long-running database transactions in SQL databases. But a modern variation of this pattern works amazingly for the distributed transaction as well. Saga pattern is a local transaction sequence where each transaction updates data in the Data Store within a single Microservice and publishes an Event or Message. The first transaction in a saga is initiated by an external request (Event or Action). Once the local transaction is complete (data is stored in Data Store, and message or event is published), the published message/event triggers the next local transaction in the Saga.

Saga by Md Kamaruzzaman

If the local transaction fails, Saga executes a series of compensating transactions that undo the preceding local transactions' changes.

There are mainly two variations of Saga transactions co-ordinations:

Choreography: Decentralised co-ordinations where each Microservice produces and listen to other Microservice’s events/messages and decides if an action should be taken or not.

Orchestration: Centralised co-ordinations where an Orchestrator tells the participating Microservices which local transaction needs to be executed.

Pros

Provide consistency via transactions in a highly scalable or loosely coupled, event-driven Microservice Architecture.

Provide consistency via transactions in Microservice Architecture where NoSQL databases without 2PC support are used.

Cons

Need to handle transient failures and should provide idempotency.

Hard to debug, and the complexity grows as the number of Microservices increase.

When to use Saga

In highly scalable, loosely coupled Microservice Architecture where event sourcing is used.

In systems where distributed NoSQL databases are used.

When not to use Saga

Lowly scalable transactional systems with SQL Databases.

In systems where cyclic dependency exists among services.

Enabling Technology Examples

Axon, Eventuate, Narayana

Further Reading

Backends for Frontends (BFF)

In modern business application developments and especially in Microservice Architecture, the Frontend and the Backend applications are decoupled and separate Services. They are connected via API or GraphQL. If the application also has a Mobile App client, then using the same backend Microservice for both the Web and the Mobile client becomes problematic. The Mobile client's API requirements are usually different from Web client as they have different screen size, display, performance, energy source, and network bandwidth.

Backends for Frontends pattern could be used in scenarios where each UI gets a separate backend customized for the specific UI. It also provides other advantages, like acting as a Facade for downstream Microservices, thus reducing the chatty communication between the UI and downstream Microservices. Also, in a highly secured scenario where downstream Microservices are deployed in a DMZ network, the BFF’s are used to provide higher security.

Backends for Frontends by Md Kamaruzzaman

Pros

Separation of Concern between the BFF’s. We can optimize them for a specific UI.

Provide higher security.

Provide less chatty communication between the UI’s and downstream Microservices.

Cons

Code duplication among BFF’s.

The proliferation of BFF’s in case many other UI’s are used (e.g., Smart TV, Web, Mobile, Desktop).

Need careful design and implementation as BFF’s should not contain any business logic and should only contain client-specific logic and behavior.

When to use Backends for Frontends

If the application has multiple UIs with different API requirements.

If an extra layer is needed between the UI and Downstream Microservices for Security reasons.

If Micro-frontends are used in UI development.

When not to use Backends for Frontends

If the application has multiple UI, but they consume the same API.

If Core Microservices are not deployed in DMZ.

Enabling Technology Examples

Any Backend frameworks (Node.js, Spring, Django, Laravel, Flask, Play, …..) supports it.

Further Reading

API Gateway

In Microservice Architecture, the UI usually connects with multiple Microservices. If the Microservices are finely grained (FaaS), the Client may need to connect with lots of Microservices, which becomes chatty and challenging. Also, the services, including their APIs, can evolve. Large enterprises will like to have other cross-cutting concerns (SSL termination, authentication, authorization, throttling, logging, etc.).

One possible way to solve these issues is to use API Gateway. API Gateway sits between the Client APP and the Backend Microservices and acts as a facade. It can work as a reverse proxy, routing the Client request to the appropriate Backend Microservice. It can also support the client request's fanning-out to multiple Microservices and then return the aggregated responses to the Client. It additionally supports essential cross-cutting concerns.

API Gateway by Md Kamaruzzaman

Pros

Offer loose coupling between Frontend and backend Microservices.

Reduce the number of round trip calls between Client and Microservices.

High security via SSL termination, Authentication, and Authorization.

Centrally managed cross-cutting concerns, e.g., Logging and Monitoring, Throttling, Load balancing.

Cons

Can lead to a single point of failure in Microservice Architecture.

Increased latency due to the extra network call.

If it is not scaled, they can easily become the bottleneck to the whole Enterprise.

Additional maintenance and development cost.

When to use API Gateway

In complex Microservice Architecture, it is almost mandatory.

In large Corporations, API Gateway is compulsory to centralize security and cross-cutting concerns.

When not to use API Gateway

In private projects or small companies where security and central management is not the highest priority.

If the number of Microservices is fairly small.

Enabling Technology Examples

Amazon API Gateway, Azure API Management, Apigee, Kong, WSO2 API Manager

Further Reading

Strangler

If we want to use Microservice Architecture in a brownfield project, we need to migrate legacy or existing Monolithic applications to Microservices. Moving an existing, large, in-production Monolithic applications to Microservices is quite challenging as it may disrupt the application’s availability.

One solution is to use the Strangler pattern. Strangler pattern means incrementally migrating a Monolithic application to Microservice Architecture by gradually replacing specific functionality with new Microservices. Also, new functionalities are only added in Microservices, bypassing the legacy Monolithic application. A Facade (API Gateway) is then configured to route the requests between the legacy Monolith and Microservices. Once the functionality is migrated from the Monolith to Microservices, the Facade then intercepts the client request and route to the new Microservices. Once all the legacy monolith's functionalities are migrated, the legacy Monolithic application is “strangled,” i.e., decommissioned.

Strangler by Md Kamaruzzaman

Pros

Safe migration of Monolithic application to Microservices.

The migration and new functionality development can go in parallel.

The migration process can have its own pace.

Cons

Sharing Data Store between the existing Monolith and new Microservices becomes challenging.

Adding a Facade (API Gateway) will increase the system latency.

End-to-end testing becomes difficult.

When to use Strangler

Incremental migration of a large Backend Monolithic application to Microservices.

When not to use Strangler

If the Backend Monolith is small, then wholesale replacement is a better option.

If the client request to the legacy Monolithic application cannot be intercepted.

Enabling Technology

Backend application frameworks with API Gateway.

Further Reading

Circuit Breaker

In Microservice Architecture, where the Microservices communicates Synchronously, a Microservice usually calls other services to fulfill business requirements. Call to another service can fail due to transient faults (slow network connection, timeouts, or temporal unavailability). In such cases, retrying calls can solve fix the issue. However, if there is a severe issue (complete failure of the Microservice), then the Microservice is unavailable for a longer time. Retrying is pointless and wastes precious resources (thread is blocked, waste of CPU cycles) in such scenarios. Also, the failure of one service might lead to cascading failures throughout the application. In such scenarios, fail immediately is a better approach.

The Circuit Breaker pattern can come to the rescue for such use cases. A Microservice should request another Microservice via a proxy that works similarly to an Electrical Circuit Breaker. The proxy should count the number of recent failures that have occurred and use it to decide whether to allow the operation to proceed or simply return an exception immediately.

Circuit Breaker by Md Kamaruzzaman

The Circuit Breaker can have the following three states:

Closed: The Circuit Breaker routs requests to the Microservice and counts the number of failures in a given period of time. If the number of failures in a certain period of time exceeds a threshold, it then trips and goes to Open State.

Open: Request from the Microservice fails immediately, and an exception is returned. After a timeout, the Circuit Breaker goes to a Half-Open state.

Half-Open: Only a limited number of requests from the Microservice are allowed to pass through and invoke the operation. If these requests are successful, the circuit breaker goes to a Closed state. If any request fails, the Circuit Breaker goes to Open state.

Pros

Improve the fault-tolerance and resilience of the Microservice Architecture.

Stops the cascading of failure to other Microservices.

Cons

Need sophisticated Exception handling.

Logging and Monitoring.

Should support manual reset.

When to use Circuit Breaker

In tightly coupled Microservice Architecture where Microservices communicates Synchronously.

If one Microservice has a dependency on multiple other Microservices.

When not to use Circuit Breaker

Loosely coupled, event-driven Microservice Architecture.

If a Microservice has no dependency on other Microservices.

Enabling Technology

API Gateway, Service Mesh, various Circuit Breaker Libraries (Hystrix, Reselience4J, Polly.

Further Reading

Externalized Configuration

Every business Application has many configuration parameters for various Infrastructure (e.g., Database, Network, connected Service addresses, credentials, certificate path). Also, in an enterprise environment, the application is usually deployed in various runtimes (Local, Dev, Prod). One way to achieve this is via the Internal Configuration, which is a fatal bad practice. It can lead to severe security risk as production credentials can easily be compromised. Also, any change in configuration parameter needs to rebuild the Application. This is even more critical in Microservice Architecture as we potentially have hundreds of services.

The better approach is to externalize all the Configurations. As a result, the build process is separated from the runtime environment. Also, it minimizes the security risk as the Production configuration file is only used during runtime or via environment variables.

Pros

Production configurations are not part of the Codebase and thus minimize security vulnerability.

Configuration parameters can be changed without a new build.

Cons

We need to choose a framework that supports the Externalized Configuration.

When to use Externalized Configuration

Any serious production application must use Externalized Configuration.

When not to use Externalized Configuration

In proof of concept development.

Enabling Technology

Almost all enterprise-grade, modern frameworks support Externalized Configuration.

Further Reading

Consumer-Driven Contract Testing

In Microservice Architecture, there are many Microservices often developed by separate teams. These microservices work together to fulfill a business requirement (e.g., customer request) and communicate with each other Synchronously or Asynchronously. Integration testing of a Consumer Microservice is challenging. Usually, TestDouble is used in such scenarios for a faster and cheaper test run. But TestDouble often does not represent the real Provider Microservice. Also, if the Provider Microservice changes its API or Message, then TestDouble fails to acknowledge that. The other option is to make end-to-end testing. While end-to-end testing is mandatory before production, it is brittle, slow, expensive, and is no replacement for Integration testing (Test Pyramid).

Consumer-Driven contract testing can help us in this regard. Here, the Consumer Microservice owner team write a test suite containing its Request and expected Response (for Synchronous communication) or expected messages (for Asynchronous communication) for a particular Provider Microservice. These test suites are called explicit Contracts. For a Provider Microservice, all the Contract test suites of its Consumers are added in its automated test. When the automated test for a particular Provider Microservice is performed, it runs its own tests and the Contracts and verifies the Contract. In such a way, the contract test can help maintain the integrity of the Microservice Communication in an automated way.

Pros

If the Provider changes the API or Message unexpectedly, it is found autonomously in a short time.

Less surprise and more robustness, especially an enterprise application containing lots of Microservices.

Improved team autonomy.

Cons

Need extra work to develop and integrate Contract tests in Provider Microservice as they may use completely different test tools.

If the Contract test does not match real Service consumption, it may lead to production failure.

When to use Consumer-Driven Contract Testing

In large-scale enterprise business applications, where typically, different teams develop different services.

When not to use Consumer-Driven Contract Testing

Relative simpler, smaller applications where one team develops all the Microservices.

If the Provider Microservices are relatively stable and not under active development.

Enabling Technology

Pact, Postman, Spring Cloud Contract

Further Reading