July 2026

Boundaries Preserve Optionality

Good architecture is not about adding layers for their own sake. It is about preserving future options by putting decisions where they belong.

Code Quality
Good architecture is not about adding layers for their own sake. It is about preserving future options by putting decisions where they belong.

Clean architecture can look expensive before the first painful change arrives.

That is why inexperienced developers often see its pieces as ceremony. A service layer looks like extra files. A DTO looks like duplication. A repository looks like indirection. A prompt service looks like architecture around a string. A domain model looks like more work than putting the rule directly in the controller.

Sometimes they are right.

Bad boundaries are ceremony. Useless layers are real. Architecture astronauts exist, and many teams have paid dearly for patterns applied because they looked professional rather than because the system needed them.

Good boundaries are different. Good boundaries preserve optionality. They give the system a place to change. They let one part of the code move without dragging every other part with it. They turn future work from a scavenger hunt into a targeted change.

That is the point of architecture.

Integrated Is Not the Same as Simple

I saw a website delivered with monster methods in the controller and thousands of lines of JavaScript. AI prompts were hardcoded. Display logic was integrated with data processing. The boundaries between interface, behavior, data access, and AI interaction were smeared together. When I was a younger developer, I delivered code that looked like this, too.

When asked about the approach, the developer said it felt better because everything was integrated and there were not too many unnecessary layers.

That sounds reasonable if you have not been burned by that shape before. Everything is in one place. There are fewer files to open. You can read the request path from top to bottom if you are willing to keep enough of the system in your head.

That is collocation impersonating simplicity. Tangled code can look simple because the complexity has not been named yet. The UI changes, and suddenly the data transformation is implicated. The prompt changes, and suddenly the page rendering path has to be reviewed. The business rule changes, and suddenly the controller, view, JavaScript, and database path all become suspects.

The code was integrated in the same way a knot is integrated. Everything touched everything.

The Question Is Where the Decision Should Live

A boundary is not just a file split. It is a claim about ownership. Who owns this rule? Who owns this transformation? Who owns this workflow? Who owns this decision?

That is the question clean architecture is trying to answer. Not “where can this code run?” Plenty of code can run in plenty of places. Business logic can run in the controller. It can run in JavaScript. It can run in a stored procedure. It can run in an API. It can run in a background job.

The fact that code can run somewhere does not mean the decision belongs there.

Controllers should coordinate request flow. Views should display state. JavaScript should support interaction unless the application is intentionally a client-side application. Data access should retrieve and persist. API boundaries should express capabilities, not merely shuffle records around. Prompt logic should live somewhere it can be reviewed, tested, versioned, and changed.

Those are not universal laws. They are starting points for judgment. The point is not to memorize a layer diagram. The point is to ask what part of the system should own the decision and what future changes become easier or harder because of that choice.

The Wrong Boundary Can Still Work

Years ago, at a woodworking tool manufacturer, we debated where business logic belonged in a new slice of work. A senior developer argued that the API should own the business logic. I made the final call and kept the API as a pure data-transfer layer, with the business logic bundled into the web application.

The system worked. The choice did not create a performance problem. I do not remember it creating meaningful technical debt.

But in retrospect, he was right. My decision kept the API clean in a narrow sense, but it preserved fewer future options. His recommendation would have made the API the owner of the business capability. Mine made the web application the owner of that logic.

That matters because presentation layers multiply. Consumers change. Interfaces come and go. Business rules usually live longer than the screen that first needs them.

If another consumer had needed the same capability later, the system would have had three choices. Duplicate the business logic. Reimplement it slightly differently. Pull the logic back into a shared place after the first design had already taught the system a different shape.

That is the subtle cost of boundary decisions. The wrong boundary does not always arrive as an outage, performance problem, or obvious defect. Sometimes it simply preserves fewer future options. That is still a cost.

Domain-Driven Design Is One Boundary Vocabulary

This is where domain-driven design can be useful.

DDD gives teams a vocabulary for naming boundaries around business concepts. Bounded contexts, aggregates, entities, value objects, repositories, and domain services are all attempts to say: this part of the system has a shape, this object owns this rule, this concept should not leak everywhere, and this language means something specific inside this boundary.

That can be extremely valuable. It can also become religion.

I have worked with engineers who treated DDD as the only correct answer. If the code did not follow their preferred version of DDD, it was wrong. That is not architecture. That is taste calcified into doctrine.

DDD helps when the domain is complex, the business language matters, and the team needs explicit boundaries around concepts that would otherwise smear across the system. It does not help when the pattern becomes more important than the problem.

The value is not the label. The value is the preserved options.

Can this business rule change without rewriting the screen? Can this workflow support another consumer? Can this concept evolve without leaking into five unrelated modules? Can the language of the business be represented clearly enough that the next developer knows where the change belongs?

That is where DDD earns its keep.

Missing Boundaries Create Scavenger Hunts

At a marketing services SaaS provider, we had what looked like a small behavior change in a primary customer-facing application. A senior developer worked on it for a month and still could not fully deliver it. When I looked at the code, the problem became obvious.

The behavior lived in more than 40 locations.

Not one shared rule. Not one clear service. Not one module that owned the decision. More than 40 places with similar, but not identical, logic shaped by years of feature additions.

Testing that kind of change is miserable because you are no longer testing a behavior. You are testing a search effort. Did you find all the places? Did you understand all the variations? Did you preserve the accidental differences? Did one customer path depend on the old behavior in a way nobody remembered?

That is what a missing boundary costs. It turns a small change into archaeology. The system had not preserved one clean place to turn. It had scattered the steering across the codebase, and every future change had to pay for that scattering.

Sometimes the Boundary Goes Around the Mess

The cleanest architecture is not always the next correct move.

At the same company, data that drives web pages is stored across multiple tables and fields in some very interesting ways, including space-delimited lists. The current code has to merge some of that data and manipulate it based on position and index. That logic exists in multiple applications in different forms, which regularly leads to data corruption and manual intervention.

The technically ideal answer would be to refactor the database design. That would also have a huge blast radius. Internal applications would be affected. External-facing applications would be affected. Data flows, testing, integrations, deployment sequencing, and operational risk would all become part of the same move.

So the better move was smaller. My team built a library that serves as a serialization and deserialization layer between the applications and the database. The ugly logic moved into one place. Applications can be reworked to use the library. The database does not become perfect, but the system gets safer.

That is still a boundary: a boundary around the mess. Good architecture is not always the cleanest possible diagram. Sometimes good architecture is the move that reduces blast radius, centralizes risk, and gives the next change a better place to happen.

The platform becomes better, even if it is not yet best.

AI Makes Boundaries More Valuable

AI-assisted development makes this more important, not less.

Agents are very good at following explicit structure. They are much worse when the codebase teaches them that anything can talk to anything, prompts can live anywhere, business rules can sit in the UI, and data transformations can be repeated wherever a developer happened to need them.

If the human developers cannot tell where logic belongs, the AI will not magically infer the right architecture. It will copy the local pattern. If the local pattern is tangled, it will produce more tangles.

This is why standards beat preference. Explicit standards tell human and AI developers where decisions belong. Controllers coordinate. Services own behavior. Data access is isolated. Prompts are versioned. DTOs define boundaries. Dependencies are injected. Mapping happens in predictable places.

That does not remove judgment. It gives judgment a path to follow.

The same point applies to taste. A senior engineer may look at a hardcoded prompt in a controller and immediately feel the risk. That feeling is fast orientation. The teachable version is clearer: prompt behavior should live behind a boundary so it can be reviewed, tested, versioned, and changed without editing the page that happens to use it first.

That is how taste becomes architecture.

The Test of a Boundary

A boundary is earning its keep when a change stays local. That does not mean every change touches only one file. Real systems are messier than that. It means the system has a natural place for the change to begin. The developer knows where to look. The test surface is understandable. The implementation can change without breaking consumers that should not care.

Good boundaries give you options:

  • You can change storage without rewriting the workflow.
  • You can change the UI without revalidating business rules.
  • You can change a prompt without touching rendering code.
  • You can add a second consumer without duplicating behavior.
  • You can test a decision without dragging the whole application with it.
  • You can tell an AI agent where new logic belongs and expect it to follow the pattern.

That is what optionality looks like in code. It is not abstract. It is not aesthetic. It is not architecture for architecture’s sake. It is the ability to make the next change without reopening every old decision.

The point of architecture is not to admire the diagram. The point is to keep the codebase capable of change.

Receipts

  • Website field note: A delivered website had monster controller methods, thousands of lines of JavaScript, hardcoded AI prompts, and display logic mixed with data processing. The problem was not that it failed immediately. The problem was that every future change had too many reasons to touch too many things.
  • Woodworking tool manufacturer: A senior developer argued that business logic belonged in the API rather than the presentation layer. The final decision went the other way. It worked, but it preserved fewer future options.
  • Marketing services SaaS provider: A small behavior change in a primary customer-facing application required touching more than 40 similar but not identical code locations.
  • Serialization boundary: The marketing services SaaS provider built a shared serialization and deserialization library around messy database shapes, including space-delimited lists, to reduce blast radius without immediately refactoring the full database.
  • Related standard: The discipline behind these boundary decisions connects to Disciplina: Standards Beat Preference and Taste Is Fast (and Earned) Orientation.
← All writing