Deciding where an enterprise application's boundaries go and which direction dependencies cross them: the classical presentation / domain / data-source split, the styles that reorganise it (hexagonal, clean, modular monolith, vertical slices), and how a boundary is enforced rather than documented. Use when a package structure is argued about, when a controller contains business rules, when an entity or DTO travels end to end, when a service layer only forwards, when hexagonal is adopted witho...
Scanned 9/19/2026
Install to Claude Code
npx -y skills add robsonkades/agent-skills --skill layering-and-boundaries --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Layering And Boundaries?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/robsonkades-layering-and-boundaries)More formats (shields.io, HTML) on the badges page.
---
name: layering-and-boundaries
description: >
Deciding where an enterprise application's boundaries go and which direction dependencies
cross them: the classical presentation / domain / data-source split, the styles that
reorganise it (hexagonal, clean, modular monolith, vertical slices), and how a boundary is
enforced rather than documented. Use when a package structure is argued about, when a
controller contains business rules, when an entity or DTO travels end to end, when a
service layer only forwards, when hexagonal is adopted without a driver, or when adding a
field requires editing seven files. Does not cover which layer business rules take
(domain-logic-organization), whether a boundary should be remote
(distribution-boundaries), the data-access patterns (data-source-patterns), or what the
application service around a use case owns (service-layer-design).
---
# Layering and Boundaries
## Purpose
Give each boundary in the system a reason to exist, a direction, and a mechanism that
enforces it. Layers are the oldest structuring idea in enterprise software and the most
routinely cargo-culted: teams inherit three packages and a naming convention without the
constraint that made them worth having, and pay the indirection cost with none of the
benefit.
The two failures this exists to prevent: layers that are documentation only, so the domain
imports the web framework and nobody notices for a year; and layers multiplied past their
value, so a field addition touches an entity, a mapper, a DTO, a request, a response, a
service interface and its single implementation.
## Workflow
1. **Identify the desired change or violated contract, then find existing boundaries**,
including informal ones: a package
nobody outside touches, a class every feature edits, a schema owned by another team.
Inspect callers, build rules, transaction/security ownership and accepted architecture
decisions. If a missing ownership or compatibility requirement changes the choice, ask
for that requirement; mark minor reversible assumptions and proceed.
2. **For each candidate boundary, name what varies across it.** A boundary earns its cost
when it isolates change, ownership, trust, invariants or a public contract. Co-change
is a reason to inspect its cost, not proof that these protections are unnecessary.
Compare retaining the current structure with the smallest useful adjustment and any
materially different style justified by those drivers.
3. **Fix the dependency direction and write it down.** Direction is the whole substance of
a layering decision; without it you have packages, not layers.
4. **Decide what crosses.** The type that crosses a boundary is part of the boundary's
contract. Serializing a JPA entity directly can couple the HTTP representation to the
persistence model and lazy-loading behavior; inspect the actual mapping/serialization.
5. **Enforce mechanically using the project's language/build tools.** Module visibility,
dependency checks or compilation units; the reference's ArchUnit examples are Java-specific.
Check that forbidden dependencies actually fail; code review alone can miss violations.
6. **Recheck the count.** Use representative change history and the boundary's protection
contract to assess value. Missing history, especially in a new system, is uncertainty;
remove a boundary only after checking callers, invariants and migration cost.
## The classical split, stated as obligations
```text
Presentation request/response shapes, protocol, formatting, validation of
input syntax, session and navigation. Knows the domain;
the domain does not know it.
│
▼
Domain business rules, invariants, workflow, calculations.
Should be readable without knowing whether the caller is
HTTP, a scheduled job or a message consumer.
│
▼
Data source persistence, external systems, transaction mechanics.
Knows the schema and the protocol; ideally does not know
the business rules that use it.
```
Two properties matter more than the diagram. **Downward-only dependency:** a lower layer
never names a higher one. **Skip rules are a decision, not an accident:** presentation
reaching data source directly can be legitimate for read paths; it is a defect when it
bypasses required authorization, tenant isolation or invariants on either read or write
paths (`query-objects-and-specifications`).
## Decision rules
```text
Two sides change for the same reason, at the same time, by the same team
→ investigate redundant separation; merge only if no independent
protection/contract justifies it and compatibility can be preserved.
Two sides differ in what they are about (business rules vs SQL dialect)
→ a boundary, enforced by dependency direction. Cheapest and
highest value: this is the classical layering split.
Two sides differ in ownership or release cadence
→ a boundary that must be enforced mechanically, because social
enforcement fails exactly when the two teams are busiest.
A dependency must point upward (domain needs to notify, to fetch, to
schedule)
→ invert it: the consuming domain/application layer owns the port, the outer layer
implements it. This makes the dependency follow the inside-owned
contract, as in ports and adapters.
An interface exists with exactly one implementation, no inversion, and no
second implementor in prospect
→ inspect whether it expresses an API, ownership or testing contract;
without such a purpose, consider removing the indirection
(enterprise-architecture-smells).
The boundary is between features rather than between technical concerns
→ consider vertical slices or modules; the layer packages will
otherwise scatter each feature across three places.
```
## Rules
- A layer is defined by its dependency direction, not by its package name. `service`,
`repository` and `controller` packages with imports flowing in both directions are a
naming convention with layering vocabulary attached.
- The domain layer's test is blunt and worth applying literally: could this code compile
and its tests run with the web framework and the ORM off the classpath? Where the answer
is no, name the specific import and decide whether it is a leak or an accepted trade.
- Do not confuse **layers** (a dependency rule) with **tiers** (a deployment topology).
Layering is a source-code decision with indirection and migration costs. Tiers add a
network, serialisation and partial failure, making changes operationally more involved
(`distribution-boundaries`). Neither is cost-free or inherently irreversible.
- The type that crosses a boundary is the contract. Decide deliberately whether it is the
domain type, a dedicated representation, or a projection; each choice is defensible and
the failure is choosing by default (`remote-facade-and-dto`).
- Layer count is a cost. Three layers with real constraints beat six with none. Every
additional layer must buy something nameable; it can add mapping, indirection and a
longer change path, depending on the contracts that cross it.
- The read path and the write path are allowed to differ. Writes benefit from going
through the domain to protect invariants; reads frequently do not, and forcing every
query through an aggregate can add unnecessary loading. Inspect generated queries and
fetched data before attributing N+1 or over-fetching to the layer choice
(`architecture-and-performance`).
- Hexagonal, clean and onion architectures share an inward-dependency principle —
outward dependencies are inverted through interfaces the inside owns — with different
vocabularies and different prescriptions for application/domain structure. Name the
concrete dependency rules rather than assuming the labels are interchangeable.
- Adopting one of those styles is a decision with drivers, not a default. The driver is
usually "the domain must be testable and outlive this framework" or "we will replace
this integration". Without such a driver you are buying mapping code.
- A boundary you cannot violate accidentally is worth more than a boundary described in a
wiki. Prefer compiler and build-time enforcement that covers the intended rule; ArchUnit
is one option for Java bytecode, not evidence that runtime wiring or business invariants
are enforced.
Deliver the retained or proposed boundary, its protected outcome, allowed dependencies and
crossing types, concise alternatives/rationale, and validation performed or still needed.
For a change, identify affected callers and a verifiable migration increment that preserves
required transaction, security and compatibility behavior. Record consequential decisions
using the repository's ADR convention (`architecture-decision-making`); routine choices
need only a concise rationale. State what missing evidence would change the recommendation.
## References
- [Layering styles compared](references/layering-styles.md) — classical three-layer,
hexagonal/ports-and-adapters, clean, modular monolith and vertical slices side by side:
what each actually constrains, what it costs, the driver that justifies it, and where
classical layering is still the right answer. Read when the style itself is the question,
or when a team proposes adopting one.
- [Enforcing a boundary](references/boundary-enforcement.md) — package layout that makes
violations visible, ArchUnit and JPMS enforcement with concrete rules, what may cross a
boundary and in which direction, and the seven recurring leaks (entity in the web layer,
framework annotations in the domain, transaction demarcation in the wrong place, and the
rest). Read when designing the package structure or auditing an existing one.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!