The Monolith That Wouldn't Die
A few years ago, I inherited a monolith that had grown beyond any reasonable size. It was the backbone of our logistics platformâhandling everything from user authentication to route optimization to payment processing. And it was a nightmare.
One small change to the payment module meant redeploying the entire application. A bug in the routing logic could bring down the entire system. New developers spent months just understanding the codebase.
The obvious solution? "Break it into microservices!" everyone shouted. And they were right. But they were also wrong.
The Microservices Trap
Here's what I've learned from migrating multiple enterprise platforms: microservices are not a technology problem. They're a reasoning problem.
The common approach goes like this: "Let's take our monolith, split it along technical boundaries, and deploy each piece independently." It sounds sensible. But it almost always fails.
Why? Because you've just created a distributed monolith. You've split the code, but you haven't split the logic. Services still depend on each other in ways you don't understand. You've traded deployment complexity for network complexity. Not exactly a win.
The Mathematical Pivot
I remembered something from my mathematics days at Yogyakarta State University. When we studied functions, we talked about domains and codomains. A function maps inputs to outputs. It doesn't care about implementation detailsâit just cares about the transformation.
That's the insight: a service is just a function. It takes some input, produces some output, and has a well-defined boundary. Everything else is implementation detail.
So instead of asking "What technical components can we split?" we started asking:
What are the domain boundaries? (What maps to what?)
What are the invariants? (What must always be true?)
What are the preconditions and postconditions? (What must be true before and after?)
This was pure functional thinking. And it transformed our entire approach.
The Refactor That Worked
We stopped building microservices based on technical layers. Instead, we built them based on business capabilities and data ownership.
User Service:
Owns identity, profile, and authentication
Route Service:
Owns routing logic, maps, and optimization
Order Service:
Owns orders, tracking, and history
Payment Service:
Owns transactions, billing, and invoices
Each service owned its own data. Services communicated through well-defined APIs. And crucially, each service maintained a clear invariant:
"The User Service always returns valid user objects. The Route Service always returns valid routes. The Order Service always maintains state consistency."
What Happened Next
The results were transformative. Deployment became trivialâwe could deploy each service independently. Teams could work in parallel without stepping on each other's toes. New developers could understand a single service in days, not months.
But the biggest win was in debugging. When something went wrong, we could trace it to a specific service with a specific contract. No more hunting through millions of lines of code for a bug that could be anywhere.
"That's the mathematician's advantage in architecture. We don't just build thingsâwe define the rules that govern how they interact. We establish invariants, enforce preconditions, and guarantee postconditions. Everything else is just implementation."
The Mindset Shift
Here's what I want every engineer to understand:
1. Start with boundaries, not technology. Don't ask "What technology should I use?" Ask "What are the boundaries of this system?" Once you've defined the boundaries, the technology choices become obvious.
2. Define invariants first. Every system has rules that must always be true. Identify them early. Enforce them strictly. Everything else can be flexible.
3. Think in terms of contracts, not implementations. Your APIs are contracts. They define what inputs are valid and what outputs are guaranteed. The implementation can change as long as the contract remains the same.
4. Embrace the distribution, don't fight it. Distributed systems are harder than monoliths. But they're also more scalable, more resilient, and more maintainableâif you design them right.
Parting Thought
Building distributed systems isn't about mastering Kubernetes or Kafka. It's about mastering abstraction. It's about looking at a complex system and seeing the mathematical structure underneath.
When I look at a system now, I don't see code. I see functions, invariants, transformations, and boundaries. I see the mathematics that makes the system work.
And that's the real power of a mathematical mindset: it lets you see through the complexity to the simple, elegant structure underneath.
So the next time you're designing a system, don't reach for a whiteboard. Reach for your mathematical intuition. Ask the hard questions about boundaries, invariants, and contracts. The code will follow.
