Around the mid 2000’s, “web microservices” took off as a concept. Monoliths, large applications that managed all of its functionality inside itself, had the rule of the land. Now, developers began to eye their pickaxes and break their own monoliths down into smaller parts. Developers everywhere considered breaking their multi-million-line codebases into a slew of smaller applications with only a few thousand lines each.
But what if they started with the smaller bits first? Is that a good idea? Why do we not hear about going that direction more often?
What are microservices?
There is no unified definition for microservices. When developers talk about them, they are usually referring to a setup that involves:
- many individual applications working in concert;
- each application having some singular responsibility;
- each service using their own resources and is freestanding; and
- the entire system is unified in some goal.
It is important to note that a lot of systems and web services may look like microservices, especially if they have distributed scaling for different systems. As a rule of thumb, if services are taking on more than one responsibility, it probably is not part of a microservice. Things can get complicated, though, because a microservice network may rely on other monoliths or tie into external systems.
In general, if the people working on the system think of it as a microservice network, it probably is.
Why are microservices?
To understand the appeal of microservices, we have to take a detour through why developers sometimes regard monolith as a dirty word.
The tradition in software development is to make a single project into a single thing — be it a website, application, process or whatever. Code will be built next to each other because it is serving towards the larger goals of the application itself. This makes sense, because that is how software works.
However, as time goes on, more features are added, shortcuts are made, people come and go, technology changes and problems present themselves. The codebase will become very large and new features will creep in.
Once this goes on for long enough, the organization is left with a very large piece of code that is hard to change and very hard to conceptualize for one person. This product has no ability to go down for maintenance gracefully, so it is hard to touch.
Since this software is hard to touch and conceptualize, the feature set begins to stagnate. So, after a while you are left with an impenetrable, massive block of code: a God object, a mega-artifact, a monolith.
Once one has a monolith, chances are it is too big and heavy and integral to the organization to remove and replace outright. But, with a little strategy and ingenuity, developers can pry out a piece of the functionality and patch the hole in, then pull out another piece and patch the hole… and repeat until the monolith is no more.
This is the typical use case for microservices: to replace one or more monoliths. The benefits of doing so should be apparent, given the downsides of monoliths:
- Developers can work on each piece without having to mess with all the others, in most cases.
- With some design consideration, parts can be isolated to be resilient and continue to work when other parts fail.
- Pieces can be added as needed without bringing the system down or touching other parts.
- Modernization can now be lots of little efforts, rather than one massive project.
- It is easier to conceptualize the system, either by thinking of the entire system as a few pieces or specializing in sections of the system.
- Some responsibilities may be able to be swapped out with existing products if newer and better technology becomes available.
- Additional features are simply additional microservices.
So, why even make the monolith? Why not skip the initial construction and go directly to microservices? Should you start with microservices?
Do not start with microservices
As attractive as the microservice scheme is, it is not flawless. Problems can be exacerbated by attempting to shift to microservices prematurely.
It is not to say never to start here, but in general, you should only start with a microservice setup if you already have a microservice setup since your costs are already paid. In most other cases, it only adds additional complexity and problems.
I am also not saying to avoid microservices altogether. It is preferable to iterate into a microservice setup than aim for it from the get-go. Iteration allows you to build your response to reality, rather than guessing and creating a fragmented system.
Designing for microservices is a pre-optimization
Many of the benefits of microservices can be achieved through good code practices. Clear organization, modular code designs and dependency management will alleviate many of the pains of monoliths from the get-go.
Remember, the problem is not the form or the shape of the monolith itself, but the fact that it was built too large and complicated. With some thought, feature creep can often be sent to other products before one absorbs all of them.
Working backwards from a monolith aids in the design process, since the design is proven and experience is dictating where to draw the dividing lines. Starting with microservices does not grant the same advantages — your team will be giving educated guesses as to what needs to live where. Your performance, and sometimes your network, can be brought to its knees because it turns out your modules cannot work without each other. If this happens, you now own a shadow monolith: a monolith so large that it has escaped the confines of itself.
The message overhead
One of the advantages of a single service is that everything is really close together and easily accessible through direct memory. If parts of the same service are separated through a network, there will be some propagation delay due to having to bundle request between different services, sending the messages, decoding the messages and repeating the process all over again for the response.
Very busy systems may generate more events than the system can handle, and your team may wind up solving problems they did not scope, like queueing systems.
Unclear ownership and higher burden of communication
The chain of ownership and responsibility is pretty easy to track and conceptualize when it comes to single systems. When it comes to a collection of systems, things get fuzzy. Does your team now own all of those pieces, or just a subset? If something goes wrong, who gets the alert?
Debugging such a system can be very difficult as well. It is obvious if something is malfunctioning in a single system, which system is broken in a microservice cluster? Are you willing to invest in the training and documentation required to allow your ops team to diagnose a microservice fault? Are you willing to give your developers elevated network access to debug over the network that your cluster relies on?
Microservices are a response to a problem, not a goal
The crux is this: when creating something new, one small thing is easier to start than three or four small things. The agile mindset recommends starting as small and simple as possible, and while it sounds like creating a cloud of small simple services is adhering to this principle, orchestrating such a system is inherently complex. Microservices were a response to making giant unknowable all-in-one systems; be sure not overcorrect into a sea of micro-fine, unknowable systems.