Skip to main content

Command Palette

Search for a command to run...

Part 9. Operational Concerns in Modular Monoliths

Updated
4 min read
Part 9. Operational Concerns in Modular Monoliths

There’s a point in every system’s life where architecture stops being something you reason about and starts being something you experience. It usually arrives during an incident, when logs are noisy, alerts are firing, and someone asks a deceptively simple question.

“Which part of the system is actually broken?”

If your modular monolith can’t answer that clearly, then at runtime it isn’t really modular at all.

In development, modules are boundaries in code. In production, modules must be boundaries in signal. Logs, metrics, health, failures, and alerts must respect the same lines your architecture does. If they don’t, all the care you took earlier collapses into a single operational blob.

The runtime shape of a healthy modular monolith looks something like this.

One process. One deployment. Multiple, clearly attributable sources of behaviour.

If your logs don’t already look like this conceptually, diagnosing problems will always be slower than it needs to be.

The same idea applies to metrics. A single latency number for the entire application tells you very little once the system grows beyond trivial size. What you actually need to know is whether a slowdown is systemic or local.

At runtime, the system should feel more like this than a flat line.

This doesn’t mean every module needs bespoke dashboards for everything. It means the possibility exists. When an alert fires, you can immediately tell whether one module is misbehaving or whether the entire application is under stress.

That distinction is the difference between a calm response and a panicked one.

Health checks are where many modular monoliths accidentally lie to their operators. A single “healthy or unhealthy” signal flattens the system into something it no longer is. It forces binary decisions in a world that is not binary.

A more honest mental model looks like this.

In this model, the system can express partial degradation. Users might be healthy. Reporting might be lagging. Billing might be failing to reach an external dependency. The application is still running, but not everything is equal. Failure handling is where the difference between architectural intent and operational reality becomes obvious. If an exception in one module can crash unrelated behaviour, then isolation exists only in your head.

At runtime, you want failure to look like this.

Billing failing to react to an event should not destabilise Users. If it does, you’ve accidentally recreated a distributed transaction, just without the tooling to see it.

This is one of the reasons earlier parts of the series pushed so hard on honest communication and local failure. Operations is where dishonesty is punished.

Database operations are another place where modularity often collapses under pressure. When all schema changes are treated as one global concern, deployments become coupled even if the code is not.

The operationally healthy shape looks like this.

Even if all schemas live in the same physical database, ownership is clear. Migration failures are attributable. Rollbacks are targeted. People stop fearing deployments because not every change threatens everything else.

One of the most telling operational signals is how incidents are described. In unhealthy systems, everything is “the system”. In healthy modular monoliths, incidents are scoped naturally.

“We’re seeing elevated latency in Billing.”
“Users is healthy, but Reporting is behind.”

Those sentences only make sense if the runtime architecture supports them.

There’s also a human dimension here that’s easy to underestimate. Systems that are hard to observe become systems people are afraid to touch. Over time, that fear turns into conservatism, then stagnation. Not because change is dangerous, but because the feedback loop is too vague to trust.

Good operational boundaries don’t just help machines. They help people stay confident.

One of the quiet benefits of doing this work inside a modular monolith is that it prepares you for the future without forcing it. If one module eventually needs to be extracted, the operational model already exists.

The diagrams don’t change much. The lines just move.

If this looks familiar, that’s intentional. Extraction should feel like relocation, not reinvention.

Operational clarity is not a nice-to-have. It’s the difference between a system that survives contact with reality and one that slowly erodes trust.

If Parts 1 through 8 were about making the code honest, Part 9 is about making the system honest while it’s running.


That leaves one final problem to address. Not how to design from scratch, and not how to operate once things are clean, but how to get from where most teams actually are to where this series has been pointing all along.

Part 10 is about migrating a legacy layered .NET application to a modular monolith, without stopping delivery or pretending you have infinite time.