Microservices is a distinctive architectural style that has become popular in recent years as businesses aim to become more agile. The Microservices approach breaks development down into manageable segments, improving efficiency and reducing the time required for new deployments. Microservices architecture is trendy for greenfield development projects, but it can also be applied to traditional applications post-launch.
What are Microservices?
Microservices is an architectural style that breaks an application down into a suite of smaller services, each running in its own process. The number of microservices needed depends on the scale of the application. For example, a large application might be made up of thousands of microservices.
Each microservice is designed to fulfill a specific (business) function. For example, a social networking application could have a service for posting updates, a service for searching, a service for uploading images, and so on.
Microservices communicate using a lightweight mechanism such as an HTTP resource API. In addition, these communications are stateless, meaning they contain all of the information needed to fulfill a request and never rely on information passed by a previous communication or during a previous session.
Why Use Microservices?
A traditional “monolithic” application houses code in a single location for all of the services needed to fulfill its functions.
To start with, that usually works well. But what happens if a specific service needs to be scaled to meet demand? Generally, a new instance of the entire server-side component will need to be created, and work will need to be done across the codebase to ensure the newly-scaled service fits into the wider application ecosystem. This is hardly a good use of resources.
Alternatively, if an application grows over time, independent teams of programmers will likely be responsible for working on different functions. Even with the best of intentions, it’s difficult for these teams to work on the same codebase at the same time while maintaining strong communication. Even if perfect communication is possible, a lot of time and agility are inevitably lost.
These are the problems microservices architecture is designed to solve. In addition, because each service has its own codebase, microservices can be modified or scaled individually by independent teams without causing wider compatibility issues or requiring heavy inter-team communication.
Other advantages of microservices include:
- Ability to use different programming languages for different services (a.k.a. polyglot programming)
- Much simpler fault isolation
- Data is federated so that each service can adopt the most appropriate data model.
- Enables agile, parallel development by separate programming teams
- There are no cross-dependencies between codebases
How Big (or Small) Should a Microservice Be?
Defining the size of Microservices can be tricky.
Two particularly bad practices for determining service size (which we see a lot) are:
- Based on the number of lines of code
- Based on what can be built or rebuilt in a single Sprint
Both of these approaches fail because they fundamentally miss the point of using a microservices architecture: To split an application into its individual functions to speed development and improve agility.
In our experience, one of the best ways to determine service size is to use a concept from Domain-Driven Design (DDD) called Bounded Contexts. A Bounded Context sets the boundaries of subdomains within a complex domain based on business needs. Each bounded context defines the language that will be valid within a subdomain, where the boundary of that subdomain lies, and how it will integrate with other subdomains.
Bounded Contexts contain all of the domain logic needed to fulfill a specific need or function, making them an excellent means of determining the appropriate size for individual services within a microservices architecture.
This arrangement closely resembles the Unix approach: Do one thing and do it well.
How do Microservices Communicate?
There are several different ways for microservices to communicate. Two important methods are:
- Synchronous HTTP communication
- Asynchronous Message-Driven communication
Typically, implementation starts with synchronous HTTP REST-like communication between services. There are plenty of libraries supporting this method of communication, and it’s straightforward to bootstrap this approach.
However, a synchronous call will force the calling side to wait until the execution on the receiver side is done, leading to unnecessary blocking. Further, the calling service must “know” the receiving service exists to make a call, which leads to tight coupling. Therefore, HTTP communication is commonly used in small, simple systems and in instances where the caller needs to instantaneously find out the system's state (i.e., by performing a GET call).
To achieve better location transparency and looser coupling, an alternative approach is asynchronous Message-Driven communication between services. This approach requires an additional component – a Message Broker – which increases the system’s complexity. In large-scale systems, message-driven communication is usually chosen over synchronous HTTP communication.
The Evolutionary Approach
Applying a microservices architectural style from the outset of a project can be difficult. Why? Because the process of decoupling a domain into smaller subdomains – in other words, figuring out where to draw the boundaries between different microservices – is often difficult to do before an application is up and running.
For this reason, it’s usually easier to start by building a monolithic application and letting the individual microservices reveal themselves over time. We call this the evolutionary approach.
Axon Framework supports this evolutionary approach by making it easy to develop a “structured” monolith — Something that resembles a traditional monolithic application but has a clearly defined structure. This structure is what will be used later on to determine the boundaries between individual microservices.
If a service needs to be scaled or updated, Axon Framework makes it possible to decouple the subdomains in different services. In addition, Axon Framework is message-driven, which enables loose coupling between subdomains and location transparency. And when you have location transparency — which literally means components are not “aware” of the location of other components — you can safely change the location of components without worrying they will no longer be able to communicate.
Taking things a stage further, Axon Server provides an easy infrastructure for loose coupled message-driven communication between microservices. This combination drastically simplifies the separation process and makes shifting to a microservices approach far more achievable.
To find out more about how AxonIQ aids the development of event-driven microservices, visit our product overview page.
Alternatively, continue learning about architectural concepts.
September 28th, Amsterdam
Join us for the AxonIQ Conference 2023, where the developer community attends to get inspired by curated talks and networking.
September 27th, Amsterdam
The event to collaborate, discuss, and share knowledge about techniques, tools, and practices for building complex, distributed event-driven applications.