Summary of Chapter 15: What Is Architecture?
1. Defining Architecture
- Architecture is the set of decisions about the design of a system that you want to get right early because they are hard or expensive to change later.
- These decisions often involve trade-offs between various quality attributes such as performance, maintainability, scalability, and flexibility.
2. The Goal of Architecture
- The primary goal of architecture is to minimize the human resources required to build and maintain the system.
- Good architecture ensures that developers can make changes to the system quickly and safely without excessive costs or risks.
3. Separating Policy and Details
- A key architectural concern is to separate policy (high-level business rules) from details (e.g., frameworks, databases, and UI).
- This separation allows policies to remain stable even as details change.
4. The Role of Boundaries
- Boundaries are critical in architecture. They allow different parts of the system to evolve independently.
- By establishing clear boundaries, you isolate high-level use cases from low-level implementation details.
5. Delaying Decisions
- Good architecture delays decisions about details for as long as possible.
- Postponing decisions on technologies (e.g., databases, frameworks) until the last responsible moment allows you to gather more information and make better choices.
6. Avoiding Irreversible Decisions
- Irreversible decisions can lock your system into specific tools or approaches, making future changes difficult and costly.
- Architects should strive to make decisions that are reversible or minimize the impact of changes.
7. Making Systems Easy to Change
- One of the hallmarks of a well-architected system is its ease of change.
- This is achieved by ensuring that business rules are not tightly coupled to external details like frameworks, databases, or tools.
8. The Architect’s Job
- The architect’s job is not to dictate details but to ensure the system’s structure aligns with its goals.
- Architects should focus on creating a system that is:
- Independent of frameworks
- Testable
- Independent of UI
- Independent of databases
- Independent of any external agency
9. The Dependency Rule
- Core business rules should never depend on frameworks, libraries, or other external tools.
- Dependencies should point inward, toward the high-level policies of the system.
What Does “Dependencies Should Point Inward” Mean?
- Core Idea: The most important and stable parts of your application (e.g., business rules, domain logic) should not depend on volatile or implementation-specific details like frameworks, databases, or user interfaces. Instead, these details should depend on the core logic.
- Inward Flow: Dependencies in your system should always point toward the center of your architecture, where the most abstract and high-level policies reside.
The concept of “dependencies should point inward” is central to Clean Architecture. It is part of the Dependency Rule, which states that dependencies in a software system must always flow toward higher-level, more abstract policies, and not toward lower-level, more concrete details. Let me clarify this with an explanation and examples.
What Does “Dependencies Should Point Inward” Mean?
- Core Idea: The most important and stable parts of your application (e.g., business rules, domain logic) should not depend on volatile or implementation-specific details like frameworks, databases, or user interfaces. Instead, these details should depend on the core logic.
- Inward Flow: Dependencies in your system should always point toward the center of your architecture, where the most abstract and high-level policies reside.
Key Layers of Clean Architecture
- Entities (Core Business Rules):
- These are the most abstract and high-level policies in your system.
- Entities represent the essential rules and concepts of your business, independent of any external technology.
- Dependencies never flow out of this layer.
- Use Cases (Application-Specific Business Rules):
- These define how the system behaves in response to user actions.
- Use cases can depend on Entities, but they should not depend on external frameworks, databases, or UI.
- Interface Adapters:
- These adapt the input/output to match the needs of the core logic (e.g., translating database records into domain objects).
- They depend on the Use Cases, not the other way around.
- Frameworks and Drivers:
- This is the outermost layer, containing implementation details like frameworks, databases, and user interfaces.
- These details depend on the inner layers but not vice versa.
Why Should Dependencies Point Inward?
- Stability:
- Core business rules change less frequently than implementation details like frameworks or UI libraries. Keeping dependencies inward ensures that frequent changes in outer layers don’t ripple through the system.
- Testability:
- By isolating business logic from frameworks and databases, you can test your core logic independently, without requiring a database or UI.
- Flexibility:
- If you need to replace a database (e.g., switching from MySQL to MongoDB) or a UI framework (e.g., React to Angular), the impact is limited to the outer layers.
What Are Boundaries in Software Architecture?
Boundaries are conceptual lines that separate different parts of your system. These parts could be:
- Layers (e.g., UI, business logic, data access).
- Components (e.g., microservices or modules).
- Contexts (e.g., different domains in domain-driven design).
The idea is to clearly define what each part is responsible for and ensure that different parts interact only through well-defined interfaces or contracts. This prevents unnecessary dependencies and coupling between parts of your system.
Why Do Boundaries Matter?
1. Separation of Concerns
- Each part of your system should focus on its specific responsibility without being concerned with how other parts work.
- For example:
- Business logic should not worry about how the database stores data.
- UI components should not contain business rules.
- Boundaries ensure each layer or module focuses solely on its responsibility.
2. Flexibility
- By establishing clear boundaries, you can change or replace one part of the system without impacting others.
- For example:
- You can switch from a SQL database to a NoSQL database.
- You can change a UI framework (e.g., from Angular to React).
- Boundaries isolate these changes to their specific modules.
3. Testability
- Boundaries allow you to test parts of the system independently.
- For example:
- You can test business rules without needing a database or UI.
- Mocking dependencies becomes easier because each part interacts through well-defined interfaces.
4. Scalability
- When boundaries are clear, you can scale individual components of your system based on need.
- For instance, you might scale the database layer independently of the business logic layer.
5. Enforcing Rules (Dependency Inversion)
- Boundaries help enforce the Dependency Rule, where higher-level policies (e.g., business rules) are not dependent on lower-level details (e.g., frameworks, databases).
- By making dependencies flow through well-defined interfaces, you ensure that lower-level changes don’t ripple through the system.
What Happens Without Boundaries?
- Tight Coupling:
- If layers or components are tightly coupled, changes in one part (e.g., the database) can cascade through the entire system.
- Rigid Systems:
- Systems become difficult to adapt to new requirements or technologies because every part depends on every other part.
- Difficult Testing:
- Testing individual parts becomes challenging because they depend on external systems (e.g., testing business rules requires a running database).
- Increased Maintenance Cost:
- Over time, the system becomes harder to understand and modify, increasing development and maintenance effort.
Conclusions
- Architecture is about decisions: Focus on the decisions that are difficult or costly to change later.
- Policy over details: Prioritize business rules and delay decisions about implementation details like tools and frameworks.
- Flexibility and maintainability: A well-designed system is easy to modify and adapt over time.
- Boundaries matter: Use boundaries to decouple different parts of the system, enabling them to evolve independently.