Clean Architecture

  1. What is Design and Architecture?
    • no difference between design and architecture
    • low-level details and high-level structures are all part of the same whole
    • The goal of software architecture is to minimize the human resources required to build and maintain the required system
    • Bad architecture can result in increasing costs per new feature
    • making a mess is always slower than staying clean
    • can start from scratch, but overconfidence will drive the redesign into the same mess as the original project
    • every organization should take the quality of its software architecture seriously
  2. A Tale of Two Values
    • every software system provides behavior and structure
    • behavior: programmers are hired to make machines behavior in a way to make or save money
    • structure: software should be simple and easy to change
    • if a program is impossible to change it will become useless when the requirements change
    • if a program does not work but is easy to change, it’s easy to fix
    • architecture is important but never particularly urgent
    • fight for the architecture
    • you are a stakeholder and must do what is best for the company
  3. Paradigm Overview
    • structured programming imposes discipline on direct transfer of control
    • object-oriented programming imposes discipline on direct transfer of control
    • functional programming imposes discipline upon assignment
  4. Structured Programming
    • structured programming allows modules to be recursively decomposed into provable units
    • decomposed functions can be represented using the restricted control structures of structured programming
    • use tests to prove those small provable functions
  5. Object-Oriented Programming
    • what is OO? Is it encapsulation, inheritance, and polymorphism?
    • encapsulation
      • many OO languages have little or no enforced encapsulation
    • inheritance?
      • inheritance is just the redeclaration of a group of variables and functions within an enclosing scope
      • C programmers could do before OO languages
      • OO did not give us something new but it did make it more significantly more convenient
    • polymorphism
      • polymorphism is an application of pointers to functions
      • OO made it much safer and more convenient
      • pointers to functions are dangerous and bugs are devilishly hard to track down
      • OO languages eliminated conventions and the dangers
      • OO allows the plugin architecture to be used anywhere, for anything
    • dependency inversion
      • any source code dependency can be inverted
      • absolute control over the direction of all source code dependencies
      • e.g. plugin the UI and database to the business rules
      • UI and the database can be compiled into separate components
      • the component containing the business rules will not depend on the components containing the UI and the database
      • results in independent deployability and developability
    • OO is the ability, through the use of polymorphism, to gain absolute control over every source code dependency in the system
  6. Functional Programming
    • variables in functional languages do not vary
    • eliminates race conditions, deadlocks, concurrent update problems
    • one pattern is to separate the application into mutable and immutable components
    • use transactional memory to protect mutable variables from concurrent updates and race conditions
    • push as much processing as possible into immutable components
    • event sourcing is strategy for tracking all transactions and getting to the state by applying all transactions
    • with enough storage and processor power, can make the application entirely immutable
  7. SRP: The Single Responsibility Principle
    • A module should have one, and only one, reason to change
    • “reason” being users or stakeholders
    • accidental duplication: separate the code that different actors depend on
    • merges: multiple people changing the same file
    • solutions
      • can separate the data from the functions
      • can keep the most important method in the original class and use that class as a facade for the lesser functions
    • SRP is about functions and classes but it reappears as the “Common Closure Principle” for components and the “Axis of Change” for the architectural level
  8. OCP: The Open-Closed Principle
    • A software artifact should be open for extension but closed for modification
    • An interactor that deals with interfaces will not be impacted by changes to the implementations
    • a hierarchy of protection based on the notion of “level”
    • Interactors are the highest-level concept so most protected
    • Interfaces also provide information hiding
    • the goal of the system is to make the system easy to extend without incurring high impact of change
    • the goal is accomplished by arranging components into a dependency hierarchy that protects higher-level components from changes in lower-level components
  9. LSP: The Liskov Substitution Principle
    • when substituting based on subtypes the behavior shouldn’t change
    • violation when the subtype cannot conform
    • e.g. if a square is a subtype of a rectangle, setWidth and setHeight may have unexpected behavior
    • in early days LSP guided the use of inheritance
    • over the years it has morphed into a broader principle that pertains to interfaces and implementations
    • a simple violation of substitutability can cause a system’s architecture to be polluted with a significant amount of extra mechanisms
  10. ISP: The Interface Segregation Principle
    • it has harmful to depend on modules that contain more than you need
    • depending on something that carries baggage that you don’t need can cause you troubles that you didn’t expect
  11. DIP: The Dependency Inversion Principle
    • in the most flexible systems, source code dependencies refer only to abstractions
    • e.g. in Java, the use, import, and include statements should refer only to source modules containing interfaces and abstract classes
    • not realistic to avoid all concrete elements, but strive to avoid depending on volatile ones
    • interfaces are less volatile than the concrete implementation
    • good software designers work hard to reduce the volatility of interfaces
    • don’t refer to volatile concrete classes
      • puts severe constraints on the creation of objects and generally enforces the use of Abstract factories
    • don’t derive from volatile concrete classes
      • in static languages, inheritance is the strongest most rigid of all source code relationships
      • use with great care
    • don’t override concrete functions
      • concrete functions often require source code dependencies
      • when you override those functions you inherit those dependencies
      • to manage multiple dependencies, you should make the functions abstract and create multiple implementations
    • never mention the name  of anything concrete and volatile
    • Use abstract factories to create objects
    • e.g. An Application may deal with a ServiceFactory (Interface) to get a Service (Interface) where the ServiceFactory is backed by a ServiceFactoryImpl and the Service is backed by a ConcreteImpl
    • DIP violations cannot be entirely removed, but they are gathered into a small number of concrete components e.g. main
    • e.g. the main function would instantiate the ServiceFactoryImpl and place that instance in the global variable of type ServiceFactory
    • The way dependencies point towards more abstract entities is the Dependency Rule
  12. Components
    • components are the unit of deployment
    • can be linked together into a single executable or deployed separately
    • independently developable
    • initially, programmers included source code of the library functions with their application code, but devices were slow and memory was expensive
    • to shorten, programmers compiled the function library separately and loaded the binary at a known address
    • they would load the binary function library and then the application, but started running out of memory
    • the compiler was changed to output binary code that could be relocated in memory by a smart loader resulting in the linking loader
    • eventually, linking loaders were to slow to tolerate so the linking and loading were separated into two phases
    • eventually, Moore’s law won out and linking time started shrinking fast
    • linking became very fast so component plugin architecture was born
  13. Component Cohesion
    • the reuse/release equivalence principle
      • the granule of reuse is the granule of release
      • people who want to reuse software won’t unless components are tracked through a release process and given release numbers
      • classes and modules in a component must belong to a cohesive group
    • the common closure principle
      • gather into components classes that change for the same reasons and at the same time
      • single responsibility principle for components
      • for most applications, maintainability is more important than reusability
      • combining components that need to change together minimizes the workload related to releasing, revalidating, and redeploying the software
      • associated with open-closed principle because 100% closure is not attainable so keep component closed to the same type of changes
    • the common reuse principle
      • don’t force users of a component to depend on things they don’t need
      • more about what classes to keep out of a component
      • when we depend on a component try to make sure we depend on every class in that component
      • generic version of ISP, do not depend on classes we don’t use
    • need to balance between all three principles
    • in early stage, you may lean more towards CCP because developability is more important than reuse
    • may shift to care more about reuse as the project matures
  14. Component Coupling
    • allow no cycles in the component dependency graph
    • weekly builds can slowly devolve as project size grows
    • partition development environment into releasable components
    • allows teams to handle integration in small increments on their own schedule
    • cycles result in one large component
    • break the cycle by applying DIP or creating a new component that both components depend on
    • components cannot be designed from top-down it must evolve as the system grows and changes
    • component dependency diagrams are a map to the buildability and maintainability of the application
    • one of the overriding concerns is the isolation of volatility
    • the stable dependency principle
      • depend in the direction of stability
      • a component with a lot of incoming dependencies is very stable because it requires a great deal of work to reconcile any changes
      • a component that has no dependencies is unstable
    • can measure stability by counting the number of dependencies that fan in vs fan-out
    • not all components should be stable
    • if a component depends on a less stable component, create an interface and depend on that instead
    • a component should be as abstract as it is stable
    • unstable components should be concrete since instability allows it to be easily changed
  15. What is Architecture?
    • architecture is the shape given to that system by those who build it
    • purpose of the shape is to leave as many options open as possible, for as long as possible
    • troubles do not lie in operations, but on the deployment, maintenance, and ongoing development
    • the primary purpose of architecture is to support the life cycle of the system
    • development
      • should make the system easy to develop
      • cannot make progress if you have multiple developers if not broken into well-defined components
      • if no other factors are considered, the system will likely evolve into one component per team which is likely not the best for deployment, operations, and maintainability
    • deployment
      • the higher the cost of deployment the less useful of a system
      • should easily be able to deploy with a single action
      • e.g. microservices may be easy to develop, but deployment may be difficult
    • operation
      • a good architecture communicates the operational needs of the system
      • architecture should reveal the operation
    • maintenance
      • the most costly aspect
      • the primary cost is spelunking and risk
      • spelunking is the cost of digging through the existing software and trying to determine the best place and strategy to add a new feature or repair a defect
      • while making changes, the likelihood of creating defects is always there, adding to the cost of risk
      • carefully thought-through architecture mitigates these costs
    • keep options open
      • all systems decomposed into policy and details
      • the goal of the architect is to create a shape for the system that recognizes policy as the most essential element of the system while making the details irrelevant to that policy
      • delay and defer detail decisions
      • the longer you wait, the more time you have to make the proper ones
      • even if the detail decision has been made, a good architect pretends like it hasn’t been
  16. Independence
    • use cases
      • the system must support the intent of the system
      • the most important thing a good architecture can do to support behavior is to clarify and expose that behavior so that the intent of the system is visible at the architectural level
    • maintenance
      • architecture must support all kinds of throughput and response time based on the use case
      • may involve many different processing strategies e.g. multiple services in parallel, lightweight threads, single process, etc
      • leave these decisions open so you can transition through threads, processes, and services as the operational needs of the system change over time
    • development
      • conway’s law: the design will be a copy of the organization’s communication structure
      • a system that must be developed by many teams must have an architecture that facilitates independent actions by those teams
    • deployment
      • good architecture doesn’t rely on multiple configuration scripts and property file tweaks
    • most of the time we don’t know all the use cases and even if we did, they will change
    • decoupling layers: separating business rules, UI, database, etc
    • the use cases themselves can change (vertical)
    • separating to the service level is an option you should also leave open
    • duplication
      • accidental duplication is when two seeming duplication sections can change at different rates and for different reasons
      • if duplication is incorrect, you will make the wrong abstraction with a new dependency for both of those components
    • my perference is to push the decoupling point to where a service could be formed and leave option open for a servie
    • a good architecture allows for the reversing of microservices back into monolith if separate services no longer required
  17. Boundaries: Drawing Lines
    • don’t pollute core business logic
    • draw lines between things that matter and things that don’t
    • e.g. the database is just a tool to store and fetch data
    • the business rules don’t care about what kind of database we use
    • you business logic shouldn’t even be aware of the DI framework you’re using
    • business rules use plugins that aren’t related to the core business
  18. Boundary Anatomy
    • boundaries in monolith is just function call, very fast
    • threads is just a way to organize the schedule and order or execution, can be contained within a component or spread across many
    • local processes are stronger boundaries and run on separate address spaces
      • often communciate with each other using sockets, mailboxes, or queues
      • local process is like an uber component
    • strongest boundary is a service
      • assumes all communications take place over the network
      • must deal with high latency
      • lower-level services should plug in to higher-level services
      • the source code of higher-level services must not contain any specific knowledge of any lower-level services
  19. Policy and Level
    • policies that manage I/O are the lowest level policies in a system
    • high level policies tend to change less frequently and for more important reasons than lower-level policies
    • keep policies separate with all source code dependencies pointing in the direction of the higher-level policies reduces impact of change
  20. Business Rules
    • an entity is an object that embodies critical business rules and operates on critical business data
    • class stands alone unsullied with concerns about databases, user interfaces, or third party frameworks
    • not all business rules are as pure as entities since some only make sense as part of an automated system
    • may have application-specific business rules
    • use cases describe the application-specific rules that govern the interaction between the users and the Entities
    • Entities have no knowledge of the use cases that control them
    • use cases accept simple request data structures and returns simple response data structures
  21. Screaming Architecture
    • a good architecture allows decisions about frameworks, databases, webservers, and other environmental issues to be deferred
    • should be easy to change your mind
    • the web is an IO device
    • for frameworks develop a strategy so it doesn’t take over your architecture
    • should be able to unit test use cases without frameworks in place
    • the architecture should tell readers about the system not the details
  22. The Clean Architecture
    • hexagonal architecture, DCI, BCE have many things in common
      • independent of framework
      • testable
      • independent of UI
      • independent of the database
      • independent of any external agency
    • dependency rule: source code dependencies must point only inward towards higher-level policies
    • no operational changes to an application should affect the entity level
    • software in the use cases layer contain application-specific business rules and shouldn’t affect entities nor should be affected by UI, db, frameworks
    • software in the interface adapters layers converts data format
      • conver to form most convenient for entities and use case
      • no code inward of circle should know anything about the database
    • data that crosses boundarie consist of simple data structures not entities or database rows
      • always pass data in the form that is most convenient for the inner circle
  23. Presenters and Humble Objects
    • humble object
      • split behaviors into two modules
      • one of those modules is humble which contains the hard to test code
      • the other contains all the testable behaviors
      • e.g. the Presenter and the View
    • the view is humble and hard to test, but doesn’t process data
    • the presenter accepts data and formats it for the view to show on the screen
    • database gateways
      • between use case interactors and the database
      • interface contains methods for every create, read, update, or delete operation
      • use case layer doesn’t know SQL
      • the gateway implementations are humble because simply use SQL
      • interactors encapsulate application-specific business rules
    • ORMs should be data mappers because they return data variables not objects
      • should exist at the data layer
      • ORMS are another form of humble object boundary
    • service listeners
      • application will load data into simple data structures
      • then pass those across the boundary to modules that properly format the data and send it to external services
  24. Partial Boundaries
    • fully-fledged boundaries are expensive
    • do all the work to create independent components but keep them together
    • full-fledged boundaries use reciprocal boundary interfaces
    • simpler structure only has one way (strategy pattern)
    • e.g. Client uses a serviceBoundary interface
    • boundary can be defined by facade class
    • client will have transitive dependencies to all service classes
  25. Layers and Boundaries
    • architectural boundaries are expensive need to know when to use them
    • if ignored expensive to add later too
    • you must guess intelligently
    • must constantly make the decision as the system evolves
    • implement the boundaries right at the inflection point where the cost of implementating becomes less than the cost of ignoring
  26. The Main Component
    • main component is the ultimate detail or the lowest level policy
    • creates all the factories, strategies, and other global facilities
    • DI should take place in the main component
    • think of main as a plugin to the application
  27. Services: Great and Small
    • services don’t define boundaries, the separation between high-level policy from low-level detail does
    • services are strongly coupled by the data they share
    • services cannot always be independently developed, deployed, and operated
    • services can be designed using SOLID principles and given a component structure
    • services must be designed with internal component architectures that follow the dependency rule
  28. The Test Boundary
    • tests are part of the system
    • tests are very detailed and concrete and should depend inward toward the code (dependency rule)
    • should be independently deployable
    • tests that are not well integrated into the design of the system tend to be fragile
    • if tests are strongly coupled to the system must change along with the system
    • design for testability
    • don’t depend on volatile things e.g. GUI
    • create a specific API that the tests can ues to verify all the business rules
    • this api is a super set of the suite of interactors and interface adaptors used by the user interface
    • role of the testing api is to hide the structure of the application from the tests
    • decouples the tests from the application
    • structural coupling is strongest and most insidious forms of test coupling
    • allows production code to be refactored and evolved in ways that don’t affect the tests
  29. Clean Embedded Architecture
    • don’t let all code become firmware
    • program to interfaces and substituability
  30. The Database Is a Detail
    • your use case should not care if database is relational, etc
    • databases are prevalent because of disks
    • disk is being replaced by RAM and once they are will likely no longer keep in tables and directory structures
    • will organize into linked lists, trees, hash tabes, etc
    • database performance is a concern that can be entirely encapsulated and separated from the business rules
    • the organizational structure of data, the data model, is architecturally significant but technologies that move data on and off disks is not
  31. The Web is a Detail
    • constant oscillation between computing power in the client vs server
    • decouple business rules from the UI
    • the GUI is a detail and the web is a GUI
    • the business logic can be thought of as a suite of use cases
    • this kind of abstraction is not easy and it will likely take several iterations to get just right
  32. Frameworks Are Detail
    • framework authors don’t know you nor your problems
    • they don’t owe you anything
    • they make no commitment to you whatsoever
    • you take on all the risk and burden of the framework
    • they may violate dependency rules
    • it may fight you as your application evolves
    • the framework may evolve in a direction you don’t find helpful
    • don’t marry the framework
    • business objects should not know about your framework
  33. Case Study: Video Sales
  34. The Missing Chapter
    • devil is in the implementation details
    • package by layer e.g. controller, service, repository is good way to get started
    • package by feature is vertical
    • both are suboptimal
    • consider inside (domain) and outside (infrastructure)
    • inside contains all domain concepts and outside contains all interactions with outside world
    • rename “OrdersRepository” to “Orders” since inside should be stated in terms of the ubiquitous domain languages
    • package by component
      • bundling all responsiblities related to a single coarse-grained component into a single Java package
      • similar to microservices
      • provides one interface to interact through
      • doesn’t expose repository
    • don’t mark everything as public
    • if all packages are public then structure is only organization not encapsulation
    • if you’re separating infrastructure from domain code use proper access modifiers e.g. so web controller can’t access database repository directly
    • best design intentions can be destroyed in a flash if you don’t consider the intricacies of the implementation strategy
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s