Breaking Down the Monolith
Tags: architecture, programming, microservices, patterns
Published: 2022-10-26 04:04:20

Very rarely do software engineers start with a clean slate. More often than not, we end up maintaining a Frankenstein code base that has evolved over time into something massive and increasingly difficult to modify in a way that can adapt to business needs quickly. Sometimes it doesn't even have to evolve over time in order to become an unmaintainable mess. It doesn't take very much time neglecting thought about separation of concerns and just throwing newly requested behavior in an existing service to end up with something that's doing too much and is difficult to maintain.

Usually when software engineers find themselves in this position, their first instinct is to rewrite it all at once and build it better next time. In the context of using a microservice based architecture, I would say that this is not a good strategy though. Rewriting an entire application all at once is extremely dangerous. The time required to rewrite is usually very long and problems will tend to hide themselves until the very end. This leaves you with a very long feedback loop and stakeholders that are unhappy because things will break in new and spectacular ways when you actually deploy things to production; regardless of how thorough you think your testing is.

My favorite strategy for avoiding the all at once rewrite is to use the strangler pattern. When employing this pattern, I look at the existing monolith and try to determine functionality that could exist as its own service and could have utility as an independently deployable unit. An example of this could be the act of using a templating engine to render and send an email. After identifying the section of the monolith that I would like to carve off, I then develop the future replacement in isolation. I do this while taking full advantage of the tight feedback loop that you can get from employing CI/CD practices and building quality testing into the pipeline. This allows me to learn quickly and safely while always being in a deployable state. At this point, the service is launched darkly and is not actually being used by anything. This is great because this allows me to analyze how this fledgling service will behave in the deployment environments and address potential issues before they impact any users. Once I am confident that what I have is ready for use, I then write the code to replace the old behavior with the new behavior. This is the most dangerous moment of the whole process because this is the only part of the process where the monolith is actually being modified. This is not nearly as dangerous as the full blown rewrite because the amount of time since the mistake would have been introduced should be much smaller. This means that I will have been operating under false assumptions for less time and won't need as much rework if corrections need to be made. As this process is being performed, applications that utilize the monolith can be updated to utilize the new microservices either directly or through something like an API gateway. After the process has been repeated enough times, I will eventually find the old monolith simply acts as a request router and that no applications use it. At this point, I have successfully broken down the monolith.

Ultimately, this pattern allows me to gradually make architectural improvements to a monolith while keeping things reasonably stable and still delivering features. I have found this to be a good compromise because I get to make architectural improvements and end up with a rich set of loosely coupled microservices; all while still providing the features and stability that are demanded by stakeholders.

If you have used the strangler pattern or have another favorite pattern for decomposing monoliths, let me know in the comment section below.