peopleanalyst

library / lib62a8f8d13dab550e

Architecture Patterns with Python

Harry J. W. Percival & Bob Gregory · 2020

In a sentence

A practical guide to applying Domain-Driven Design, Test-Driven Development, and Event-Driven Architecture patterns in Python to build maintainable, testable, and loosely coupled systems.

Architecture Patterns with Python teaches Python developers how to manage growing complexity in real-world applications by systematically applying battle-tested architectural patterns drawn from the DDD, TDD, and microservices traditions. Starting from a concrete e-commerce allocation domain, the book walks step-by-step through building a layered architecture—Domain Model, Repository, Service Layer, Unit of Work, Aggregates—and then extends it into an event-driven system using Domain Events, a Message Bus, Commands, CQRS, and Dependency Injection. Every pattern is introduced test-first, with working Python code, explicit trade-off tables, and honest discussion of costs. By the end, readers have a blueprint for writing Python applications whose complexity grows slowly relative to their size, whose core business logic is fully decoupled from infrastructure, and whose test suites are fast, meaningful, and pyramidal.

The four lenses

  • Science
  • Statistics
  • Systems
  • Strategy

Tags

f1-systems

The model

A causal model describing how architectural design levers (patterns, principles, and practices) shape intermediate code-quality and team-behavioral states, which in turn drive system-level outcomes such as maintainability, testability, and delivery speed.

Dependency Inversion Principle Applicationdesign lever

The degree to which high-level modules (domain, service layer) depend on abstractions rather than on concrete low-level infrastructure modules such as ORMs, email libraries, or HTTP frameworks. Operationalized as explicit abstract base classes or duck-typed interfaces between layers.

Domain Model Puritydesign lever

The extent to which domain model classes contain no direct imports of or dependencies on infrastructure concerns such as ORMs, web frameworks, email clients, or databases. A pure domain model consists solely of plain Python objects expressing business rules and concepts.

Repository Pattern Adoptiondesign lever

The use of a repository abstraction that hides persistent storage details behind a simple add/get interface, allowing the domain and service layers to be tested without a real database by substituting an in-memory fake repository.

Service Layer Adoptiondesign lever

The presence of a dedicated orchestration layer that defines use-case entry points, coordinates repositories and domain services, handles transactions via the Unit of Work, and presents a technology-agnostic API to entrypoints such as Flask or CLI tools.

Unit of Work Pattern Adoptiondesign lever

The use of a Unit of Work abstraction that manages atomic database transactions, provides access to repositories within a single context, and collects domain events for publishing, decoupling service functions from session management.

Aggregate and Consistency Boundary Designdesign lever

The degree to which the domain model is organized around well-chosen aggregates that act as single entrypoints for modifying related objects, enforce invariants within their boundary, and constrain the scope of each database transaction to one aggregate.

Event-Driven Design Adoptiondesign lever

The extent to which the system models significant business occurrences as explicit Domain Event objects, uses a Message Bus to route events to handlers, and treats both internal workflows and external integrations as event-processing pipelines rather than direct method calls.

CQRS Adoptiondesign lever

The degree to which read operations are served by separate, simplified read models or raw SQL views rather than forcing the write-optimized domain model to serve query needs, reducing complexity and enabling independent scaling of reads and writes.

Dependency Injection and Bootstrappingdesign lever

The practice of explicitly declaring all external dependencies (UoW, email, message publisher) as function or class parameters and wiring them together in a single bootstrap script, enabling easy substitution of real and fake implementations across entrypoints and tests.

Test Pyramid Shapebehavioral pattern

The distribution of automated tests across unit, integration, and end-to-end levels, with a healthy pyramid having many fast unit tests, fewer integration tests, and very few end-to-end tests. An inverted pyramid (ice-cream cone) indicates over-reliance on slow, brittle tests.

Infrastructure Couplingpsychological state

The degree to which business logic modules are tightly coupled to specific infrastructure choices such as a particular ORM, web framework, database engine, or messaging library, making it expensive to change any infrastructure component without modifying business logic.

Global Code Coupling (Big Ball of Mud)contextual condition

The overall degree of entanglement among modules in the codebase, characterized by homogeneity of function across layers, business logic scattered across web handlers and model classes, and dependencies that make changing any component risky and expensive.

Domain Model Expressivenesspsychological state

The degree to which the domain model code reads in the language of the business domain, uses ubiquitous language for class and method names, and serves as living documentation that non-technical stakeholders could validate, making the model a faithful representation of business rules.

Testabilitypsychological state

The ease with which individual components of the system can be exercised in isolation with fast, deterministic tests that do not require real databases, email servers, or HTTP services, enabled by well-defined abstractions and injectable fake dependencies.

Consistency and Invariant Enforcementbehavioral pattern

The degree to which the system reliably prevents invalid states—such as over-allocation of stock or double-booking—through a combination of aggregate boundaries, version numbers for optimistic locking, and transaction isolation, even under concurrent load.

Inter-Service Couplingcontextual condition

The degree to which separate services or subsystems depend on one another synchronously and directly (high coupling, e.g., synchronous HTTP RPC chains) versus communicating through asynchronous events with no direct dependency on each other's availability (low coupling).

System Maintainabilityoutcome metric

The long-term ease of modifying, extending, and refactoring the codebase in response to changing business requirements without introducing regressions, measured by the cost and risk of making changes as the system grows in size and complexity.

Delivery Speed and Agilityoutcome metric

The rate at which the team can safely ship new features and bug fixes, influenced by test suite speed, confidence from automated tests, and the ease of reasoning about the impact of a change in a well-structured codebase.

System Reliability and Resilienceoutcome metric

The ability of the system to continue functioning correctly and to recover gracefully when individual components fail, enabled by small atomic transactions, independent failure of event handlers, retry logic, and eventual consistency between aggregates.

Read Performance and Scalabilityoutcome metric

The speed and horizontal scalability of query operations, improved by separating read models from the write-optimized domain model, using denormalized views or caches, and avoiding SELECT N+1 patterns inherent in ORM-based domain traversal for reads.

How they connect

  • dependency inversion influences infrastructure coupling
  • dependency inversion influences testability
  • domain model purity influences domain model expressiveness
  • domain model purity influences testability
  • repository pattern influences infrastructure coupling
  • repository pattern influences testability
  • service layer influences test pyramid shape
  • service layer influences global code coupling
  • unit of work pattern influences consistency enforcement
  • unit of work pattern influences testability
  • aggregate design influences consistency enforcement
  • aggregate design influences infrastructure coupling
  • event driven design influences service coupling between systems
  • event driven design influences global code coupling
  • event driven design influences system reliability
  • cqrs adoption influences read performance
  • cqrs adoption influences global code coupling
  • dependency injection influences testability
  • dependency injection influences infrastructure coupling
  • infrastructure coupling influences maintainability
  • global code coupling influences maintainability
  • global code coupling influences delivery speed
  • testability influences test pyramid shape
  • test pyramid shape influences delivery speed
  • test pyramid shape influences maintainability
  • consistency enforcement influences system reliability
  • service coupling between systems influences system reliability
  • service coupling between systems influences maintainability
  • domain model expressiveness influences maintainability
  • read performance influences delivery speed

The story

The reader A Python developer working on a moderately complex application who wants clean, maintainable code but keeps ending up with a tangled ball of mud that is hard to test, change, or reason about.

External problem

Business logic is spread across web handlers, ORM models, and utility modules; tests are slow, fragile, or absent; adding features breaks existing behavior.

Internal problem

The developer feels frustrated, embarrassed, and stuck—unsure whether their architecture is fundamentally broken or whether they are just missing the right vocabulary and tools.

Philosophical problem

It is wrong that growing a codebase should make it progressively harder to change; software should remain malleable as it matures.

The plan

  1. Model the business domain in pure Python objects (Entities, Value Objects, Domain Services) with no infrastructure dependencies, driven by unit tests.
  2. Introduce the Repository pattern to abstract persistent storage, enabling trivial in-memory fakes for testing.
  3. Add a Service Layer to define use-case boundaries, keep controllers thin, and make the bulk of tests fast and decoupled.
  4. Apply the Unit of Work pattern to group operations atomically and give tests a single seam for controlling database state.
  5. Introduce Aggregates to enforce consistency boundaries and manage concurrency safely.
  6. Adopt Domain Events and a Message Bus to decouple side effects from core use cases.
  7. Distinguish Commands from Events to clarify error-handling semantics.
  8. Integrate microservices via asynchronous external events using Redis pub/sub.
  9. Apply CQRS to use simple read models for queries while preserving a rich domain model for writes.
  10. Wire everything together with a bootstrap script and explicit dependency injection, making production and test configurations easy to swap.

Success

  • A test pyramid dominated by fast, dependency-free unit tests with a small number of integration and E2E tests.
  • A domain model that can be changed or refactored without touching infrastructure or rewriting tests.
  • New features that fit cleanly into existing architecture without increasing global complexity.
  • Microservices that communicate through well-defined events and can evolve independently.
  • A codebase new developers can onboard to quickly by reading living-documentation tests written in domain language.

At stake

  • The application becomes an unmaintainable 'big ball of mud' where every change risks breaking something else.
  • Test suites take hours to run, providing little confidence and slowing delivery.
  • Business logic is scattered across web handlers, ORM models, and manager classes, invisible to domain experts.
  • The team becomes afraid to refactor and accumulates technical debt until the system must be rewritten.
  • Microservices become a 'distributed big ball of mud' with tight temporal coupling through synchronous HTTP chains.

Related in the library