Layer Analysis
Boundary classifies source code components into four architectural layers and enforces dependency rules between them.
Architectural Layers
From innermost (most protected) to outermost:
| Layer | Purpose | Default Patterns |
|---|---|---|
| Domain | Core business logic, entities, value objects | **/domain/**, **/entity/**, **/model/** |
| Application | Use cases, application services, orchestration | **/application/**, **/usecase/**, **/service/** |
| Infrastructure | Database adapters, external APIs, persistence | **/infrastructure/**, **/adapter/**, **/repository/**, **/persistence/** |
| Presentation | HTTP handlers, CLI, API controllers | **/presentation/**, **/handler/**, **/api/**, **/cmd/** |
Dependency Rules
The core rule is that inner layers must not depend on outer layers:
Domain ← Application ← Infrastructure
← Presentation
Valid dependencies:
- Application can import from Domain
- Infrastructure can import from Domain and Application
- Presentation can import from Domain and Application
Violations:
- Domain importing from Infrastructure or Presentation
- Application importing from Infrastructure or Presentation
- Any circular dependency between layers
Scoring
Boundary calculates three sub-scores that combine into an overall architecture score (0–100):
| Score | Default Weight | What It Measures |
|---|---|---|
| Layer Conformance | 40% | How closely each package’s (A, I) values match its assigned layer’s expected region |
| Dependency Compliance | 40% | Fraction of cross-layer imports that flow in the correct direction |
| Interface Coverage | 20% | Balance between domain port interfaces and infrastructure adapters |
Interface Coverage
Interface coverage measures how well your infrastructure layer uses ports (interfaces) to decouple from the domain. Boundary counts:
- Ports: Exported interfaces in the Domain layer
- Adapters: Components in the Infrastructure layer with kind
Adapter,Repository, orService
The score is min(ports, adapters) / max(ports, adapters) * 100. If there are no infrastructure adapters, the dimension is undefined and omitted from output — it is never defaulted to 100.
Go-specific adapter detection
In Go, infrastructure adapters commonly use unexported concrete types paired with an exported constructor:
// unexported concrete type — boundary counts this as a real component
type mongoUserRepository struct { ... }
// exported constructor — the usual Go idiom
func NewMongoUserRepository() ports.UserRepository {
return &mongoUserRepository{}
}
Boundary includes unexported structs from the infrastructure layer in all component counts and interface coverage calculations.
Structs named *Handler or *Controller in the application or presentation layers are treated as orchestrators, not adapters, and are not counted toward interface coverage. Infrastructure-layer handlers (driving/primary adapters) are counted as infrastructure components.
Component Extraction
Boundary identifies these component types from source code:
- Interfaces / Traits – Port definitions
- Structs / Classes – Entities, value objects, adapters
- Imports – Dependency relationships between components
- Functions – Service methods, handlers
Automatic Filtering
Standard Library Imports
Standard library imports are automatically excluded from the dependency graph. For Go, any import path without a dot (e.g., fmt, encoding/json) is recognized as stdlib. This prevents stdlib packages from inflating the unclassified component count.
External Dependencies
Import targets that don’t correspond to any source file in the project (e.g., third-party libraries like github.com/stripe/stripe-go) are automatically treated as cross-cutting. They appear in the dependency graph but don’t trigger layer violations.
Cross-Cutting Concerns
Some packages (logging, error handling, utilities) don’t belong to any layer. Configure these as cross-cutting concerns to exclude them from violation checks:
[layers]
cross_cutting = ["common/utils/**", "pkg/logger/**", "pkg/errors/**"]
Cross-cutting patterns apply to both source files and import targets. Use ** glob patterns for best results:
cross_cutting = ["**/methods/**", "**/observability/**", "**/uptime/**"]
Cross-cutting components are still tracked in the dependency graph for visualization, but dependencies to/from them don’t count as violations.
Anemic Domain Model Detection
Boundary flags domain entities that have no business methods as potential anemic domain models. This check only applies to components in the domain layer — infrastructure DTOs and data transfer objects in other layers are not flagged.
Custom Layer Patterns
Override the default patterns in .boundary.toml to match your project structure:
[layers]
domain = ["**/core/**", "**/models/**"]
application = ["**/app/**", "**/usecases/**"]
infrastructure = ["**/infra/**", "**/db/**", "**/clients/**"]
presentation = ["**/web/**", "**/grpc/**"]
For monorepos with per-service structures, use layer overrides.