In this section, we will discuss two microservice patterns, called communication styles and decomposition, with a sufficient level of detail that you will be able to discuss them with other developers, architects, and DevOps.
Communication styles and decomposition microservice patterns
Communication styles
Microservice applications are distributed by nature, so they heavily rely on the authorizations network. This makes it important to understand the different communications styles available. These can be to communicate with each other but also with the outside world. Here are some examples:
- Remote procedure calls: It used to be popular for Java to use Remote Method Invocation (RMI), which is a tight coupling between client and servers with a non-standard protocol, which is one limitation. In addition, the network is not reliable and so traditional RMIs should be avoided. Others, such as the SOAP interface, and a client generated from the Web Service Definition Language (WSDL), are better but are seen as heavy weight, compared to REpresentational State Transfer (REST) APIs that have widely been adopted in microservices.
- Synchronous communication: It is simpler to understand and implement; you make a request and get a response. However, while waiting for the response, you may also be blocking a connection slot and resources, limiting calls from other services:
- Asynchronous communication: With asynchronous communication, you make the request and then get the response later and sometimes out of order. These can be implemented using callbacks, async/await, or promise in Node.js or Python. However, there are many design considerations in using async, especially if there are failures that need monitoring. Unlike most synchronous calls, these are non-blocking:
When dealing with communications, you also need to think about whether your call is blocking or non-blocking. For example, writing metrics from web clients to a NoSQL database using blocking calls could slow down your website.
You need to think about dealing with receiving too many requests and throttling them to not overwhelm your service, and look at failures such as retires, delays, and errors.
When using Lambda functions, you benefit from AWS-built event source and spinning up a Lambda per request or with a micro-batch of data. In most cases, synchronous code is sufficient even at scale, but it's important to understand the architecture and communication between services when designing a system, as it is limited by bandwidth, and network connections can fail.
One-to-one communication microservice patterns
At an individual microservice level, the data management pattern is composed of a suite of small services, with its own local data store, communicating with a REST API or via publish/subscribe:
API Gateway is a single entry point for all clients, and tailored for them, allowing changes to be decoupled from the main microservice API, which is especially useful for external-facing services.
One-to-one request/response can be sync or async. If they are sync, they can have a response for each request. If the communication is async, they can have an async response or async notification. Async is generally preferred and much more scalable, as it does not hold an open connection (non-blocking), and makes better use of the central processing unit (CPU) and input/output (I/O) operations.
We will go into further detail on the data-management patterns later in the book, where we will be looking at how microservices integrate in a wider ecosystem.
Many-to-many communication microservice patterns
For many-to-many communication, we use publish/subscribe, which is a messaging pattern. This is where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers; rather, the receiver needs to subscribe to the messages. It's a highly scalable pattern as the two are decoupled:
Asynchronous messaging allows a service to consume and act upon the events, and is a very scalable pattern as you have decoupled two services: the publisher and the subscriber.
Decomposition pattern by business capability
How do you create and design microservices? If you are migrating existing systems, you might look at decomposing a monolith or application into microservices. Even for new a green-field project, you will want to think about the microservices that are required:
First, you identify the business capability, that is, what an organization does in order to generate value, rather than how. That is, you need to analyze purpose, structure, and business processes. Once you identify the business capabilities, you define a service for each capability or capability group. You then need to add more details to understand what the service does by defining the available methods or operations. Finally, you need to architect how the services will communicate.
The benefit of this approach is that it is relatively stable as it is linked to what your business offers. In addition, it is linked to processes and stature.
The drawbacks are that the data can span multiple services, it might not be optimum communication or shared code, and needs a centralized enterprise-language model.
Decomposition pattern by bounded context
There are three steps to apply the decomposition pattern by bounded context: first, identify the domain, which is what an organization does. Then identify the subdomain, which is to split intertwined models into logically-separated subdomains according to their actual functionality. Finally, find the bounded context to mark off where the meaning of every term used by the domain model is well understood. Bounded context does not necessarily fall within only a single subdomain. The three steps are as follows:
The benefits of this pattern are as follows:
- Use of Ubiquitous Language where you work with domain experts, which helps with wider communication.
- Teams own, deploy, and maintain services, giving them flexibility and a deeper understanding within their bounded context. This is good because services within it are most likely to talk to each other.
- The domain is understood by the team with a representative domain expert. There is an interface that abstracts away of a lot of the implementation details for other teams.
There are a few drawbacks as well:
- It needs domain expertise.
- It is iterative and needs to be continuous integration (CI) to be in place.
- Overly complex for a simple domain, dependent on Ubiquitous Language and domain expert.
- If a polyglot approach was used, it's possible no one knows the tech stack any more. Luckily, microservices should be smaller and simpler, so these can be rewritten.
More details can be found in the following books:
- Building-microservices, Sam Newman (2015)
- Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans (2003)
- Implementing Domain-Driven Design, Vaughn Vernon (2013)