A brief introduction to microservices
Microservices and the Domain Driven Approach
A microservice is a small piece of autonomous software, running its own process and communicating with lightweight mechanism like REST API (Newman, 2015). Microservices usually work together to build larger applications. Within a monolithic application, writing cohesive code can become challenging as the code base is growing over time and code referring to same functions is spread over the project. The microservice approach focuses on keeping the code base as small as possible to implement a business logic. This encourages cohesion and maintenance of the code within a small group of developers (Newman, 2015). Readability, correctness of the code and maintenance are increased as well. To ensure that the microservice is kept small, boundaries have to be defined to limit the implemented business logic.
Another characteristic of microservices is their autonomy. A microservice is deployed as a separated entity, usually running on its own operating system with its own computer resources. To realize this isolation, virtualization in form of containerization is required. All communication in between microservices, between microservices and an orchestration engine or between microservices and a database is implemented through network requests to enforce separation between services and avoid tight coupling (Newman, 2015). This is called loose coupling. Microservices use asynchronous communication. Common communication protocols are HTTP for REST API communications and AMQP for message queuing. Microservices composing an end-to-end application usually use REST APIs, event-driven communication or data-driven communication. Event-driven communication is mostly designed on top of message-driven communication. In event-driven communication the state of an entity, i.e. microservice, can change over time. This change in state causes an emission of an event notification. This notification is published by the microservice and detected and consumed by other services. The notification itself makes use of the message-driven technology. If a microservice is exposing a REST API, other services will communicate with this service by calling functions of that service via HTTP requests. This requires all other services to have a HTTP-client library implemented to directly talk to the microservice exposing the API. In other words, a microservice would need a client library for every microservice it wants to communicate with. In terms of autonomy and loose coupling this communication design can lead to problems. Changing a microservice API could lead to changes in other services’ client libraries and would require an update. This outlines the importance of carefully modelling the service’s API and keep using a defined standard over all services. This problem can be solved by microservice communication over a conductor engine (e.g. Netflix Conductor), which is providing an elegant design to overcome the HTTP API/ Http- client coupling, while still making use of easy to implement HTTP requests.
Providing autonomous microservices, running on separated machines, not only encourages loose coupling, but also allows technology heterogeneity (Newman, 2015). While monolithic applications are usually written in one programming language and making use of one framework, microservices run on isolated machines including all necessary dependencies. This allows the microservice to use the technology, which fits best to implement a certain business logic. With microservices, teams are able to adopt new technology quickly, while risk of failure is limited to the boundaries of the microservice (Newman, 2015). Although using the right tools to solve a problem can lead to more efficiency, the risk of using multiple technologies in multiple microservices is, that developers may not fully understand the used technologies. Also, it is hard to estimate reliability and performance of an upcoming new technology.
Fault Tolerance and Resilience
Another important benefit of separated services is fault tolerance and resilience (Newman, 2015). A crashed microservice will not affect any other service and failure will not cascade. The problem is isolated and can be located to one microservice. Automated failure recovery and restarting crashed services will reduce downtime and downtime costs. Although isolation will prevent the whole application to crash, service separation may also be the source of crashing. Separation leads to communication over a network. High network traffic or misconfiguration can be new sources for failure.
Scalability of single services can overcome high network traffic and boosts performance. In monolithic applications resource consuming tasks and functions will lead into scaling up the whole application’s computer resources. Within a distributed application system, only these services are scaled, which need high computing power. All other services can run on low powered machines or containers. This leads to higher efficiency in resource consumption, higher application performance and reduced downtimes caused by overload. However, scaling is not only possible on computer resource side. The number of identical services can be scaled as well. Therefore, the capacity utilization is automatically managed, and traffic is balanced across the identical services.
The small project size of microservices and the autonomy makes fast deployment easy. Changes in code will only affect the microservice itself. Monolithic applications struggle because of their size. If a bug occurs the whole application has to be redeployed, which leads to downtime. To reduce downtime long release cycles are common. Microservices, however, do not force downtime on redeployment as the services are mostly decoupled. Fast release cycles can be achieved, which paves the way to continuous delivery.
Reusability and Replacement
Reusability and replacement of microservices in distributed applications are the last important characteristics to mention. Microservices can be reused easily in other applications, as they do not heavily depend on the other application’s services. But microservices often communicate with databases or other data sources and process data in a given structure. To ensure the correct behavior the data should be aligned. In case a special functionality of the distributed application is not needed anymore or has to be rewritten, the microservice with this functionality can be replaced. This is an advantage especially in fast changing businesses or heavily regulated sectors.
Domain Driven Approach
All these characteristics and benefits rely on two main factors. The small size and the autonomy of microservices. As mentioned above it has to be ensured, that the microservices are kept small and clear boundaries have to be defined to split an application’s business logic into microservices. To tackle this challenge Eric Evan has formulated the Domain Driven Design. His approach provides a broad framework for making design decisions and a technical vocabulary for discussing domain design, called ubiquitous language (Evans, 2003). In the context of building applications, domain-driven design talks about the business logic as domains. As it is hard to define a single unified model for a large domain, domain-driven design introduces Bounded Contexts (Fowler, 2014). Bounded Contexts are independent problems of the domain, which have defined entities, value objects, boundaries and a defined interface for shared information. Each Bounded Context correlates to a microservice. To determine where to draw the boundaries of a Bounded Context two contradicting rules are defined. Keep the Bounded Context, i.e. the microservice, as small as possible and avoid communication between bounded contexts and encourage cohesion. It is to balance the microservices by decomposing the system into as many small microservices as possible until communication is growing with each additional attempt to separate a new Bounded Context. Cohesion is the main target within a single bounded context (Microsoft, 2018).
- Newman, S. (2015). Building Microservices - Design Fine- Grained Systems. Sebastopol, CA, USA: O’Reilly.
- Microsoft. (2018, 08 10). .NET Microservices: Architecture for Containerized .NET Applications. Retrieved January 2019, from Design a DDD-oriented microservice: https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/ddd-oriented-microservice
- Fowler, M. (2014, January 15). Martin Fowler - Bounded Context. Retrieved January 2019, from https://martinfowler.com/bliki/BoundedContext.html
- Evans, E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional.