How I Learned to Stop Worrying and Love Microservices #1: Cost of poor code
In this series of posts I’m going to discuss different major advantages of microservices as a pattern. I’ll try to cover aspects that are usually not covered at all or are not discussed deep enough.
Software patterns usually have both pros and cons, thus their advantages are subjective and one shall pick them depending on context. However, I will try to limit these publications to objective pros only — benefits that make sense for everybody irrespective of programming language, team size or food habits.
The first article is going to focus on cost of poorly written code — comparison of how it affects monolith applications as opposed to microservice environments.
Moreover, I plan to record an easy to follow YouTube video after a few articles. Animations and screencasts will make my points clearer.
Intro: How frameworks, conventions and patterns improved spaghetti code
When you write a 100-line script it doesn’t really matter how top-notch your design is or how well you follow popular patterns. A tiny application is relatively easy to follow simply because it’s tiny, optimising it would be an anti-pattern in itself — often called premature optimisation.
Generally speaking, you start refactoring your code when you notice how messy your files have become, or that you are repeating essentially the same logic twice or more times. But in a 100-line file, how many repetition can possibly fit?
Then your boss asks for more and more features and your app starts growing. It doesn’t make sense anymore to keep all logic in a single file, you create more files. Once you have too many files, you start organising them in folders. Once you have too many folders and files, you look for a way to classify elements, you start calling them models, controllers and so on.
And then you start facing the dangers of spaghetti code — file X refers to file Y, file Y relies a little bit on file Z, file Z imports a few functions from files A and B. Then your new junior developer changes one file and breaks the whole application, and you have to fix a bug before your boss gets his first angry customer call.
Frameworks arrived to cope with this mess. Frameworks provided us with conventions, with systematic way to sort application files. Eg. controllers must only deal with web requests, model classes should contain business logic, views should only have HTML, configuration must be stored in a separate place and, preferably, depend on current environment.
Want to add a resource? Edit the route file!
Want to add extra behaviour after a record has been saved? Create an event listener!
Want to add extra database conditions? Create a table behaviour class!
And so on.
Not only all that helps, it’s the only way to maintain a large monolithic application — if you allow anybody to add code anywhere, your codebase will jump out of control pretty fast.
But reality is — you are likely to have inexperienced (or hungover) colleagues who will break conventions from time to time, and even if they don’t your code is going to grow harder and harder to follow, despite all the great optimisations.
Writing good code makes most sense in the context of a large application. SOLID principles, design patterns, frameworks and conventions are introduced to help cope with the difficulties introduced by large applications.
Monolithic vs Microservices: Effect on spaghetti
This is microservices’ effect on spaghetti:
Sorry, wrong image. This is what spaghetti code looks like for a monolithic application:
One class (or function) calling another class, and that class depends on another class, and that class depends on a few other classes and so on. You want to follow the code or a fix a bug — you have to open many files and study them simultaneously. Why is this specific to a monolithic app?
In monolithic applications, pieces of code call each other directly leveraging the fact that they are all loaded in memory on the same machine. This leads to lazy developers introducing more and more unnecessary connections.
Microservices, on the other hand, typically communicate via web calls (typically RESTful) or message bus. Therefore, a developer cannot simply call a method of one microservice from another microservice–one would have to create an extra route first, expose that method in the controller and so on. It’s an extra work that people don’t want to do unless absolutely necessary. There is also a great possibility that that other microservice is maintained by another team in your company, and they are reluctant to add a needless (from their perspective) route just to make your life easier.
As a result, you are more likely to end up with a fewer, better organised connections.
Less connections between microservices (read different functionalities of your application) means that if microservice A is poorly implemented, it won’t have much impact on other microservices.
In microservices environment, poor code is contained, isolated.
Microservices: relax your perfectionist inner self
Since one microservice is nothing more than a class or two cut out from your monolithic application, you now have fewer, dramatically fewer files. Therefore, you probably don’t need to care about strict folder or file naming convention and perhaps you can even relax your SOLID limitations.
Now you’ve got yourself some slack — you can afford a few mistakes, a few suboptimal implementations, and yet your application is still reasonably easy to follow and navigate because your LoC dropped from thousands to tens or hundreds!
You can actually develop faster now because you are less concerned about strict rules and inter-dependencies!
Microservices pattern allows you to reduce cost of suboptimal source code, deploy faster and sleep better.