The Lie of Modularity: Why Your Distributed System Breaks in Ways You Never Expected

You know that sinking feeling. The one when your system—built from clean, independent, reusable components—inexplicably fails in production. The logs show nothing. The tests passed. But somewhere, deep in the interaction between two services you thought were isolated, something went wrong.

This isn’t a bug. It’s a feature of how you think about composition.

Most engineers believe that running two independent components together is just addition. But in formal specification, composition is a collision of state spaces. Ignoring this creates the exact distributed bugs TLA+ is meant to prevent.

I’ve been there. I once spent three weeks debugging a distributed transaction system. Each component worked perfectly in isolation. The combined system? It would deadlock every few hours. The root cause? A shared state variable that both components accessed—one reading, one writing—in a pattern that only surfaced under specific timing conditions. The developers had assumed that because the components were ‘independent,’ their state spaces were separate. They were wrong.

This is the core insight of the Mimeng principle: Emotion first, logic second. The frustration of hidden interactions is real. It’s not just you. It’s the fundamental nature of composing state machines.

Let’s get specific. In TLA+, when you compose two specifications, you’re not just running them side by side. You’re creating a new state space that is the Cartesian product of the two original state spaces. That means every variable from both components is now shared. Every transition is interleaved. The emergent behavior is not a sum—it’s a whole new beast.

Yet most teams treat composition as a deployment concern. They adopt microservices, define APIs, and assume that as long as each service respects its contract, the system will work. They ignore the shared state. The caches. The databases. The message queues. The state that is implicitly shared because two services both read from the same configuration source.

Neutrality is death. Pick a position: The modularity myth is the single biggest cause of distributed system failures, and it’s time to call it out.

I’ve seen this pattern across dozens of projects. The team that builds a ‘composable’ system only to discover that the composition introduces new invariants they never modeled. The ‘independent’ services that fight over a shared lock. The ‘reusable’ component that corrupts state when used in a different context.

Here’s the twist: The best articles make you rethink something you thought you knew. You thought composition was about reuse. It’s actually about collision. The most powerful technique is not to avoid collisions—it’s to model them explicitly. Use TLA+ to specify the shared behavior, not just the individual components. Write a spec that includes the interactions, the communication channels, the shared variables. Then prove that the combined system satisfies your safety properties.

I learned this from a team that built a consensus algorithm. They had separate specs for each node. The composition failed. They rewrote the spec to model the network as a shared state machine. Suddenly, they could prove the protocol correct. The difference was dramatic: they went from chasing bugs to preventing them.

So what do you do? First, stop assuming that independent components remain independent. Second, when you compose, write a spec that captures the combined state space. Third, use TLA+ to find the collisions before they find you in production.

Stories stick; statistics slide. The next time you’re tempted to believe that modularity will save you, remember the three weeks I spent debugging deadlocks. There’s a better way. It involves modeling the collision, not pretending it doesn’t exist.

The lie of modularity is that your system is the sum of its parts. The truth is that your system is the product of its parts—and the product is always larger and more complex than you expect. Embrace that complexity. Model it. Test it. Or prepare for the inevitable collapse.

FAQ

Q: Isn't this an overreaction? Microservices work fine for most teams.

A: Microservices work fine until they don't. The failures are rare but catastrophic. The point is that the assumption of independence is false, and when you hit a composition bug, it's incredibly hard to debug. TLA+ modeling is cheap insurance against the worst-case scenario.

Q: How do I actually apply this? I'm not going to model every system in TLA+.

A: Start with the critical interactions. Identify the shared state (caches, databases, config). Write a small TLA+ spec that models only those shared state transitions. You don't need to model everything—just the parts where collisions could happen. Ten minutes of modeling can save weeks of debugging.

Q: Isn't the real problem bad design, not composition itself?

A: Bad design is part of it, but composition fundamentally multiplies complexity. Even perfectly designed components can interact in unexpected ways. The contrarian take is that true modularity is impossible in distributed systems—every component eventually shares state. Accepting that leads to better engineering.

📎 Source: View Source