How to tame a monolith

Ranjith Zachariah
Nov 4 · 5 min read
Photo by Dawn Armfield on Unsplash

Once upon a time, a software application was a large unwieldy body of code: a Monolith. Delivering a change to the monolith was a coordinated effort that spanned multiple teams and months (even years). Software releases were big, often dangerous events.

Today, Software as a Service applications are composed of hundreds of small, loosely coupled services. These are built and delivered by software teams operating autonomously with alignment. Changes to software applications are smaller, more frequent, and less risky. This new architectural style is called Microservices.

That’s an oversimplified picture, though. Tech companies vary in their level of microservice adoption and fervor; some shunning them altogether. Even at organizations sold on the concept, adopting microservices is a journey that can span years. Most of us are still living with monoliths, somewhere in our architecture. We might refer to it as our legacy code: the code we are scared to touch. But it’s also our legendary code: it’s the code that made our business matter.

If we have to deal with monoliths some of the time, can we make that less painful? Can we make it pleasant? Can we love the monolith?

Monolith Rehabilitation Program

Review the tests

Before you touch your monolith, make sure you have a reliable automated procedure for validating that it works. Given this is a monolith, you’ll want unit tests for the many units that comprise the beast. You’ll also want component level tests that use the service through the API, and integration tests of your most common workloads. Review the existing test coverage. If you’re missing key tests, add them before proceeding with the program.

Containerize it

We’re usually scared of delivering our monoliths, and for good reason. Shipping this large body of code often requires a complicated deployment procedure. To make our monolith better, we want to deliver frequent, tiny, low-risk changes to it. That’s only possible if we have fast, reliable deployment.

Docker container technology has co-evolved with the Microservices architectural style. Organizations are building more services and wrapping them in uniform Docker containers. We get a host of benefits from Docker, including faster deployment, easier scalability and access to the container ecosystem.

Docker is great for microservices, and it’s also great for monoliths. If you can wrap your monolithic service in a Docker container, it will be far more tame.

Structure the logging

If we’re going to make changes to our monolith, we need to understand how it behaves. We can trace our service behavior by studying its log stream. That can be done more effectively using a log aggregator, like Splunk.

You’ll get more mileage from your log aggregator if your logs are relevant, consistent and structured. Make sure you log how data gets in and out of your service. For an HTTP API, add logging in your request middleware. If you use other HTTP endpoints, make sure your outbound HTTP requests are logged consistently, eg by using a shared HTTP client or outbound middleware. If you use a database, make sure your data access layer logs consistently. If you use a cache, use a shared cache client that logs cache hits/missses and latency.

A naive approach to logging is to write all relevant info in a consolidated message, eg

Received request GET /api/widgets/123

Notice we’re embedding context about the request (the HTTP method and path) directly in the message. This will make it easy to find this specific request, but hard to see the universe of requests across methods and paths.

A better log is structured:

Path=/api/widgets/123, Method=GET, Message="Received request"

With a structured log, you can easily parse out the relevant context and correlate log events. Use the structure that makes your use of the log aggregator most effective. In many scenarios, a JSON structure is best.

Example questions to answer through logging:

  • What does the request stream to my service look like?

Delete dead code

Once you’re comfortable with delivery and observability, your next move is to delete the dead weight. A monolith has likely accreted many unnecessary bits. These have been left behind by risk averse developers stopping by for a feature or bugfix. Use your IDE or a static analysis tool to find the dead code. Delete with extreme prejudice. Paraphrasing Antoine de Saint-Exupery, “A software program isn’t done when you have nothing more to add; it’s done when you have nothing more to remove.”

Format and ReSharpen

When a codebase is large, many developers tend to work on it. These developers have varied styles and levels of hygiene. Your monolith is a unified whole. You want it to be consistent and clean. Use tools, like your IDE, to reformat the code. Use a code analysis tool to apply automatic code refactorings that simplify the code. If you’re working in a Visual Studio ecosystem, I highly recommend ReSharper. ReSharper is a great boon to the code health of your monolith. You can apply automatic refactorings file by file, or even at the solution level. With ReSharper, you can be the Marie Kondo of software development, organizing and decluttering your monolith like a pro. Only accept code that sparks joy in your heart.

Bound it

Your monolith is likely aggressively violating the single responsibility principle. It may serve independent resources unrelated to its core purpose. For example, an order transaction service may also be providing order reports and support tools. Services, even monoliths, should have a bounded domain context. Extract isolated resources from your monolith into separate microservices. If query scalability is a problem, you may want to apply CQRS, and segregate a query endpoint from a command processing service.

Preserve it

After putting in all this hard work to improve your monolith, lock in the gains. You want to communicate your monolith coding standards to future developers, including yourself. A simple approach is to update the README. Describe

  • the purpose of the service

For greater adherence, you can automatically enforce your coding standards using a git commit hook.

Celebrate!

If you’ve made it this far in the program, your monolith has better testing, deployment, observability, hygiene, scope and documentation. It’s less frightening! It’s time to celebrate, perhaps with a frothy legal addictive stimulant.

Photo by Matt Hoffman on Unsplash

Now use that monolith to deliver something awesome.

Ranjith Zachariah

One man’s pursuit of happiness

Ranjith Zachariah

Written by

Ranjith Zachariah

One man’s pursuit of happiness

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade