Graduate Program KB

Clean Architecture

  • A good architecture must support:
    • Use cases and operation of the system
    • Maintenance of the system
    • Development of the system
    • Deployment of the system

Use Cases

  • Architecture must support the system's primary use cases
  • The intent and behavior of the system should be clear from the architecture
    • Developers should not have to search for core behaviors
    • Key behaviors should be visible at the top level of the system
      • Components should have descriptive names
      • Components should be positioned appropriately within the architecture

Operation

  • Architecture must support operational requirements like high throughput and fast response times
  • Different architectures suit different needs: parallel services, lightweight threads, isolated processes or monoliths
  • A flexible architecture should not assume specific communication means, allowing easier transitions between threads, processes and services
  • Good architecture leaves options open to adjust for changing operational needs

Development

  • Architecture should support the development environment, allowing team independence
  • Conway's Law: Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.
    • System structure mirrors the organization’s communication
  • Systems with many teams need an architecture that prevents development interference
    • Properly partitioned components enable isolated, independent work across teams

Deployment

  • Architecture should simplify and streamline deployment, aiming for "immediate deployment"
    • Avoids complex configuration scripts, manual setups and tweaks
  • Proper partitioning and isolation support seamless deployment
  • Master components should manage integration and supervision for smooth startup

Leaving Options Open

  • Good architecture balances use cases, operations, team structure and deployment
    • Realistically, achieving this balance is challenging due to unknown use cases, operational constraints and other changing requirements
  • Some architectural principles are relatively inexpensive to implement and balance these concerns
    • Allows us to create well-isolated components to keep options open
  • The goal of a good architecture is to create a flexible system that is easy to change as needed

Decoupling Layers

  • The architect won't know all the use cases, but knows the basic intent of the system
    • Employ Single Responsibility and Common Closure Principle to separate things that change for different reasons and collect things that change for the same reason
  • UI and business rules should be separate to allow independent changes
  • Different types of business rules (application-specific vs. domain-wide) should be isolated since they change at different rates for different reasons
  • Database and technical details should also be independent, as they change for separate reasons
  • After decoupling the system, it will be divided into horizontal layers (UI, app-specific rules, domain rules, database)

Decoupling Use Cases

  • Previously looked at horizontal layers of the system
  • Use cases are also subject to change for different reasons, making them a natural way to divide the system
    • Separated as vertical slices through the horizontal layers
    • Each use case should have its own UI, business rules and database functionality
  • Decoupling allows adding new use cases without affecting existing ones
    • Group UI and database elements by use case to minimise interference across use cases

Decoupling Mode

  • Decoupling use cases supports operational needs, such as high and low throughput separation
    • UI, database and business rules can be deployed on separate servers if decoupled
  • Independent components can become services or microservices, enabling network-based communication.
  • Service-oriented architecture (SoA) is one possible approach, but flexibility in decoupling mode is key
  • A good architecture keeps options open, allowing operational adjustments when needed
    • Decoupling mode is one of those options

Independent Develop-Ability

  • Decoupling reduces team interference, allowing independent work on different components
    • Ex. If business rules don't know much about the UI, then work on the UI cannot really affect a team that focuses on business rules
  • Decoupled use cases enables teams to work on different use cases without interference (ex. addOrder vs deleteOrder)
  • As long as the layers and use cases are decoupled, the arhictecture of the system will support all the organisation's teams

Independent Deployability

  • Decoupling layers and use cases enhances deployment flexibility
    • Enables independent deployment, allowing hot-swapping of components
  • New use cases can be added without impacting existing parts of the system

Duplication

  • Distinguish between true duplication (same change needed in all instances) and accidental duplication (divergent changes over time)
    • Avoid merging use cases with similar structures prematurely as they may evolve differently
    • Unifying them early can cause separation to be challenging later
  • Resist coupling use cases or layers due to apparent duplication in screens, algorithms or database structure
    • Ensure the duplication is real first
    • Maintain separate view models for UI and database layers to ensure proper decoupling, even if they appear similar

Decoupling Modes (Again)

  • Decoupling can occur at various levels: source code, deployment or service level
    • Source level: Components communicate within a single address space using simple function calls (monolithic)
    • Deployment level: Components are deployable units (ex. DLLs, jar files) that can operate independently
    • Service level: Components operate as independent services, communicating via network packets
  • Optimal decoupling mode may change as a project progresses, so the architecture should remain flexible
  • Decoupling at the service-level by default is expensive and encourages coarse-grained decoupling
  • A solution is to push decoupling to the point where a service could be formed, leaving components in the same address space for as long as possible
    • Leaving the option for a service open
  • Good architectures allows transitions between monolith and microservices based on evolving operational needs

Conclusion

  • Changing decoupling mode should be feasible as the system evolves
    • The decoupling mode may need to change over time depending on how the system grows
    • A good architect anticipates and enables flexibility in decoupling