How do I model clean architecture with packages?
To model clean architecture with packages, organize code into concentric circles where the outermost layers depend only on the innermost domain core. Ensure that dependencies always point inward, strictly prohibiting outer layers from importing classes from inner layers. This flow prevents business logic from leaking into infrastructure concerns.
Structuring the Domain Layer
Define the Core Boundary
Begin by identifying the most critical business rules that must remain independent of external factors. The domain core is the heart of the system and should never depend on anything else. In UML, this is typically the innermost package in your diagram. It contains entities, value objects, and domain services.
Ensure that classes within this package do not import anything from the application or infrastructure packages. The goal is to create a bubble of pure logic that can run in any environment. This strict isolation allows for easy unit testing and business logic reuse.
Organize Domain Entities
Group related entities into their own sub-packages if the domain is large. Avoid creating a single flat package containing every class. Use the “clean architecture UML packages” approach to group by context rather than type.
Do not place database-specific classes like mappers or repositories inside the domain layer. This package should only contain business rules and the data structures required to enforce them. Keep the boundaries clear to maintain high cohesion.
Implementing the Use Cases Layer
Connect Domain to Application
The use cases layer acts as the bridge between the pure domain and the outside world. In your package diagram, this layer sits immediately outside the domain core. It contains the specific commands and queries required to execute business operations.
Classes in this layer should only depend on the domain layer. If a use case needs data, it calls the domain interface, but it does not need to know how that data is retrieved. This separation ensures that the application flow remains decoupled from the underlying infrastructure.
Define Input and Output Ports
Design input and output interfaces that the use cases rely on. These interfaces are part of the application layer or the domain layer depending on the specific architecture variant. The application layer orchestrates the flow of data into the domain.
When modeling these packages, ensure that the application layer does not know the specifics of the UI or the database. It should only call domain objects and implement the business rules defined by the core. This prevents the business rules from being polluted by technical concerns.
Managing Infrastructure and Frameworks
Isolate Technical Details
The outermost layer handles all the heavy lifting related to technology. This includes database access, HTTP servers, and third-party library integrations. In UML, these packages wrap everything else. They contain the implementation details of the interfaces defined in inner layers.
Infrastructure classes should implement the interfaces defined by the use case or domain layers. They act as adapters that translate external requests into domain language. This ensures that the core logic remains untouched by changes in technology.
Prevent Dependency Cycles
A common error in modeling is allowing the infrastructure layer to depend on the domain or application layer for its own configuration. Avoid this by using dependency injection frameworks. The infrastructure layer should receive dependencies from the outside.
Ensure that no outer package imports a class from an inner package. This rule is the cornerstone of successful clean architecture. Violating this rule creates a circular dependency that makes refactoring impossible. Your package dependencies must flow strictly from the outside in.
Configuring the Presentation Layer
Design Input Interfaces
The presentation layer is the user-facing component of the application. It handles HTTP requests, GUI events, or CLI inputs. In the UML diagram, this is the outermost ring, depending on the application layer.
The presentation layer should not contain business logic. It should only format data for the user or parse user input before sending it to the use cases. It acts as a thin wrapper around the application logic.
Render Output Results
Render the results returned by the use cases into a format suitable for the user. This could be HTML, JSON, or a GUI widget. Ensure that the presentation layer does not depend on the infrastructure layer directly.
Instead, it relies on the application layer to provide the necessary data. This separation allows you to change the UI framework without affecting the business rules. Clean architecture UML packages ensure that these layers remain independent.
Establishing Dependency Rules
Enforce One-Way Dependencies
Set up a strict rule that dependencies point only inward. An outer package can know about an inner package, but an inner package cannot know about an outer package. This directionality is essential for maintainability.
Automate this enforcement in your build process if possible. Tools can detect violations where an inner package imports an outer one. Early detection prevents architectural drift over time.
Handle Interface Implementation
Use interfaces to define the contracts between layers. The outer layers implement the interfaces defined in the inner layers. This allows the inner layers to remain agnostic of the implementation details.
When drawing your UML, show the interface as a distinct element within the inner package. The outer package then realizes this interface. This visual clarity helps teams understand the direction of the dependency flow.
Addressing Common Packaging Challenges
Handling Circular Dependencies
Occasionally, you may find yourself needing to pass data back and forth between layers. This often leads to circular dependencies. The solution is to extract the common data into a shared interface or DTO package.
Move the data transfer objects to a package that both sides can use. This package should be neutral and contain only data structures. It does not contain logic, so it does not violate the dependency rules.
Managing Test Infrastructure
Testing often requires access to infrastructure components. Do not embed test infrastructure into your main packages. Instead, create a separate test package that sits outside the main production structure.
The test package can depend on the production packages to execute tests. It can also depend on test-specific infrastructure like mock databases. Keep this separation distinct to maintain the integrity of the production architecture.
Visualizing Dependencies in UML
Diagram Orientation
Draw your package diagram as a set of nested rectangles. The domain core is the smallest rectangle inside. Surround it with the application layer, then the infrastructure layer, and finally the presentation layer.
Use arrows to show dependencies. Ensure all arrows point toward the center. A single arrow pointing outward indicates a violation of the clean architecture rules. Verify this visually before committing to the design.
Labeling and Grouping
Label each package clearly to indicate its responsibility. Use names like “Domain”, “UseCases”, “Infrastructure”, and “Presentation”. Group related classes within these packages to improve readability.
Do not overcrowd the diagram. If a package contains too many classes, consider splitting it into sub-packages. This improves clarity and helps developers navigate the codebase more efficiently. Adhere to the principles of clean architecture UML packages for the best results.
Key Takeaways
- Dependencies must flow strictly from the outer layers inward to the domain core.
- The domain core should not depend on any other layer or package.
- Infrastructure and presentation layers implement interfaces defined by the application and domain layers.
- Use nested UML packages to visualize the concentric dependency structure.
- Avoid circular dependencies by using shared data objects or interfaces for cross-layer communication.