Skip to main content

Clean Architecture

Our definition of Clean Architecture, including images used in this file, come from the Clean Coder blog.

Clean Architecture Overview

While the broad strokes remain the same as the Clean Coder definitions, this document will tie into our current architecture for realistic examples.

Broad Strokes

  • Layers in Clean Architecture are ordered such that outer layers are dependent on inner layers.
    • The opposite should never be the case, or at least as little as possible.
    • The ideal state is that if an element in an outer layer changes it should change none of the layers within it.
  • The closer you get to the innermost layer, the closer you are to the raw business logic that acts on raw data.
  • The number and labels of layers are not important, and are simply used as shorthand for the types of resources present at deeper/higher levels.

Layers

Reasons for Layers

  • Isolating layers reduces the chance for changes to that layer to affect other layers. For example, if the Database code is isolated from the business logic, then changes to database technology will not require rewriting business logic.
  • Allows development and testing of individual components without an understanding of the tech stack beyond that component.

"Frameworks and Drivers" Layer

  • The outermost layer, often components external to the central product.
  • May change based on changes from inner layers.

Orca Examples

  • API Gateway
  • Postgres Database
  • S3 Buckets
  • SQS Queues
  • Lambda API
  • Step Functions

"Interface Adapters" Layer

  • Best viewed as a sub-section of the "Frameworks and Drivers" layer.
    • Changes to the database change this layer as well, which would otherwise contradict the dependency chain of Clean Architecture.
  • Translates data between the forms required for the external components above it, and the internal components beneath it.
    • All technical details from the above layer should be stripped before handing down to the lower layer.
  • Receives calls from the layer above it and/or the layer beneath it.

Resource Examples

  • Centralized code for contacting the Postgres Database
  • Centralized code for posting events to SQS Queues
  • Pulling relevant information out of parameters passed to Lambda handler functions before calling additional logic

Code Examples

  • Storage adapter rdbms.py
    • Provides access to database without requiring knowledge of database outside of constructor.
    • Implements single-purpose interface to allow future features to use different storage adapters as needed.
    • Allows additional layer between RDBMS database technologies by isolating technology-specific SQL to abstract methods.

"Use Cases" Layer

  • The business logic of the application, often analogous to user stories.
    • Larger features and flows may consist of several Use Cases chained together by SQS Queues or Step Functions.
  • May call higher or lower components as needed, though neither should need to know the details of the other.

Resource Examples

  • task functions in Lambdas that perform a series of operations.

Code Examples

  • Use case get_orphans_page.py
    • Performs the underlying flow of getting results from the database adapter, and passing them out to the caller.
    • Accepts adapters for specific purposes.

"Entities" Layer

  • Simple, reusable objects and functions.
  • Can only communicate with the Use Cases layer.

Resource Examples

  • Shared helper libraries without external dependencies
  • Individual Lambda functions

Code Examples

  • Entity orphan.py
    • Simple data classes.
    • Standardizes information transfer between classes without the need for dictionary keys.

Additional Thoughts

  • Categorization of some resources are blurred.

    • I have attempted to place each resource type type in its highest possible layer to encourage viewing that type's availability as mutable.
    • StepFunctions are analogous to user stories, but are AWS reliant components and require a translation layer.
    • The Lambda API (parameters for the handler) are dictated by AWS. Lambdas also contain business logic, and should therefore contain their own translation layer between API and business logic.
  • Orca does currently have some architecture that does not follow these principles.

    • Database code is mixed into some of our lambdas, and the Postgres return structure in particular is used frequently rather than reformatting to avoid entanglement.
    • We hope to clean some of this up, and to use these aforementioned practices going forward.