← Back to home

Refactoring the Monolith: A Weekend Story

The problem with legacy monoliths

There's a certain kind of fear that only a five-year-old Spring Boot monolith can inspire. You know the one—the OrderService.java file that has grown to 4,000 lines, where every dependency is injected, and touching a boolean flag somehow breaks the invoicing system.

A few months ago, we decided it was finally time to extract the logistics routing engine out of the main supply chain monolith.

It started innocently enough. "We'll just draw a boundary around these packages," we said. "We'll use interfaces."

What followed was a weekend of untangling a Gordian knot of JPA entities. The problem with legacy systems isn't usually the logic; it's the state. Entities that were lazily loaded in one corner of the application were suddenly throwing LazyInitializationExceptions because they were now crossing service boundaries.

How we extracted the logistics service

Here's how we survived it:

  1. Strangler Fig Pattern: We didn't flip a switch. We deployed the new service alongside the old one and used feature flags to route a tiny percentage of non-critical traffic to it first.
  2. Data Replication over Distributed Transactions: Instead of doing two-phase commits (which nobody wants), we relied on eventual consistency. We published domain events to Kafka when the monolith updated an order, and the new logistics service consumed them to build its own read model.
  3. Writing throwaway tests: We wrote high-level integration tests that we knew we were going to delete. Their only purpose was to prove that the outputs matched before and after the extraction.

The outcome

By Monday morning, staging was stable. It wasn't pretty, and we still had a lot of cleanup to do, but the monolith was a little bit lighter.

👀

Oh, you're interested?

Love to hear it. I don't keep a downloadable resume floating around — but I'd much rather have a real conversation. Drop me a line and let's talk.

Say hello