Software architecture is not static—it evolves as systems grow in complexity and scope. But how do we ensure this evolution follows a coherent path rather than dissolving into chaos? The answer lies in proper stratification: organizing our systems into layers with well-defined responsibilities and change frequencies.
Let's explore how stratification provides the foundation for evolutionary architecture, from simple handlers to complex domain driven designs.
Understanding Architectural Stratification
Stratification in software architecture is like geological layers of the Earth. Each layer has distinct characteristics, serves a specific purpose, and changes at different rates. The deepest layers change rarely and provide stability, while surface layers adapt quickly to changing requirements.
The Change Frequency Principle
One of the most powerful insights in stratified architecture is understanding how change frequency varies across layers:
Understanding these different rates of change is crucial for effective architectural design. When we fail to properly stratify our systems, changes in one area can cascade unpredictably across the entire codebase.
The Dependency Rule: The Key to Effective Stratification
At the heart of effective stratification lies the dependency rule: dependencies should only point inward, toward the more stable layers.
This rule, popularized in the Clean Architecture approach, ensures that high-level policies (business rules) don't depend on low-level details (UI, databases, external systems). Instead, low-level components depend on high-level abstractions, creating a system that's resilient to change.
The Inversion of Control Principle
To maintain proper stratification, we often need to invert traditional dependencies:
This inversion creates a system where inner circles (domain) don't know about outer circles actual implementation (infrastructure), only relying on the interface / contract, making the core business logic immune to external changes.
Benefits of Proper Stratification
Stratification isn't just an academic exercise—it delivers tangible benefits:
Isolated Change Impact: Changes in one layer don't cascade throughout the system
Targeted Testing: Each layer can be tested with appropriate strategies
Parallel Development: Teams can work on different layers simultaneously
Evolutionary Growth: The architecture can evolve without complete rewrites
Technology Independence: Core business logic remains stable despite infrastructure changes
NFR Alignment
Proper stratification aligns particularly well with certain non-functional requirements:
Maintainability: ⭐⭐⭐⭐⭐
Extensibility: ⭐⭐⭐⭐⭐
Testability: ⭐⭐⭐⭐⭐
Scalability: ⭐⭐⭐⭐
Performance: ⭐⭐⭐
Stratification particularly excels at supporting maintainability, extensibility, and testability, but can create minor challenges for performance optimization which must cross layers.
Stratification Through the Three Dimensions
Stratification interacts with the three dimensions of system design in specific ways:
Coordination
Stratified architectures naturally support orchestrated coordination, with higher layers coordinating the activities of lower layers. The application layer typically serves as the conductor, organizing the interaction between domain entities and infrastructure services.
Communication
Communication patterns change across strata:
Between UI and application: Often synchronous, RPC-style
Between application and domain: Typically direct method calls
Between domain and infrastructure: Abstracted through interfaces/ports
Consistency
Consistency requirements also vary by layer:
Domain layer: Often requires atomic consistency for business invariants
Application layer: Manages transaction boundaries
Infrastructure layer: May implement eventual consistency strategies
Stratification Across Evolutionary Stages
As systems evolve from simple to complex, stratification takes different forms:
Each generation adds more sophisticated stratification:
Gen 1 (Handlers): Minimal stratification with handlers directly accessing infrastructure
Gen 2 (Controllers): Grouping related operations with some separation
Gen 3 (Services): Clear separation of business logic into service layer
Gen 4 (CQRS): Separate read and write paths with dedicated layers
Gen 5 (DDD): Rich domain model with explicit boundaries and infrastructure abstractions
Common Anti-Patterns and Challenges
The Big Ball of Mud
The absence of stratification leads to the infamous "Big Ball of Mud" pattern:
Without clear stratification, dependencies form a tangled web that's impossible to maintain or evolve.
Leaky Abstractions
When layers leak implementation details upward, we lose the benefits of stratification:
When UI components are aware and influenced by the database model, they become tightly coupled to implementation details, making change difficult.
The Anaemic Domain Model
When business logic is scattered across application services rather than encapsulated in domain entities:
This anti-pattern disperses domain logic, making it hard to maintain consistency and enforce business rules.
Stratification and Domain Relationships
Stratification interacts with Strategic DDD relationships in important ways:
Bounded Contexts: Each context may have its own internal stratification
Anti-corruption Layers: Implement stratification between bounded contexts
Shared Kernel: Requires careful stratification to avoid tight coupling
Upstream/Downstream: Stratification helps manage dependencies between contexts
Practical Implementation Strategies
The Dependency Rule in Practice
Implementing the dependency rule requires:
Clear Module Boundaries: Package/namespace organization that enforces layer separation
Interface Segregation: Tailored interfaces for cross-layer communication
Dependency Injection: Resolving concrete implementations at runtime
Adapters Pattern: Translating between layer-specific models
Testing Strategies Across Strata
Effective testing aligns with stratification:
Domain Layer: Unit tests for business rules and invariants
Application Layer: Integration tests for workflows and use cases
Infrastructure Layer: Mock-based tests or real infrastructure tests
UI Layer: End-to-end tests or component tests
The Conservation of Complexity
Proper stratification doesn't eliminate complexity—it manages it. As explained in the Conservation of Complexity article, complexity can only be redistributed, not destroyed.
In stratified architectures, we deliberately move complexity to where it can be best managed:
Complex business rules go in the domain layer where they're isolated and protected
Integration complexity moves to dedicated infrastructure adapters
Workflow complexity sits in the application layer
Architect's Alert 🚨
Remember that over-stratification can be as problematic as under-stratification. Every layer adds indirection, and too many layers can create a "lasagna architecture" that's difficult to trace through. Start with the minimum necessary layers and add more only when clear boundaries and responsibilities emerge.
The goal is not to create the most layers, but to create the right layers that reflect your domain's natural structure and change patterns.
Evolutionary Transitions
As systems grow, stratification typically evolves:
Initial Stage: Minimal separation with direct access to infrastructure
Growth Stage: Introduction of service layer for business logic
Interchangeable in order:
Maturity Stage: Rich domain model with infrastructure abstractions
Optimization Stage: Specialized read/write paths and bounded contexts
The key is recognizing the right time for transition—usually when the current architecture starts hindering rather than helping development.
Conclusion
Stratification provides the foundation for evolutionary architecture. By understanding change frequency across layers and enforcing the dependency rule, we can create systems that evolve gracefully rather than crumble under their own weight.
As we explore each evolutionary stage in this series, we'll see how stratification manifests in increasingly sophisticated ways, from simple handlers to rich domain models. The same principles apply throughout, but their expression becomes more refined as our architecture matures.
Reflection time:
How does your current architecture manage change frequency across layers?
Which architectural boundaries do you find hardest to maintain?
Have you experienced the pain of poor stratification? What symptoms appeared first?
Which stage of architectural evolution best describes your current systems?