Common Pitfalls
Reading time: ~12 minutes
Introduction​
Even with a well-designed architecture like Prism, implementers often encounter challenges and pitfalls during development. This document highlights common mistakes, misconceptions, and challenges that teams face when implementing Prism Architecture, along with practical strategies to avoid or overcome them.
Understanding these pitfalls helps teams navigate the implementation process more effectively, reducing rework and ensuring the architectural benefits are fully realized. By being aware of these common issues in advance, you can implement Prism Architecture more successfully and with fewer detours.
Layer Boundary Violations​
One of the most common pitfalls occurs when the carefully designed layer boundaries of Prism Architecture are compromised.
Accessing Layers That Should Be Inaccessible​
Pitfall: Directly accessing layers that should not be accessible according to the dependency rules.
Symptoms:
- Import statements that cross inappropriate layer boundaries
- Tight coupling between layers that should be independent
- Difficulty swapping implementations of lower layers
Prevention:
- Use compiler enforcement through project structure when possible
- Implement layer boundary tests that fail if dependencies are violated
- Conduct regular architecture reviews with focus on layer dependencies
- Use dependency injection to make dependencies explicit and controlled
Circular Dependencies Between Layers​
Pitfall: Creating circular dependencies between layers, which violates the unidirectional dependency rule.
Symptoms:
- Compilation issues requiring workarounds
- Difficulty understanding how changes propagate through the system
- Increased coupling between layers
Prevention:
- Define clear interfaces in the Core Layer
- Ensure unidirectional dependencies through project structure
- When tempted to create a circular dependency, refactor to use Core interfaces instead
Communication Pattern Misuse​
Proper use of Intent and State patterns is essential for effective layer communication in Prism Architecture.
Creating Intent and State Outside Common Layer​
Pitfall: Defining Intent and State types directly in implementation layers rather than in the Common Layer.
Symptoms:
- Inconsistent Intent and State handling across layers
- Type compatibility issues when passing objects between layers
- Difficulty tracing communication flow
- Duplicate definitions for similar purposes
Prevention:
- Define all Intent and State base types in the Common Layer
- Organize in a clear folder structure by purpose and usage
- Create actual instances in the originating layers at runtime
- Implement architecture verification tests checking for proper placement
Not Following Immutability Principle​
Pitfall: Creating mutable Intent and State objects, leading to unexpected state changes during communication.
Symptoms:
- Unpredictable application behavior
- Data inconsistencies between layers
- Hard-to-reproduce bugs
- Thread safety issues
Prevention:
- Design all Intent and State classes as immutable
- Create new instances instead of modifying existing ones
- Use immutable collections for lists and maps
- Implement lint rules or static analysis to enforce immutability
Excessive Transformation Between Layers​
Pitfall: Completely transforming data at each layer boundary, creating unnecessary mapping overhead.
Symptoms:
- Excessive boilerplate code for mapping
- Performance bottlenecks from unnecessary transformations
- Code duplication across mappers
- Increased maintenance burden
Prevention:
- Follow the package delivery model where objects flow through the architecture
- Extract only what's needed at each layer rather than complete transformation
- Use selective mapping only when actually required
- Include original entities in State objects when appropriate
Architectural Over-Engineering​
While proper architecture is important, excessive abstraction can be counterproductive.
Excessive Layering​
Pitfall: Creating too many layers or excessive abstractions that add complexity without benefit.
Symptoms:
- Simple features require changes across many files
- Developers struggling to understand where functionality should go
- Delays in feature development due to architectural overhead
- "Architecture for architecture's sake" mindset
Prevention:
- Start with essential layers and add complexity only as needed
- Regularly evaluate if abstractions are providing tangible benefits
- Be guided by actual requirements rather than theoretical purity
- Consider using Prism Lite for smaller applications
Premature Optimization​
Pitfall: Over-optimizing architectural components before understanding actual performance needs.
Symptoms:
- Complex caching mechanisms added before profiling
- Custom collection types optimized prematurely
- Performance considerations overriding clean design
- Excess time spent optimizing non-critical paths
Prevention:
- Focus on clean, maintainable design first
- Measure performance before optimizing
- Optimize only after identifying actual bottlenecks
- Keep optimizations focused on performance-critical areas
Domain Model Anti-Patterns​
The Core and Domain layers form the foundation of Prism Architecture and require special attention.
Anemic Domain Model​
Pitfall: Creating entity classes that are little more than data containers without behavior.
Symptoms:
- Entities with only getters and setters
- All business logic in services rather than entities
- Entities being treated as simple data containers
- Services that work exclusively with one entity type
Prevention:
- Put entity-specific behavior in entities
- Design rich domain models with business methods
- Use Value Objects for meaningful domain concepts
- Reserve Domain Services for operations that span multiple entities
Missing Domain Events​
Pitfall: Failing to use Domain Events for significant domain changes, leading to tight coupling.
Symptoms:
- Direct Service-to-Service calls for notification
- Orchestration Layer carrying too much responsibility
- Difficulty implementing reactive behavior
- Cross-domain dependencies
Prevention:
- Identify significant domain changes that should generate events
- Design explicit Domain Event types
- Implement event publishing and subscription mechanisms
- Use events to communicate changes across domain boundaries
Infrastructure Layer Issues​
The Infrastructure Layer presents its own unique challenges due to its connection with external systems.
Leaking Infrastructure Concerns​
Pitfall: Allowing infrastructure details to leak into higher layers.
Symptoms:
- Domain models with persistence annotations
- Database-specific exceptions propagating through layers
- HTTP status codes used in domain logic
- References to infrastructure frameworks in domain code
Prevention:
- Define clean interfaces in the Core Layer
- Translate infrastructure-specific errors to domain errors
- Keep infrastructure details contained within Infrastructure Layer
- Use Data Transfer Objects for external communication
Ignoring Repository Interface Contracts​
Pitfall: Implementing repositories that don't adhere to their interface contracts.
Symptoms:
- Repositories returning different results than specified
- Side effects not documented in interfaces
- Inconsistent exception handling
- Missing transaction management
Prevention:
- Document repository interface contracts clearly
- Implement comprehensive tests for repository implementations
- Use integration tests to verify conformance to contracts
- Conduct thorough code reviews for repository implementations
Presentation Layer Mistakes​
The Presentation Layer is often the most visible part of the application and presents its own challenges.
Mixing UI and Domain Logic​
Pitfall: Embedding domain logic directly in presentation components.
Symptoms:
- Business rules implemented in presenters
- Presenters calling repositories directly
- Domain calculations in UI components
- Duplicate domain logic across multiple presenters
Prevention:
- Keep presenters focused on UI logic only
- Delegate business operations to the Orchestration Layer
- Create clear Use Cases for domain operations
- Test presenters in isolation from domain logic
Not Following Presenter Hierarchy​
Pitfall: Creating a flat presenter structure instead of the recommended hierarchical approach.
Symptoms:
- Direct dependency between unrelated presenters
- Top-Level Presenters containing too much granular logic
- Component Presenters calling Orchestration Layer directly
- Unclear presenter responsibilities
Prevention:
- Follow the three-tier presenter hierarchy (Top, Mid, Component)
- Respect the responsibilities of each level
- Ensure proper communication flow between levels
- Review presenter design for adherence to hierarchy
Ignoring UI State Immutability​
Pitfall: Using mutable UI state, leading to unpredictable UI behavior.
Symptoms:
- UI updates at unexpected times
- State inconsistencies between components
- Hard-to-reproduce UI bugs
- Thread safety issues on UI thread
Prevention:
- Design UI state as immutable
- Create new state instances for changes
- Use proper state propagation mechanisms
- Test state transitions explicitly
Cross-Cutting Concerns​
Some pitfalls span multiple architectural layers and require holistic solutions.
Improper Error Handling​
Pitfall: Inconsistent or improper error handling across layers.
Symptoms:
- Technical errors exposed to users
- Inappropriate error transformation between layers
- Missing error handling in key components
- Inconsistent error response formats
Prevention:
- Design a consistent error handling strategy
- Transform errors appropriately at layer boundaries
- Use domain-specific error types for business errors
- Include error information in State objects when appropriate
Ignoring Testability​
Pitfall: Implementing components in ways that make testing difficult or impossible.
Symptoms:
- Components with hard-coded dependencies
- Logic that relies on global state
- Time-dependent behavior without test hooks
- UI components tightly coupled to business logic
Prevention:
- Design for testability from the start
- Use dependency injection for all dependencies
- Provide test hooks for time, randomness, and other external factors
- Separate concerns to allow isolated testing
Inconsistent Naming Conventions​
Pitfall: Using inconsistent naming across layers and components.
Symptoms:
- Different naming styles across layers
- Inconsistent naming for similar concepts
- Confusion about component purpose based on name
- Difficulty finding related components
Prevention:
- Establish clear naming conventions
- Use consistent prefixes and suffixes
- Follow the established patterns in the Naming Conventions document
- Conduct naming reviews during code reviews
Advanced Pitfalls​
These more subtle pitfalls tend to emerge in larger projects or as applications evolve.
Overly Rigid Implementation​
Pitfall: Implementing Prism Architecture too rigidly, focusing on rules over practical needs.
Symptoms:
- Architectural purity prioritized over productivity
- Simple features requiring complex implementations
- Developer frustration with architectural constraints
- Slow development velocity due to architectural overhead
Prevention:
- Remember that architecture serves the application, not vice versa
- Be pragmatic about applying architectural patterns
- Balance architectural integrity with development efficiency
- Adapt patterns to fit your specific context
Feature-Driven vs. Layer-Driven Organization​
Pitfall: Organizing code purely by layer rather than balancing with feature organization.
Symptoms:
- Difficulty finding all files related to a feature
- Changes to a feature requiring updates across many folders
- Team organization not aligning with code organization
- Challenges when assigning feature ownership
Prevention:
- Consider feature modules for larger applications
- Balance layer-based and feature-based organization
- Align code organization with team structure when appropriate
- Use both approaches where they make the most sense
Misaligned Team Structure​
Pitfall: Team structure that doesn't align with architectural boundaries.
Symptoms:
- Frequent merge conflicts between teams
- Architectural boundaries being eroded
- Inconsistent implementation patterns
- Slow decision-making across architectural boundaries
Prevention:
- Align team structure with architectural boundaries when possible
- Create clear ownership for architectural components
- Establish cross-team architecture governance
- Provide architecture guidance to all teams
Implementation Checklist​
To avoid common pitfalls, use this checklist during your Prism Architecture implementation:
Layer Setup​
- All layers have clear boundaries and responsibilities
- Dependency rules are enforced (compile-time or convention)
- Common Layer contains all Intent and State definitions
- Core Layer is free from external dependencies
Communication​
- Intent and State types follow the organized structure
- All communication objects are immutable
- Package delivery model is used consistently
- Clear flow of data and requests between layers
Domain Model​
- Entities have behavior, not just data
- Value Objects are used for domain concepts
- Domain Services handle cross-entity operations
- Domain Events are used for significant occurrences
Testing​
- Each layer has appropriate tests
- Components are designed for testability
- Integration tests verify layer interactions
- Architecture verification tests confirm boundaries
General​
- Naming conventions are followed consistently
- Documentation explains architectural decisions
- Team understands architectural principles
- Regular architecture reviews are conducted
Conclusion​
Implementing Prism Architecture effectively requires awareness of these common pitfalls and proactive strategies to avoid them. By understanding these challenges in advance, teams can navigate the implementation process more smoothly and realize the full benefits of the architecture.
Remember that Prism Architecture is designed to be practical and adaptable. The goal is not perfect architectural purity, but rather a clean, maintainable system that enables effective development. Focus on applying the core principles correctly while adapting the details to your specific context.
When you encounter issues, refer back to this guide and the fundamental principles of Prism Architecture. Often, the solution involves returning to these core concepts and applying them with appropriate flexibility for your situation.