The Hidden Art of Building Software Around Boundaries, Not Giants

tfc

Hatched by tfc

Jul 08, 2026

10 min read

87%

0

The Strange Truth About Simplicity

Most software teams do not fail because their code is too small. They fail because their boundaries are too vague.

That sounds almost backwards. We are taught to think that architecture is mainly about scale: small systems can stay simple, and large systems need more machinery. But the more interesting question is not how big a system is. It is how clearly it knows where one thing ends and another begins.

That is why two ideas that often get treated as separate, even ideological, can actually point to the same deeper discipline. One is the notion of an Aggregate: a cluster of associated objects treated as a unit, with a root that becomes the only outwardly visible reference point. The other is the modular monolith: a system organized into modules rather than fragmented into distributed services.

At first glance, one sounds like an object modeling concept and the other like an architectural deployment choice. But both are really about the same challenge: how do you preserve integrity when a system grows? The answer is not merely to add layers or split code. The answer is to design boundaries that are strong enough to protect meaning, but not so strong that they destroy coherence.

Good architecture is not the art of dividing software. It is the art of deciding what must be treated as one thing, and what must never be allowed to leak outside it.


The Real Problem: Not Size, But Leakage

When software becomes painful, the instinct is often to reach for a new organizational label. Teams say they need microservices, clean architecture, serverless, event driven systems, or better abstractions. Sometimes those moves are right. But many times, they are just a reaction to a much older problem: every part of the system can talk to every other part.

That kind of leakage creates three familiar forms of entropy.

First, data entropy: one part of the system modifies state that another part assumes is stable. A payment can be captured while the order is still invalid. A user profile can be updated while onboarding is incomplete. A background job can half finish a workflow and leave the rest of the app guessing.

Second, conceptual entropy: names stop meaning anything precise. Is an order a draft, an active purchase, a shipment, or a ledger entry? If every module can poke at every field, the distinctions blur until the code no longer expresses the business.

Third, organizational entropy: every change requires coordination across too many parts of the system, so team boundaries become meaningless. Developers spend more time negotiating side effects than building features.

This is where the idea of an Aggregate matters. An Aggregate says something profound: some collections of objects are not just related, they are inseparable for the purpose of change. They form a unit of consistency. You may look at them from the outside, but you must not reach inside and mutate random parts. Only the root speaks for the whole.

That is not a technical trick. It is an epistemic claim. It says the system contains a zone where the only safe way to interact is through a single point of truth.


Why Modular Monoliths Feel So Ordinary, and Why That Is Their Strength

The modular monolith has a surprisingly unglamorous reputation. It does not promise the drama of independently deployed services. It does not come with the marketing thrill of infinite scalability. It simply says: organize the application into modules.

That simplicity is easy to miss because it sounds almost too modest. But modesty is exactly the point. Most systems do not need distribution. They need discipline.

A modular monolith works when the major requirement is not independent runtime scaling, but clear internal structure. It gives you the benefits of separation without turning every boundary into a network boundary. Modules can be compiled, tested, understood, and evolved as coherent units, while still maintaining strict interfaces.

The deeper lesson is that modularity is not the same thing as fragmentation. A fractured system is full of pieces that barely understand each other. A modular system is full of parts that are intentionally connected, but only in disciplined ways. That difference matters because business domains are usually not a set of independent atoms. They are overlapping, interdependent stories.

Consider an online store. Inventory, orders, payments, shipping, and customer accounts are distinct concerns. But they are not autonomous kingdoms. A refund must know whether the order was captured. Shipping must know whether inventory is reserved. Customer support needs a consistent view of the order lifecycle. A modular monolith can model those relationships without pretending they are all separate products.

That is the hidden elegance: the system remains one conversation, not a federation of conversations that can no longer agree on the truth.


Aggregates and Modules Are Two Versions of the Same Boundary

If you look closely, an Aggregate and a module solve adjacent problems at different scales.

An Aggregate says: inside this boundary, certain objects may collaborate freely, but outside the boundary, only the root may be addressed. The purpose is consistency.

A module says: inside this boundary, certain classes or components belong together, but outside the boundary, only the public API should be used. The purpose is maintainability.

Together, they form a nested model of order.

At the smallest level, the Aggregate protects business invariants. It ensures that the facts of the domain remain coherent. At the larger level, the module protects architectural invariants. It ensures that the codebase remains navigable and resilient to change.

This nested structure is worth pausing on because it reveals a useful mental model: systems are healthiest when every level has a boundary appropriate to its kind of truth.

A database row is not the same kind of truth as an order lifecycle. A class is not the same kind of truth as a bounded business capability. A module is not the same kind of truth as a deployment unit.

Confusion begins when teams collapse these layers. They make a class do the job of a business process, a module do the job of a service, or a service do the job of a domain. The result is almost always one of two failures: either the system becomes too rigid to change, or it becomes so distributed that no one can reason about it.

The healthy alternative is to align the boundary with the kind of change you want to contain.

A boundary is not there to create distance. It is there to localize the cost of change.


A Practical Framework: The Boundary Test

When designing software, the most useful question is often not, “Should this be a microservice?” It is, “What must be kept consistent, and what can be allowed to vary independently?”

That question leads to a simple framework.

1. Identify the consistency core

Ask what facts must never disagree. In an order system, perhaps an order cannot be paid twice, shipped before payment, or modified after fulfillment. These are not just rules. They define the consistency core.

That core belongs inside an Aggregate or similarly strict boundary. It should have one route for modification, usually through the root.

2. Separate read shape from write control

A common mistake is to let every object that needs to be read also become an object that can be mutated. But the thing you need to display is not always the thing you should edit.

For example, a support dashboard may need to show customer name, last order status, shipment tracking, and payment state in one screen. That does not mean every screen element should hold a direct write path into each underlying object. Read models can be broad. Write boundaries should stay narrow.

3. Keep modules aligned with business language

A module should represent something a product manager, analyst, or domain expert can name without hand waving. If a module cannot be described in business terms, it is probably a technical convenience, not a real boundary.

This is one reason modular monoliths are so effective. They force you to make the internal shape of the code resemble the shape of the business, while avoiding the premature complexity of distribution.

4. Make cross boundary interaction explicit

If one boundary needs to affect another, do not let it reach in casually. Use events, commands, or public APIs. This is not bureaucracy. It is a way to make coupling visible.

Invisible coupling is the enemy. Explicit coupling is negotiable coupling.

5. Prefer one strong boundary over many weak ones

A single well enforced Aggregate root is often more valuable than a dozen objects with partial responsibility. Likewise, one clearly defined module is better than several fuzzy packages with shared internals and informal agreements.

Weak boundaries create the illusion of flexibility while quietly accumulating chaos.


The Deeper Tradeoff: Local Freedom Versus Global Truth

At the heart of all this is a tradeoff that software teams rarely name directly: local freedom and global truth pull against each other.

The more freedom each part has to change independently, the easier development feels in the short term. But the more freedom you allow, the harder it becomes to preserve a shared truth about the system. The more tightly you enforce global truth, the more you constrain local experimentation.

This is why architecture is not a purity contest. It is a negotiation between change and certainty.

Aggregates favor certainty within a narrow domain of responsibility. Modules favor freedom within a broader codebase. Modular monoliths are compelling because they let you place the expensive forms of coordination where they belong, rather than paying the tax of distribution everywhere.

That is a deeply underappreciated point. Many teams adopt microservices hoping to gain freedom, but they often buy a different kind of constraint: network calls, deploy choreography, data duplication, observability overhead, and eventual consistency everywhere. They create more boundaries than they can meaningfully manage.

A modular monolith, by contrast, says: preserve the semantics of separation without forcing the infrastructure to pretend the system is already a federation. It is a refusal to confuse architectural maturity with architectural sprawl.

You can think of it like a city that chooses strong neighborhoods before building highways. The goal is not to isolate communities forever. The goal is to make each neighborhood legible and self-contained before you connect it to others through carefully controlled routes.


Key Takeaways

  1. Design boundaries around consistency, not around fashion. Ask what must remain true as a unit, then place the strictest boundary there.

  2. Treat modules as meaning, not just folders. A real module reflects a business capability or domain concept, not merely a convenient way to group files.

  3. Use a single root or API to protect internal complexity. Whether at the Aggregate level or module level, one controlled entry point prevents accidental leakage.

  4. Separate the need to read from the right to mutate. Broad visibility does not require broad write access.

  5. Prefer explicit cross boundary communication. If two parts must coordinate, make that coordination visible through APIs, events, or commands rather than hidden references.


The Most Useful Question You Can Ask About Any System

The next time a software system feels unmanageable, do not ask first whether it needs to be broken apart.

Ask something sharper: where is truth being violated?

That question changes the design conversation. It moves you away from chasing abstraction for its own sake and toward protecting the parts of the system that must remain coherent. Sometimes the answer will be an Aggregate. Sometimes it will be a module. Sometimes it will be a modular monolith that keeps everything in one deployable unit but many disciplined boundaries.

The point is not to build the most distributed system or the most object oriented system. The point is to make the system speak clearly about what belongs together, what may vary, and what must never be casually touched.

In that sense, architecture is not primarily about technology. It is about respecting the shape of reality inside the software.

And once you see that, you stop asking how to split systems apart. You start asking how to give each part the exact boundary it deserves.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣