Microservices
It is common to expose every service in an application through a JSON REST interface. That is not necessarily the wrong choice, but picking the right tool for each job leaves the application simpler, more efficient, more secure, and easier to monitor and maintain — a service whose job is to notify others of something that happened is usually better served by a message than by a synchronous call.
Designing a microservice architecture means setting aside some principles architects followed for a long time and applying others in their place. The patterns below relate to the size, lifecycle and behaviour of microservices and to the relations between them.
Domain-driven design and bounded contexts
Section titled “Domain-driven design and bounded contexts”Microservice-based applications can be designed using Domain-Driven Design, an approach that requires a close understanding of the business domain. A domain can have multiple bounded contexts, each of which encapsulates the details of one business domain and defines the integration points with the others.
In an e-commerce application, order, delivery and billing are all separate bounded contexts. Each context maintains a data model derived from the bounded domain model, and each translates into one or more microservices.
Advantages of the bounded domain model:
- Changes in the domain model affect only a limited number of services.
- Services are autonomous.
Disadvantages:
- Good domain knowledge is required to define a bounded context correctly.
- Keeping the system consistent between contexts adds complexity.
Bounded Context and Aggregates cover how those boundaries are found.
Stateless services
Section titled “Stateless services”Microservices are stateless and keep their state outside the application, in databases or data grids. That is what lets an instance be replaced, scaled out or deployed independently of the other services: any instance can serve any request, so adding and removing instances is a capacity decision rather than a data-migration exercise.
Design for failure
Section titled “Design for failure”Distributed applications fail through code errors, hardware faults and network faults. Applications have to anticipate those failures and recover from them rather than assume they will not happen.
Several patterns make a microservice application fault-tolerant:
- Run services highly available, so the loss of one instance is absorbed rather than visible.
- Apply the circuit breaker pattern so a failing service or network path does not cascade into the services calling it.
- Apply the fallback pattern so a failed call degrades the response instead of failing the request.
Asynchronous communication
Section titled “Asynchronous communication”Microservices can communicate asynchronously using message-based protocols such as AMQP or MQTT, or by applying message-based patterns — point-to-point, publish-and-subscribe, request-and-reply, request-and-notification. Depending on the protocol or pattern, an intermediary such as a broker is responsible for delivering messages to the correct destination.
Further reading
Section titled “Further reading”- Understanding microservices — Red Hat’s overview of the architectural style
- Pattern: Messaging — asynchronous communication between services, and when it beats a synchronous API