The Cost of Unclear Boundaries in Layered Architecture

1. Introduction: layers are easy to draw, harder to preserve

Layered architecture often looks clear in a solution explorer. There may be separate projects, familiar names, and dependency arrows that seem reasonable.

But the real architecture is not defined by the folder structure alone. It is defined by what each layer is allowed to know, decide, and depend on.

2. The visible structure vs. the real structure

The visible structure of a layered system is easy to inspect. We can see projects, folders, namespaces, and references.

The real structure is less obvious. It is revealed by the direction of knowledge inside the system: which layer owns a decision, which types cross a boundary, and which details are allowed to influence other parts of the code.

  1. Where decisions are made.
  2. Which layer owns which rules.
  3. Which types are passed across boundaries.
  4. Whether lower layers depend on higher-layer concerns.
  5. Whether UI behavior leaks into application logic.
  6. Whether infrastructure details shape the domain model.

A system can look layered and still behave like a tightly coupled system.

3. What unclear boundaries look like in practice

3.1. Example 1: UI logic leaking into application services

The UI knows too much about business rules:

if (article.IsPublished)
{
    // show article
}

This may look harmless, but if the UI decides publication visibility, the same decision can easily appear again in the application layer and again in the repository.

Instead, the application layer, domain policy, or repository query should have a clearly defined role in deciding which articles are visible. The important point is not that every system must place this decision in the same layer. The important point is that the decision should have one owner.

3.2. Example 2: Repository behavior duplicated outside the repository

This is the same kind of problem discussed in another article in this series, where filtering was treated as a repository responsibility rather than repeated elsewhere. 1

Filtering starts in the repository, but later someone also filters in the application service “just to be safe”, and later again in the UI “because the list looked wrong”.

Result: The behavior now exists in three places, and no one knows which one is authoritative.

Unclear boundaries compared to clear boundaries in layered architecture

Unclear boundaries compared to clear ownership of decisions in layered architecture. Click the diagram to open it in full size.

When the same decision appears in several layers, the system may still work, but the ownership of the behavior becomes unclear.

3.3. Example 3: Infrastructure concepts leaking upward

Application or domain code starts depending directly on EF Core, SQL-specific behavior, HTTP clients, file paths, or configuration details.

Example:

public async Task<IQueryable<ArticleEntry>> GetArticlesAsync()
{
    ...
}

This exposes query mechanics instead of returning application-relevant results. It also allows callers outside the repository to keep shaping the query, which makes it harder to see where the actual data access decision is made.

3.4. Example 4: Domain model shaped by storage needs

The domain object starts accumulating properties that exist only because JSON, SQL, EF Core, or a UI grid needs them.

That is not always wrong, but it should be intentional.

4. The hidden costs

4.1. Changes become harder to localize

When a rule has no clear home, a change requires searching across the whole solution.

The cost is not only that code must be changed. The cost is that the developer no longer knows where the change belongs.

4.2. Tests become less trustworthy

If behavior is duplicated across layers, tests may pass while the real application still behaves inconsistently.

Example:

A repository test may verify one filtering rule, while the UI applies an additional condition that the test never sees.

4.3. The system becomes harder to explain

Architecture is partly about communication. If boundaries are unclear, every feature requires rediscovering the rules.

A clear boundary reduces the amount of system knowledge a developer must keep in their head at one time.

4.4. Refactoring becomes risky

When responsibilities are blurred, moving code becomes dangerous because hidden dependencies appear.

4.5. Small shortcuts become permanent design

Many architectural problems do not begin as bad design. They begin as small, reasonable shortcuts that are never revisited.

A shortcut may be acceptable in isolation. The problem appears when the shortcut becomes the pattern other code starts to follow.

5. A practical definition of a clear boundary

A boundary is clear when it is obvious which side owns a decision.

  • The domain owns business concepts and invariants.
  • The application layer owns use cases and orchestration.
  • The infrastructure layer owns persistence, external systems, and technical implementation.
  • The UI owns presentation, interaction, and user-specific display behavior.

These boundaries are not meant to remove judgment. Real systems often contain trade-offs, and the exact placement may vary between systems. What matters most is that the placement is deliberate, visible, and consistent.

6. How to detect unclear boundaries

Some warning signs are:

  1. The same rule appears in more than one layer.
  2. A lower layer references UI or application-specific concerns.
  3. UI code decides business behavior instead of presentation behavior.
  4. Application services know too much about database mechanics.
  5. A small change requires edits in many unrelated places.
  6. Tests must mock too many unrelated dependencies.
  7. Developers hesitate because they do not know where new code belongs.

7. How to improve boundaries gradually

The solution is rarely to reorganize the entire system at once. Large architectural cleanups can be risky, because they often mix too many decisions together. A safer approach is to improve one boundary at a time.

  1. Do not start by moving everything.
  2. Identify one decision that currently has no clear owner.
  3. Give that decision a home.
  4. Add tests around the intended behavior.
  5. Remove duplicate logic from other layers.
  6. Repeat gradually.

Architecture improves when important decisions become easier to locate.

8. Conclusion

Clear boundaries do not make a system perfect. They make it easier to reason about.

When each important decision has one home, changes become easier to place, tests become more meaningful, and the code becomes easier to explain to someone else.

That is the practical value of layered architecture: not that the solution looks organized, but that the organization helps future work stay understandable.

9. References

  1. Keeping Filtering in the Repository Layer