It can be very intimidating designing a backend system. There are many different variations and so many buzz words such as:
- Service Orientated Architecture
- Event-Driven Architecture
As it turns out, the process of choosing an architecture is a lot simpler. You just gotta know the pros and cons of each one and pick the one that suits your project needs. But first, you need to have a solid understanding of each architectural pattern, so this is why in this post we cover one of the most popular architectural patterns, the microservice.
- Basics of software architecture
- Pros and cons of the monolith architecture
- Pros and cons of microservices
- Basics of design pattern
Before we move on to the gritty details of microservices, we first have to cover the basic terminology.
Architecture, in a nutshell, is the decisions that you wish you had made early on in the project.
Architecture is the skeleton of the project. It is the hard decisions you make early on that determine the foundation of the project. It's this foundation that greatly influences the quality of code, ease of development and deployment, maintainability, and evolvability.
Examples of architecture:
- Model-View-Controller (MVC)
You might be thinking, isn't this post about microservices, why is this idiot talking about monolith?
The reason is that to understand what problems does the microservice architecture fix, we gotta first understand the problems that the old traditional monolithic architecture give.
Okay, moving on.
The word monolith means "composed all in one place", in software terms it means having a single application, made up of different components, combined into a single program from a single platform.
Components may include:
- Authorization - responsible for authorizing the user
- Presentation - responsible for handling HTTP requests, and responding back with HTML or JSON/XML (if it's an API)
- Business logic - the business logic of the application
- Database layer - responsible for communicating with the database
- Application integration - responsible for integrating with other services (using REST API or messaging) or any other data source.
- Notification module - responsible for sending notifications.
For years monolithic applications have been industry default because it's simple. All code is in one place, it's simple to test, and deploy. But as application size grows, there are some major drawbacks that get introduced.
- Maintenance - as the codebase increases, it becomes harder to make changes.
- The large code base intimidates developers, especially new ones. Which will lead to decreased productivity.
- Overloaded IDE - the larger the code base, the slower the IDE and the less productive developers will be.
- Startup time - the larger the application, the more time it will take to launch the application, in turn will lead to decreased productivity.
- Deployment - in order to update one component, your gonna have to redeploy the whole application. This might interrupt background tasks, and possibly cause problems.
- Scaling - monolithic applications are hard to scale because different modules may have different resource requirements.
- Scaling to development teams - as an application gets bigger, it would be advisable to separate developers into teams per module. For example, a team for the payments module, and another team for the products module. In a monolithic architecture, everything is coupled together, making it hard for teams to work independently. This forces the teams to coordinate their development and deployments. In turn, making it difficult to make changes and update production.
- Tech stack - Monolithic architecture forces you to stick to one technology (sometimes even a specific version of that technology). It's very hard to adopt new technology due to the fact that changing the language or framework will affect the whole application.
The microservice architecture is an approach to software development, in which a large application is made up of a suite of modular services (i.e loosely coupled modules/components). A good analogy would be legos, where each set of blocks corresponds to a component. Additionally, a group of connected components would create a full-fledged application.
Each module/service has a single business goal and uses simple interfaces to communicate with other services. Another feature of services is that, unlike the monolith application where you share a single database, microservices follow the approach of database per service. This ensures loose coupling, and the service can use the database that best suits its needs.
- Enable the continuous development and deployment of large complex applications.
- Improves maintainability - each service is small, so it's much easier to make changes.
- Better testability - services are smaller and faster to test.
- Better deployability - each service can be deployed independently
- Enables teams to be independent - teams can get full responsibility for a single or set of services. Each team can develop, test, deploy, and scale their services independently of all other teams.
- Each microservice is relatively small:
- Less complexity, easier to understand
- Smaller code base, making the IDE fast.
- The application starts faster, which makes developers much more productive, and speeds up deployments.
- Improved fault isolation. If one service faces issues, then only that service will be affected. The other services will continue normally. Unlike monolithic architecture, one misbehaving component can bring down the entire system.
- Better evolvability - when developing a new service, you can use a new technology stack. Similarly, when making major changes to an existing service, you can rewrite the whole service in a new stack.
- Developers must deal with the additional complexity of creating a distributed system:
- Developers must implement the inter-service communication mechanism and deal with partial failure
- Implementing requests that span multiple services is more difficult.
- Testing the interactions between services is more difficult.
- Implementing requests that span multiple services requires careful coordination between the teams.
- Developer tools/IDEs are oriented toward building monolithic applications and don’t provide explicit support for developing distributed applications.
- Deployment complexity - in production, there is additional complexity in deploying and managing a system compromised of many different services.
- Increased memory consumption. The microservice architecture replaces N monolithic application instances with NxM services instances. If each service runs in its own JVM (or equivalent), which is usually necessary to isolate the instances, then there is the overhead of M times as many JVM runtimes. Moreover, if each service runs on its own VM (e.g. EC2 instance), as is the case at Netflix, the overhead is even higher.
You cannot use the microservice architecture alone, there are many different design patterns that solve the same problems in different ways, and it's your job as the developer to choose which one best fits your project.
An example may be:
How to decompose the application into services?
Here you can use different design patterns:
- Decompose by business capability and define services corresponding to business capabilities.
- Decompose by domain-driven design subdomain.
- Decompose by verb or use case and define services that are responsible for particular actions. e.g. a
Shipping Servicethat’s responsible for shipping complete orders.
- Decompose by nouns or resources by defining a service that is responsible for all operations on entities/resources of a given type.
Account Servicethat is responsible for managing user accounts.
Design patterns are typical solutions to commonly occurring problems in software design. They are like pre-made blueprints that you can customize to solve a recurring design problem in your code.
You can't just find and copy a pattern. Patterns do not provide any code but provide a general concept for solving a specific problem.
Microservices have lots of patterns, to make it easier, we have separated the patterns into three categories based on their relationships to one another.
- Predecessor – a predecessor pattern is a pattern that motivates the need for this pattern. For example, the Microservice Architecture pattern is the predecessor to the rest of the patterns in the pattern language except the monolithic architecture pattern.
- Successor – a pattern that solves an issue that is introduced by this pattern. For example, if you apply the Microservice Architecture pattern you must then apply numerous successor patterns including service discovery patterns and the Circuit Breaker pattern.
- Alternative – a pattern that provides an alternative solution to this pattern. For example, the Monolithic Architecture pattern and the Microservice Architecture pattern are alternative ways of architecting an application. You pick one or the other. These relationships provide valuable guidance when using a pattern language. Applying a pattern creates issues that you must then address by applying successor patterns. The selection of patterns continuously recursively until you reach patterns with no successor. If two or more patterns are alternatives then you must typically pick just one. In many ways, this is similar to traversing a graph.
Congratulations, you now know enough to go forth in your journey of being a software architect. The journey doesn't stop here, make sure to check out microservices.io for more details on microservices.