Skill: Layered Architecture Design
This skill explains how to design software using a layered architecture, a foundational pattern for building robust, modular, and maintainable systems. The core principle is to separate code into distinct layers, each with a specific responsibility. Dependencies are strict and acyclic: higher-level layers can depend on lower-level layers, but never the other way around.
This approach is critical for managing complexity, enabling parallel development, and improving testability.
1. The Core Problem: Circular Dependencies
In complex systems, components often depend on each other. A UserService might need a Database, and the Database might need a Logger. If the Logger then needs to know about the current User to add context to logs, you create a circular dependency: UserService → Database → Logger → UserService.
This "bootstrap circularity" makes the system:
- •Hard to understand: You can't analyze one part in isolation.
- •Hard to test: You can't mock one component without pulling in the entire cycle.
- •Hard to maintain: A change in one component can have ripple effects across the entire system.
2. The Solution: Stratification into Layers
The solution is to break the cycle by stratifying the system into ordered layers. Each layer can only depend on layers below it.
A common pattern involves four layers:
╔════════════════════════════════════════════════════════════╗ ║ LAYER 3: USER INTERFACE / API ║ ║ (e.g., Web Controllers, CLI Commands, UI Components) ║ ║ - Depends on Application Layer - ║ ╠════════════════════════════════════════════════════════════╣ ║ LAYER 2: APPLICATION LOGIC ║ ║ (e.g., Use Cases, Services, Business Workflows) ║ ║ - Depends on Domain/Kernel Layer - ║ ╠════════════════════════════════════════════════════════════╣ ║ LAYER 1: DOMAIN / KERNEL ║ ║ (e.g., Core Data Structures, Business Objects, ║ ║ Interfaces for external services like repositories) ║ ║ - Has zero external dependencies - ║ ╠════════════════════════════════════════════════════════════╣ ║ LAYER 0: INFRASTRUCTURE ║ ║ (e.g., Database implementations, 3rd-party API clients)║ ║ - Implements interfaces from the Domain Layer - ║ ╚════════════════════════════════════════════════════════════╝
The Golden Rule: Dependencies only point downwards. Layer N can use Layer N-1, but N-1 knows nothing about N. The Infrastructure layer is special; it implements interfaces defined in the Domain layer (Dependency Inversion Principle).
3. Case Study: mathsets-kt Architecture
The mathsets-kt project provides an excellent, real-world example of solving a fundamental circular dependency in mathematics through layered architecture.
- •
The Circularity: To build numbers, you need sets. To compute with sets, you need numbers (for indexing, counting, etc.).
- •
The Layered Solution:
- •
Layer 0: Kernel - Provides raw, computational primitives (like
BigIntegerwrappers). It has no axiomatic pretensions. It exists so the machine can perform basic operations. It has no dependencies on other project modules. - •
Layer 1: Logic - Defines the rules and specifications of the system (e.g., the
PeanoAxiomsinterface). It describes what a number system should be, but not how it is implemented. It depends only on the Kernel. - •
Layer 2: Set Theory - Provides the main tools for the domain, like
MathSet<T>,Relation, andFunction. It uses the Kernel for computation and the Logic layer to define its properties. - •
Layer 3: Construction - Uses the tools from the Set Theory layer to formally construct the number systems (e.g., VonNeumann ordinals). This layer proves that the efficient primitives in the Kernel are consistent with axiomatic foundations. It depends on all lower layers.
- •
Dependency Graph (mathsets-kt)
Kernel/ (L0)
╱ ╲
╱ ╲
Logic/ (L1) │
│ │
│ Set/ (L2)
│ ╱ ╲
│ ╱ ╲
Relation/ (L2) Function/ (L2)
╲ ╱
╲ ╱
Construction/ (L3)
This strict, acyclic graph ensures that the foundational kernel can be built and tested without any knowledge of the abstract mathematical concepts it will later be used to construct.
4. How to Implement a Layered Architecture
- •Identify Your Layers: Start by categorizing your code. What is core domain logic? What is application-specific workflow? What is UI? What is an external dependency?
- •Define Module Boundaries: In a modern project, each layer should correspond to a separate module, package, or directory (e.g.,
kernel/,logic/,set/inmathsets-kt). - •Enforce Dependency Rules: Use your build system (e.g., Gradle, Maven,
package.json) to enforce the dependency graph. ModuleAshould not have a dependency on moduleBif the architecture forbids it. This provides compile-time checks. - •Use Dependency Inversion: When a lower layer needs to "call" an upper layer, it should do so through an interface.
- •Example: The
Applicationlayer (L2) may need to save data. It should call aRepositoryinterface defined in theDomainlayer (L1). The concreteDatabaseRepositoryimplementation lives in theInfrastructurelayer (L0) and implements that interface. This inverts the dependency flow and keeps the domain pure.
- •Example: The
References
- •Source of this Skill:
docs/ARCHITECTURE.mdfrom themathsets-ktproject. - •General Concept: P of EAA: Service Layer by Martin Fowler.
- •Related Pattern: Hexagonal Architecture (Ports and Adapters)
- •Related Pattern: The Clean Architecture by Robert C. Martin.