Before we begin, I'd like to ask for a little bit of patience. I'm not a native English speaker, so, sorry for any errors in advance! I'll try my best s2.
So, I recently made a presentation for my current workplace back-end developers chapter. The subject of the presentation, you guessed it: microservices architecture.
As you can see from the diagram, microservices is a really big topic!
So, my idea is to create a mini series of articles to talk about the following microservices architecture patters:
- API Gateways;
- Service Registry & Health Checks;
- Load Balancer;
- Service communication: messaging; and
- Circuit Breaker;
I'll be using ASP.NET Core to code the examples of these articles, but the ideas will remain the same no matter what language/framework you use, ok?
Now, to be honest, I'd love to talk about more topics, but the truth is: I've just began to study microservices. For now, that's all I feel comfortable to talk about. But, you know, if enough people enjoy this mini series, I'll write about more topics, since I'll continue to study the architecture!
Before we dive into the topics, let's talk about what the microservices architecture actually is.
Monolithic architecture aren't inherently bad, ok? It has some benefits, like:
Simple to develop -- IDEs and other tools are focused on building single applications;
Easier to apply radical changes -- You can change the code, the database scheme, build and deploy;
Simple to test -- Developers can write tests for the entire application (unit, integration, E2E...);
Simple to scale -- You can run multiple instances of an application behind a load balancer.
With all that said, why does development, testing, deploy and scaling start to become difficult as the monolithic application grows?
As time goes by, applications get bigger and more complex. Because of this, a series of problems start to happen.
Complexity: as said before, the code base of monolithic applications will grow as more and more features get added to the application. As a result, the code base becomes extremely complex, and no developer will fully understand all the code. This will make adding new feature and/or correcting new bugs more difficult and time consuming. By the end, any architectural style that was initially chose, will become a "big ball of mud";
Slow Development: complexity aside, the size of the code base will also impact the IDE's responsiveness, build and startup times. This makes so that the edit-build-run-test loop takes a long time;
Commits to Deploy: with several developers making commits to the same code base, the build will often be on an improper release state.
If developers try to mitigate the problem by using a strategy like "feature/" branches, they will face a complicated merge process. Therefore, long periods of testing and stabilization will be necessary.
Also, the testing process will be very complex. Since the code base is large and the impact of new changes is never clear, developers must run all tests in the CI. Some tests might even be manual. It's also complicated to diagnose and correct the cause of a failing test;
Difficulty to Scale
In a big monolithic application, some modules might demand more memory, while others might demand more CPU power. Since all the modules belong in the same application, the server configuration might be compromised;
It's very hard to fully test a big application. Therefore, bugs will find their way into production. To make things worse, it's difficult to isolate problems. For example, imagine a module has a memory leak, this could bring the entire application down;
As time goes by, the chosen stack might become obsolete. In a monolithic application is very hard to adopt new frameworks and technologies. To make the adoption, the team will have to re-write the entire application, which is very complicated and costly.
With all the problems that surface with a big monolithic application, we need a solution. Enters: microservices architecture.
The microservices architecture will impact on non-functional requirements, like: maintainability, testability, reliability...
But what are microservices? Let's define it using Martin Abbot's and Michael Fisher's Scale Cube:
As you can see, we can scale an application in three axis:
- X-Axis: that's when we create new instances of an application, and put them behind a load balancer to handle the increasing load;
- Z-Axis: that's when we partition the load between instances of an application. For example, instance #1 will handle usernames from A through H, instance #2 from I through P and instance #3 from R through Z;
- Y-Axis: this is the axis that defines microservices. Here, we'll use a process called 'functional decomposition' to break our monolithic application into services. Theses services can also be scaled in the X and Z-axis.
I'm not going to explain the 'functional decomposition' process for two reasons. First, as of yet, I don't know how the process works. I believe that the process is based on the Domain Driven Design (DDD), as you create sub-domain models for you application that define your services. And second, this article is just a simple introduction to microservices.
So, a high-level definition for microservices is:
"An architectural style that functionally decomposes an application into a set of services"
-- Chris Richardson, Microservices Patterns.
The benefits of using a microservices architecture include:
Enables continuous delivery and deployment of big and complex applications;
- Since the service is relatively small, automated tests are easier to write and faster to execute;
- Each service can be deployed independently of other services (if changes are local to the service). Therefore, it's easier to deploy new changes to production;
- Allows for teams to be autonomous and loosely coupled, as each team will be responsible, at most, for a few services. This makes development faster.
Services are "small" and easy to maintain;
- With a smaller code base, the IDE's responsiveness is improved, the developers fully understand the service, and each service builds and starts faster, improving productivity;
Services scale independently;
- As said before, each service can be scaled in the X and Z-axis as needed, and the servers can be better suited to each service's needs;
Easy experimentation and adoption of new technologies;
- As developers begin to code a new service, they are free to choose any technology/framework (to a certain degree, it will depend on the company's policy);
- Since the services are relatively small, re-write them using better solutions becomes more practical. If the new technology fails, you can just throw it away without risking the entire project;
- If a service has a problem - e.g. memory leak - and it goes down, it won't bring down the entire application. Therefore, the impact is much smaller.
Ok, microservices have a lot of benefits, but what about the drawbacks? Nothing is perfect, right? Right! Microservices have some drawbacks too. Here a few:
It's difficult to determine the correct set of services;
- There isn't a formula to determine the set of services. They must be carefully thought, otherwise you'll end up with a distributed monolith, which has the drawbacks of both microservices and monolithic architecture;
Distributed systems are more complex;
- They need to communicate through some inter process communication mechanism, which are more complicated than simple method calls. They also need to deal with partial failures and high latency;
- Each service has it's own database, that makes it difficult to implement transactions and queries that might cover multiple services. They need to implement sagas to maintain data consistency. Also, they can't recover data using simple queries, they need to implement API Composition or CQRS;
- It introduces the need for a high automation level;
Features that cover multiple services must be carefully deployed;
It's tough to decide when to adopt;
- Consider, for example, a startup. They need to get their product to market ASAP, but microservices architecture is much more complex than building a simple monolithic application.
As we saw, microservices have a lot of benefits and a few drawbacks - as anything does in tech. When you decide to adopt it, you must be very careful to implement it. But for complex applications, it's often the right tool for it.
Now that we've talked about what microservices architecture is, we can begin talking about some of its patterns. On part two, we'll talk about API Gateways. Until next time!
- What are microservices? - https://microservices.io/;
- Microservices Patterns: With examples in Java - Chris Richardson;