Motivation to use microservices
In order to understand the motivation behind using the microservice architecture, it is very important to see the opposite approach – when the application is built and executed as a single program. Such applications are called monolithic applications or monoliths.
Monolithic architecture is, in most ways, the simplest model to implement since it does not involve splitting the application into multiple parts that need to coordinate with each other. This can provide you with major advantages in many cases, such as the following:
- Small code base: Splitting an application into multiple independent parts may significantly increase the size of the code base by introducing extra logic required for communication between the components.
- Application logic is still loosely defined: It is very common that parts of the application or the entire system go through major structural or logical changes, especially at the very early stages of development. This might be caused by a sudden change of requirements, priorities, changes in the business model, or a different approach to development. During the early stages of development, iterating fast can be critical not only to the development process, but also to the entire company.
- Narrow scope of the application: Not every service requires a decomposition and division into separate parts. Consider a service for generating random passwords – it has a single logical feature and, in most cases, it would be unnecessary to split it into multiple parts.
In all of the preceding cases, monolithic architecture would be a better fit for the application. However, at some point, services get too big to remain monolithic. Developers start experiencing the following issues:
- Large application size and slow deployments: At a certain point, an application can become so big that it can take minutes or even hours to build, start, or deploy.
- Inability to deploy a particular part of the application independently: Not being able to replace a part of a large application can easily become a bottleneck, slowing down the development and release process.
- Higher blast radius: If there is a bug in a certain function or library widely used across the application code, it is going to affect all parts of the system at once, potentially causing major issues.
- Vertical scalability bottleneck: The more logic the application has, the more resources it needs in order to run. At a certain point, it can get hard or impossible to scale the application up even further, given the possible limits on CPU and RAM.
- Interference: Certain parts of the application can heavily load CPU, I/O, or RAM, causing delays for the rest of the system.
- Unwanted dependencies between components: Having the entire application represented as a single executable leaves room for unnecessary dependencies between the components. Imagine a developer refactoring a code base, and making a change suddenly affects some important parts of the system, such as payments. Having more isolation between the components gives more protection against such issues.
- Security: A possible security issue in the application may result in unauthorized access to all components at once.
In addition to the possible issues we just described, different components may have different requirements, such as the following:
- Resources and hardware requirements: Certain components are more CPU-intensive or memory-intensive and may perform I/O operations at a higher rate. Separating such components may reduce the load on the entire system, increasing system availability and reducing latency.
- Deployment cadence: Some parts of the system mostly remain unchanged while others require multiple deployments per day.
- Deployment monitoring and automated testing: Certain components may require stricter checks and monitoring and can be subject to slower deployments due to multi-step rollouts.
- Technologies or programming languages: It is not uncommon that different parts of the system can be written in different programming languages or use fundamentally different technologies, libraries, and frameworks.
- Independent APIs: Components may provide fully independent APIs.
- Code review process: Some components may be subject to a stricter code review process and additional requirements.
- Security: Components may have different security requirements and may require additional isolation from the rest of the application for security reasons.
- Compliance: Some parts of the system may be subject to stricter compliance requirements. For example, handling personally identifiable information (PII) for users from a certain region can put stricter requirements on the entire system. Logical separation of such components helps to reduce the scope of work required to keep the system compliant.
With all the preceding issues described, we can see that at a certain point monolithic applications can become too big for a one-size-fits-all model. As the application grows, certain parts of it may start becoming independent and have different requirements, benefiting from a logical separation from the rest of the application.
In the next section, we are going to see how splitting the application into microservices can solve the aforementioned problems and which aspects of it you should be careful with.