Many modern applications need to be built across the enterprise, sometimes even across the Internet. Each application must meet the requirements for scalability, availability, security, reliability, and resiliency.
In this article, I will introduce some design patterns that can easily help you achieve the above capabilities. I'll cover each pattern, how to use this pattern in the cloud, and when to use it and when not.
Some of these patterns are not all that new, but are very useful in today's Internet cloud world.
Here's a list of the patterns I'll be discussing in this article:
- Circuit Breaker
- Command and Query Responsibility Segregation (CQRS)
- Event Sourcing
- Sidecar
- Backend-for-Frontend
- Strangler
So let's get started.
1. Circuit Breaker
Distributed systems must be designed with failures in mind. There are microservices in the world nowadays, and these services mainly depend on other remote services. These remote services may not respond in time for various reasons such as network, downloading applications, etc. In most cases, the implementation of retries should be able to resolve problems.
But sometimes there can be serious problems such as degradation of services or complete failure of services by itself. It makes no sense to keep trying again in such cases. In this case, the circuit breaker model can be useful.
The diagram above shows an implementation of the circuit breaker circuit where when service 1 realizes that calling service 2 has continuous failures / timeouts, instead of retrying, service 1 disconnects calls to service 2 and returns a response in case of failure.
There are popular open source libraries like Netflix Hystrix
or Reselience4J that can be used to implement this pattern very easily.
If you are using API gateways or proxies
like Envoy , this can be achieved at the proxy level itself.
Note:It is very important that enough logs and warnings are implemented when the chain is open to keep track of requests received during this time and that the operations team is aware of this.
You can also implement a half-circuit breaker to continue serving customers with impaired service.
When to use this pattern
- When a service depends on another remote service, and in some scenarios, it is likely to fail.
- When the service has a very strong dependency (like master data services).
When not to use this pattern
- When you are dealing with local dependencies - a circuit breaker can create overhead.
2. Command and Query Responsibility Segregation (CQRS) ( (CQRS))
CQRS is a very useful model for modern data warehouse applications. It is based on the principle of separating read (query) and write / update (command) operations in the data store.
Let's say you're building an application that requires storing data in a database like MySQL / PostgreSQL, etc. As everyone knows, when writing data to the storage, an operation must go through several stages - for example, validation, modeling, and persistence - and therefore typical write / update operations take longer than simple read operations.
When you use a single datastore to perform read and write operations at the same time and at scale, you can start to see performance issues.
In such cases, the CQRS template can be useful. The CQRS pattern suggests using different data models for read and write operations. Some variations also suggest using separate data stores for these models.
Note: Most PaaS databases these days provide the ability to create readable replicas ( Google Cloud SQL , Azure SQL DB , Amazon RDS
, etc.) of data stores, which make it much easier to achieve data replication.
Many enterprise databases also provide this capability if you are dealing with local databases.
Note: Some people these days also choose to implement readable replicas as fast and performant NoSQL databases like MongoDB and Elasticsearch.
When to use this pattern
- When you are looking at scaling an application expecting a huge amount of reads and writes
- When you want to set up read and write operations separately.
- When your read operations are close to real or ultimately consistent.
When not to use this pattern
- When you are building a normal CRUD application that does not expect a huge amount of reads and writes in one go.
3. Event Sourcing
An event source is an interesting design pattern in which a sequence of domain events is stored as a log, and an aggregated log view gives the current state of the application.
This pattern is typically used for systems that cannot afford data storage locks and that need to maintain auditing and event history - for example, applications such as hotel / conference / seat reservation.
Given a hotel room reservation system where users are expected to book or cancel a reservation. Here you need to store your reservation and cancellation as a series of events. Available rooms are shown in a summary prior to each booking by reviewing the event logs.
Note:Most cloud service providers support messaging services such as Google Pub / Sub, Azure Service Bus, AWS SQS, and so on. These services, combined with strong consistent data stores, can be used to implement this scheme.
When to use this pattern
- When normal CRUD operations are not good enough to meet the requirements
- Typically suitable for seat reservation systems such as buses, trains, conferences, cinemas, etc. - or for e-commerce systems, which consist of activities such as shopping carts, payments, etc.
- When there is a need for strong auditing and event replay to create the current and past state of applications.
When not to use this pattern
- When normal CRUD operations are good enough to meet user needs.
4. Sidecar (Sidecar Design Pattern)
The Sidecar pattern became popular with the advent of microservices. In this scheme, the application component is deployed into a separate process or container. This helps to achieve abstraction and encapsulation.
Envoy Proxy is one of the most popular sidecar proxy servers and is widely used. It helps you decouple the main functionality of the application by using a side machine to isolate common functions like networking, observability, and security.
This type of sidecar can help the abstract L4 / L7 type of communication. Sidecars such as Envoy Proxies even help achieve higher security by implementing mutual TLS.
You can use this in combination with a service grid to achieve better connectivity and security between different microservices. You can read more about this in my previous article.
When to use this pattern
- When you are dealing with numerous and heterogeneous microservices in a product portfolio.
- When dealing with legacy applications that tend to fail to handle the interoperability and security challenges of the new era.
When not to use this pattern
- When you are dealing with a limited number of services that need to communicate with each other.
- Small applications where deploying side strollers may be uneconomical or inconvenient to operate
5. Backend-for-Front (BFF)
In a typical product development cycle, back-end engineers are responsible for creating services that interact with data warehouses, while front-end engineers take care of creating user interfaces. Apps these days must be built with both mobile and desktop in mind.
While the gap between mobile and desktop devices is getting closer in terms of hardware, connectivity and use is still a challenge for mobile devices.
In such scenarios, BFF templates become quite handy. Here, you are expected to build / configure internal services for a specific front-end.
To optimize the performance of mobile clients, you may want to build a separate back-end service that responds with lightweight and paginated responses.
You may also want to use this pattern to aggregate various services to reduce data communication between back-end and front-end.
Note: These days, if you are using an API gateway, the BFF pattern can be easily implemented in the gateway itself, and you will not need to serve separate services.
When to use this pattern
- When you want to provide a product / service for various clients such as desktop and mobile clients.
- When you want to optimize your response for a specific type of customer.
- When you want to cut down on chatting between mobile clients and different services.
- , .
- , .
6. Strangler ( )
If you work for an organization that is on the way to modernizing applications, then the Strangler Design Pattern is a must. The Strangler pattern advocates building a facade overlay on top of your legacy and new application, giving consumers the ability to look at things objectively.
This pattern separates clients from migration activity between old and new parts of the application.
Note: In a typical IT organization, if you are migrating from one ERP system to another, this type of schema will be extremely useful. If you are using the gateway API, it will be easier to implement this in the proxy gateway itself.
You need to decide if you want to keep the add-in (facade) at the end of the migration or remove it.
When to use this pattern
- When you are migrating or upgrading a complex, highly dependent application such as an ERP migration
When not to use this pattern
- When migration is easy and direct replacement is the best option.