Over view: Explains important terms used and patterns in micro service based system. Couple of patterns like CQRS or BFF quite popular. Even event based couple of well suited for few systems.
Article gives good over view of different patterns. ***
All of them used the age-old and proven technique to tackle the complexity of a large system: divide and conquer. Since the 2010s, those techniques proved insufficient to tackle the complexities of Web-Scale applications or modern large-scale Enterprise applications. As a result, Architects and Engineers developed a new approach to tackle the complexity of Software Systems in modern times: Microservice Architecture. It also uses the same old “Divide and Conquer” technique, albeit in a novel way.
Software Design Patterns are general, reusable solutions to the commonly occurring problem in Software Design. Design Patterns help us share a common vocabulary and use a battle-tested solution instead of reinventing the wheel. In a previous article: Effective Microservices: 10 Best Practices, I have described a set of best practices to develop Effective Microservices. Here, I will describe a set of Design Patterns to help you implement those best practices. If you are new to Microservice Architecture, then no worries, I will introduce you to Microservice Architecture.
By reading this article, you will learn:
- Microservice Architecture
- Advantages of Microservice Architecture
- Disadvantages of Microservice Architecture
- When to use Microservice Architecture
- The Most important Microservice Architecture Design Patterns, including their advantages, disadvantages, use cases, Context, Tech Stack example, and useful resources.
Please note that most of the Design Patterns of this listing have several contexts and can be used in non-Microservice Architecture. But I will describe them in the context of Microservice Architecture.
Microservice Architecture
I have covered Microservice Architecture in details in my previous Blog Posts: Microservice Architecture: A brief overview and why you should use it in your next project and Is Modular Monolithic Software Architecture Really Dead?. If you are interested, then you can read them to have a deeper look.
What is a Microservice Architecture. There are many definitions of Microservice Architecture. Here is my definition:
Microservice Architecture is about splitting a large, complex systems vertically (per functional or business requirements) into smaller sub-systems which are processes (hence independently deployable) and these sub-systems communicates with each other via lightweight, language-agnostic network calls either synchronous (e.g. REST, gRPC) or asynchronous (via Messaging) way.
Here is the Component View of a Business Web Application with Microservice Architecture:
Important Characteristics of Microservice Architecture:
- The whole application is split into separate processes where each process can contain multiple internal modules.
- Contrary to Modular Monoliths or SOA, a Microservice application is split vertically (according to business capability or domains)
- The Microservice boundary is external. As a result, Microservices communicates with each other via network calls (RPC or message).
- As Microservices are independent processes, they can be deployed independently.
- They communicate in a lightweight way and don’t need any smart Communication channel.
Advantages of Microservice Architecture:
- Better development scaling.
- Higher development velocity.
- Supports iterative or incremental modernization.
- Take advantage of the modern Software Development Ecosystem (Cloud, Containers, DevOps, Serverless).
- Supports horizontal scaling and granular scaling.
- It puts low cognitive complexity on the developer’s head thanks to its smaller size.
Disadvantages of Microservice Architecture:
- A higher number of Moving parts (Services, Databases, Processes, Containers, Frameworks).
- Complexity moves from Code to the Infrastructure.
- The proliferation of RPC calls and network traffic.
- Managing the security of the complete system is challenging.
- Designing the entire system is harder.
- Introduce complexities of Distributed Systems.
When to use Microservice Architecture:
- Web-Scale Application development.
- Enterprise Application development when multiple teams work on the application.
- Long-term gain is preferred over short-term gain.
- The team has Software Architects or Senior Engineers capable of designing Microservice Architecture.
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.
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.
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)
- 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:
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.
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.
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
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.
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.
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
No comments:
Post a Comment