March 30, 2022
Clean Code: Bucket Pattern, Lazy Dependency Creation in .NET
A lot is being said these days about microservices. It’s enough of a popular trend online, at conferences, and in software architect chit-chat, that one could easily get the idea that microservices are always the superior architecture to the alternatives. In fact, even the commonly-accepted name for the alternative architecture sounds bad: monoliths. Yuck. Do you want to build a “monolith”? Of course not; you’re not evil!
Many factors are currently pushing us toward seeing microservices as the savior of software systems and monoliths as the bad guys. I’d like to try to convince you that both approaches are totally valid solutions to different types of problems. In other words, depending on the problem you’re trying to solve with software, one approach or the other might be the better option for you.
Let’s delve deeper into why.
True microservices, when done correctly, are great at what they do. But implemented poorly–or for the wrong reasons–they can be something much worse than a monolith–a distributed monolith.
Distributed monoliths usually result from an attempt to create microservices, but fail to decouple the services from each other. This coupling creates dependencies between services which makes the application still a monolith, but with the added disadvantage of also being a physically distributed system (read: slower than a monolith).
As it turns out, the monoliths that we’ve been building for these last several decades also come in good and bad varieties, but you probably already know that from your own experience. There are the ones with too much coupling which are difficult to change or add to without unintended consequences. These systems can be referred to as “ball of mud” monoliths. However, there are also some well-designed monoliths. They may still might some coupling between their modules and components, but much less than the ball of mud, and these modular monoliths are generally much easier to update and maintain.
The first rule of building microservices is: don’t build microservices. If you’re going to do microservices, as Sam Newman says, make sure you have a “good reason” first. Even then, many experts in the field would still suggest starting with a monolithic solution and, only after that is functional and in use, refactor it into microservices.
Some of the most commonly cited reasons in favor of using microservices are:
1. Additional ways to scale up your application
Monoliths can be scaled in many ways, including horizontal and vertical scaling. Examples of vertical scaling include adding processor, storage, and memory resources to your servers or virtual servers. Examples of horizontal scaling include sharding your data across multiple database instances or load balancing your application across multiple web servers.
Building microservices can increase the flexibility of this scaling, allowing you to scale just one service or another as needed.
2. Independently deployable services that can be built by separate teams on separate tech stacks
Do you separate business units with their own development teams who operate independently of either other? If so, then your organization is probably a good fit for microservices. Deploying microservices in this kind of environment can also unlock additional flexibility, like separate release cycles and using different technology stacks on each microservices.
3. A need to isolate each service from the downtime or failures of the other services
Finally, well-designed microservices are great at isolating modules from the failures or downtime of other modules. If each module is truly separate from the others, each can function on its own without the need for the others to be available. The side effect of this architecture, though, constitutes my final warning about microservices–something called eventual consistency.
If, after all of what I said above, you still think microservices might be right for your project, please know that one major side effect of microservices: they require you to be ok with something called “eventual consistency”. That means that each module may be slightly out of date with the other modules. It’s possible, for example, that a user will update their billing address, and while that change is still propagating between services, they will start a new order which will still show their old billing address.
If this sounds like something that won’t work for your application, then please, DON’T USE MICROSERVICES, because this type of eventual consistency is inevitable in a well-designed set of microservices. Ignoring this limitation will almost certainly cause you to accidentally build a distributed monolith.
Whether you’re looking for advice about which architecture to choose, or you need help implementing one, either way, Trailhead can help. Reach out to us to be connected with a software expert who can help you with these difficult decisions and architectures.