As Python continues to grow in popularity, projects are becoming larger and more complex. Many Python developers are taking an interest in high-level software design patterns such as hexagonal/clean architecture, event-driven architecture, and the strategic patterns prescribed by domain-driven design (DDD). But translating those patterns into Python isn’t always straightforward.
With this hands-on guide, Harry Percival and Bob Gregory from MADE.com introduce proven architectural design patterns to help Python developers manage application complexity—and get the most value out of their test suites.
Each pattern is illustrated with concrete examples in beautiful, idiomatic Python, avoiding some of the verbosity of Java and C# syntax. Patterns include:
- Dependency inversion and its links to ports and adapters (hexagonal/clean architecture)
- Domain-driven design’s distinction between Entities, Value Objects, and Aggregates
- Repository and Unit of Work patterns for persistent storage
- Events, commands, and the message bus
- Command-query responsibility segregation (CQRS)
- Event-driven architecture and reactive microservices
Table of Contents
- Managing Complexity, Solving Business Problems
- Why Python?
- TDD, DDD, and Event-Driven Architecture
- Who Should Read This Book
- A Brief Overview of What You’ll Learn
- Example Code and Coding Along
- Conventions Used in This Book
- O’Reilly Online Learning
- How to Contact O’Reilly
- I. Building an Architecture to Support Domain Modeling
1. Domain Modeling
- What Is a Domain Model?
- Exploring the Domain Language
- Unit Testing Domain Models
- Not Everything Has to Be an Object: A Domain Service Function
2. Repository Pattern
- Persisting Our Domain Model
- Some Pseudocode: What Are We Going to Need?
- Applying the DIP to Data Access
- Reminder: Our Model
- Introducing the Repository Pattern
- Building a Fake Repository for Tests Is Now Trivial!
- What Is a Port and What Is an Adapter, in Python?
- 3. A Brief Interlude: On Coupling and Abstractions
4. Our First Use Case: Flask API and Service Layer
- Connecting Our Application to the Real World
- A First End-to-End Test
- The Straightforward Implementation
- Error Conditions That Require Database Checks
- Introducing a Service Layer, and Using FakeRepository to Unit Test It
- Why Is Everything Called a Service?
- Putting Things in Folders to See Where It All Belongs
5. TDD in High Gear and Low Gear
- How Is Our Test Pyramid Looking?
- Should Domain Layer Tests Move to the Service Layer?
- On Deciding What Kind of Tests to Write
- High and Low Gear
- Fully Decoupling the Service-Layer Tests from the Domain
- Carrying the Improvement Through to the E2E Tests
6. Unit of Work Pattern
- The Unit of Work Collaborates with the Repository
- Test-Driving a UoW with Integration Tests
- Unit of Work and Its Context Manager
- Using the UoW in the Service Layer
- Explicit Tests for Commit/Rollback Behavior
- Explicit Versus Implicit Commits
- Examples: Using UoW to Group Multiple Operations into an Atomic Unit
- Tidying Up the Integration Tests
7. Aggregates and Consistency Boundaries
- Why Not Just Run Everything in a Spreadsheet?
- Invariants, Constraints, and Consistency
- What Is an Aggregate?
- Choosing an Aggregate
- One Aggregate = One Repository
- What About Performance?
- Optimistic Concurrency with Version Numbers
- Testing for Our Data Integrity Rules
- Part I Recap
- II. Event-Driven Architecture
8. Events and the Message Bus
- Avoiding Making a Mess
- Single Responsibility Principle
- All Aboard the Message Bus!
- Option 1: The Service Layer Takes Events from the Model and Puts Them on the Message Bus
- Option 2: The Service Layer Raises Its Own Events
- Option 3: The UoW Publishes Events to the Message Bus
9. Going to Town on the Message Bus
- A New Requirement Leads Us to a New Architecture
- Refactoring Service Functions to Message Handlers
- Implementing Our New Requirement
- Test-Driving a New Handler
- Optionally: Unit Testing Event Handlers in Isolation with a Fake Message Bus
- 10. Commands and Command Handler
11. Event-Driven Architecture: Using Events to Integrate Microservices
- Distributed Ball of Mud, and Thinking in Nouns
- Error Handling in Distributed Systems
- The Alternative: Temporal Decoupling Using Asynchronous Messaging
- Using a Redis Pub/Sub Channel for Integration
- Test-Driving It All Using an End-to-End Test
- Internal Versus External Events
12. Command-Query Responsibility Segregation (CQRS)
- Domain Models Are for Writing
- Most Users Aren’t Going to Buy Your Furniture
- Post/Redirect/Get and CQS
- Hold On to Your Lunch, Folks
- Testing CQRS Views
- “Obvious” Alternative 1: Using the Existing Repository
- Your Domain Model Is Not Optimized for Read Operations
- “Obvious” Alternative 2: Using the ORM
- SELECT N+1 and Other Performance Considerations
- Time to Completely Jump the Shark
- Changing Our Read Model Implementation Is Easy
13. Dependency Injection (and Bootstrapping)
- Implicit Versus Explicit Dependencies
- Aren’t Explicit Dependencies Totally Weird and Java-y?
- Preparing Handlers: Manual DI with Closures and Partials
- An Alternative Using Classes
- A Bootstrap Script
- Message Bus Is Given Handlers at Runtime
- Using Bootstrap in Our Entrypoints
- Initializing DI in Our Tests
- Building an Adapter “Properly”: A Worked Example
- What Now?
- How Do I Get There from Here?
- Separating Entangled Responsibilities
- Identifying Aggregates and Bounded Contexts
- An Event-Driven Approach to Go to Microservices via Strangler Pattern
- Convincing Your Stakeholders to Try Something New
- Questions Our Tech Reviewers Asked That We Couldn’t Work into Prose
- More Required Reading
- A. Summary Diagram and Table
- B. A Template Project Structure
- C. Swapping Out the Infrastructure: Do Everything with CSVs
- D. Repository and Unit of Work Patterns with Django
- E. Validation
- Title: Architecture Patterns with Python
- Release date: March 2020
- Publisher(s): O'Reilly Media, Inc.
- ISBN: 9781492052203