To begin to understand microservices architecture, it helps to consider its opposite: the monolithic architectural style. Unlike microservices, a monolith application is always built as a single, autonomous unit. In a client-server model, the server-side application is a monolith that handles the HTTP requests, executes logic, and retrieves/updates the data in the underlying database. The problem with a monolithic architecture, though, is that all change cycles usually end up being tied to one another. A modification made to a small section of an application might require building and deploying an entirely new version. If you need to scale specific functions of an application, you may have to scale the entire application instead of just the desired components. This is where microservices can come to the rescue.
Microservices have been described in a number of ways, and performing a search on the internet provides many viewpoints and definitions. While there is no precise definition of this architectural style there are common characteristics and functions:
- They are developed by a small development teams.
- They are programming language agnostic.
- They encapsulate a customer or business scenario.
- They consist of code and (optionally) state that are independently versioned, deployed, and scaled.
- Owns its own data.
- They interact with other microservices over well-defined interfaces and protocols.
- They have unique names (URLs) that can be used to resolve their location.
- They remain consistent and available in the presence of failures.
This description can be summarised as:
Microservice applications are composed of small, independently versioned, and scalable customer-focused services that communicate with each other over standard protocols with well-defined interfaces.
Programming Language Agnostic
Each Microservice is free to be coded in any language the development team is most comfortable with or perhaps based on the needs of the service. In some cases you may need to use a specific third-party library, data storage technology, or means of exposing the service to clients.
Allows code and state to be independently versions, deployed, and scaled.
Each Microservice should be standalone, it should be possible to independently deploy, upgrade, and scale for both the code and (optionally) the state. That makes your service easy to test in integration terms and reduces the complexity of the entire system.
Some services will require the use of other services, and there are patterns around shared facilities, like auditing, load balancing, transactional consistency, monitoring and so forth that your service may be able to make assumptions on.
The value of this in production is that the size of a change can be very small, and deploying lots of small changes continuously has significantly less risk than large complex changes.
Interacts with other microservices over well-defined interfaces and protocols.
Microservices are loosely coupled vertical stacks. Each microservice should communicate with other services via well-known interfaces. Generally, this now comes down to using a REST approach with HTTP and TCP protocols, and XML or JSON as the serialization format.
From an interface perspective, it is about embracing the web design approach. But there is nothing stopping you from using binary protocols or your own data formats.
Owns its own dataThe ability to own your own data model is the key to microservices in my mind. Integrating or communicating at the database is always a bad idea. When you don’t own your data, you are unable to freely modify the data store or data layout, and you require significant coordination between systems to make alterations. Furthermore, the primary issue here, is around where multiple systems use the database as a point of coordination or communication. That’s what tends to cause the biggest problem.
Has a unique name (URL) that can be used to resolve its location.
Like the web, your microservice needs to be addressable wherever it is running. Those names must be propagated throughout the system. That may be a single node on a developers desktop or ten thousand nodes spread through-out data centres in cities around the world.
If you are thinking about machines and which one is running a particular microservice, then this is the wrong mindset. In the same way that DNS resolves a particular URL to a particular machine, your microservice needs to have a unique name to discover where it currently is.
Remains consistent and available in the presence of failures.
Dealing with unexpected failures is one of the hardest problems to solve, especially in a distributed system. Much of the code that we write is handling exceptional conditions, and this is also where the most time is spent in testing. Without fault tolerance, failure in one service could propagate and kill the whole application. Not only do you need to detect this microservice failure, but you also need something to restart your microservice, perhaps on another machine for availability reasons.
It is essential that a microservice reports its health, otherwise there is little insight from an operations perspective. The challenge comes in seeing the correlation of diagnostic events across a set of independent services, with each one logging independently and dealing with machine clock skews to make sense of the event order. Knowing the health of an individual service is useful, but often only when trying to diagnose a wider problem or understanding a larger trend.