0%

Book Description

As Python continues to grow in popularity, projects are becoming larger and more complex. Many Python developers are now taking an interest in high-level software architecture patterns such as hexagonal/clean architecture, event-driven architecture, and strategic patterns prescribed by domain-driven design (DDD). But translating those patterns into Python isn’t always straightforward.

With this practical guide, Harry Percival and Bob Gregory from MADE.com introduce proven architectural design patterns to help Python developers manage application complexity. Each pattern is illustrated with concrete examples in idiomatic Python that explain how to avoid some of the unnecessary verbosity of Java and C# syntax. You’ll learn how to implement each of these patterns in a Pythonic way.

Architectural design 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

  1. Preface
    1. Managing Complexity, Solving Business Problems
    2. Why Python?
    3. TDD, DDD and Event-Driven Architecture
    4. Who Should Read This Book
    5. A note to our Early Release Readers
    6. A Brief Overview of What You’ll Learn
      1. Part I, Building an Architecture to Support Domain Modelling
      2. Part II, Event-Driven Architecture
      3. Epilogue
    7. Example Code and Coding Along
    8. Conventions Used in This Book
    9. O’Reilly Online Learning
    10. How to Contact O’Reilly
    11. Acknowledgments
  2. Introduction: Why Do Our Designs Go Wrong?
    1. Encapsulation and Abstractions
    2. Layering
    3. The Dependency Inversion Principle
    4. A Place for All Our Business Logic: the Domain Model
  3. I. Building an Architecture to Support Domain Modelling
  4. 1. Domain Modelling
    1. What Is a Domain Model?
    2. Exploring the Domain Language
    3. Unit Testing Domain Models
      1. Dataclasses Are Great for Value Objects
      2. Value Objects and Entities
    4. Not Everything Has to Be an Object: A Domain Service Function
      1. Python’s Magic Methods Let Us Use Our Models with Idiomatic Python
      2. Exceptions Can Express Domain Concepts Too
  5. 2. Repository Pattern
    1. Persisting Our Domain Model
    2. Some Pseudocode: What Are We Going to Need?
    3. Applying the Dependency Inversion Principle to the Database
    4. Reminder: Our Model
      1. The “Normal” ORM Way: Model Depends on ORM
      2. Inverting the Dependency: ORM Depends on Model
    5. Introducing Repository Pattern
      1. The Repository in the Abstract
      2. What Is the Trade-Off?
    6. Building a Fake Repository for Tests Is Now Trivial!
    7. What is a Port and What is an Adapter, in Python?
    8. Wrap-Up
  6. 3. A Brief Interlude: On Coupling and Abstractions
    1. Abstracting State Aids Testability
    2. Choosing the Right Abstraction(s)
    3. Implementing Our Chosen Abstractions
      1. Testing Edge-to-Edge with Fakes and Dependency Injection
      2. Why Not Just Patch It Out?
    4. Wrap-up: Some Heuristics for Choosing Abstractions
  7. 4. Our First Use Case: Flask API and Service Layer
    1. Connecting Our Application to the Real World
    2. A First End-To-End (E2E) Test
    3. The Straightforward Implementation
    4. Error Conditions That Require Database Checks
    5. Introducing a Service Layer, and Using FakeRepository to Unit Test It
      1. A Typical Service Function
    6. Why Is Everything Called A Service?
    7. Putting things in folders to see where everything belongs
    8. Wrap-Up
      1. The DIP in Action
  8. 5. TDD in High Gear and Low Gear
    1. How is our Test Pyramid Looking?
    2. Should Domain Layer Tests Move to the Service Layer?
    3. On Deciding What Kind of Tests to Write
    4. High and Low Gear
    5. Fully Decoupling the Service Layer Tests From the Domain
      1. Mitigation: Keep All Domain Dependencies in Fixture Functions
      2. Adding a Missing Service
    6. Carrying the Improvement Through to the E2E Tests
    7. Wrap-Up
  9. 6. Unit of Work Pattern
    1. The Unit of Work Collaborates with Repository(-Ies)
    2. Test-Driving a UoW with Integration Tests
    3. Unit of Work and Its Context Manager
      1. The Real Unit of Work Uses SQLAlchemy Sessions
    4. Fake Unit of Work for Testing:
    5. Using the UoW in the Service Layer
    6. Explicit Tests for Commit/Rollback Behavior
    7. Explicit vs Implicit Commits
    8. Examples: Using UoW to Group Multiple Operations Into an Atomic Unit
      1. Example 1: Reallocate
      2. Example 2: Change Batch Quantity
    9. Tidying Up the Integration Tests
    10. Wrap-Up
  10. 7. Aggregates and Consistency Boundaries
    1. Why Not Just Run Everything in a Spreadsheet?
    2. Invariants, Constraints and Consistency
      1. Invariants, Concurrency and Locks
    3. What is an Aggregate?
    4. Choosing an Aggregate
    5. 1 Aggregate = 1 Repository
    6. Optimistic Concurrency With Version Numbers
      1. Implementation Options for Version Numbers
    7. Testing for Our Data Integrity Rules
      1. Enforcing Concurrency Rules by Using Database Transaction Isolation Levels
      2. SELECT FOR UPDATE Can Also Help
  11. II. Event-Driven Architecture
  12. 8. Events and the Message Bus
    1. Avoiding Making a Mess.
      1. First, Avoid Making a Mess of of Our Web Controllers
      2. … And Let’s Not Make a Mess of Our Model Either
      3. … Or the Service Layer!
    2. Single Responsibility Principle
    3. All Aboard the Message Bus!
      1. The Model Records Events
      2. Events Are Simple Dataclasses
      3. The Model Raises Events
      4. The Message Bus Maps Events to Handlers
    4. Option 1 : The Service Layer Takes Events from the Model and Puts them on the Message Bus
    5. Option 2: The Service Layer Raises Its Own Events
    6. Option 3: The Unit of Work Can Publish Events to the Message Bus
    7. Wrap-Up
  13. 9. Going to Town on the Message Bus
    1. A New Requirement Leads Us to Consider a New Architecture
      1. Imagining an Architecture Change: Everything Will Be an Event Handler
    2. Refactoring Service Functions to Message Handlers
      1. The MessageBus Becomes Responsible for Collecting Events from the UoW
      2. Our Tests Are All Written in Terms of Events Too:
      3. A Temporary Ugly Hack: The Messagebus Has to Return Results
      4. Modifying Our API to Do Events
    3. Implementing Our New Requirement
      1. Our New Event
    4. Test-Driving a New Handler
      1. Implementation
      2. A New Method on the Domain Model
    5. What Have We Achieved?
    6. Why Have We Achieved?
  14. 10. Commands and Command Handler
    1. Commands and Events
    2. Differences in Exception-handling
    3. Discussion: Events, Commands, and Error Handling
    4. Recovering From Errors Synchronously
  15. 11. Event-Driven Architecture: Using Events to Integrate Microservices
    1. Distributed Ball of Mud, and Thinking in Nouns
    2. Error Handling in Distributed Systems
    3. The Alternative: Temporal Decoupling using Asynchronous Messaging
    4. Using a Redis Pubsub Channel for Integration
    5. Test-Driving It All Using an End-To-End Test
      1. Redis Is Another Thin Adapter Around Our Message Bus
      2. Our New Outgoing Event
    6. Wrap-Up
  16. 12. Command-Query Responsibility Separation (CQRS)
    1. Domain models are for writing
    2. Most users aren’t going to buy your furniture
    3. Post/Redirect/Get and CQS
    4. Hold on to Your Lunch Folks.
    5. Testing CQRS Views
    6. “Obvious” Alternative 1: Using the Existing Repository
    7. Your Domain Model is not Optimized for Read Operations
    8. “Obvious” Alternative 2: Using the ORM
    9. SELECT N+1, and Other Performance Considerations
    10. Time to completely jump the shark
      1. Updating a Read Model Table Using an Event Handler
    11. Changing our Read Model Implementation is Easy
    12. But Would You Really? CRUD versus CQRS.
  17. 13. Dependency Injection (And Bootstrapping)
    1. Implicit vs Explicit Dependencies
    2. Aren’t Explicit Dependencies Totally Weird and Java-Ey Tho?
    3. Preparing Handlers: Manual DI with Closures and Partials
    4. An Alternative Using Classes
    5. A Bootstrap Script
    6. Messagebus Gets Given Handlers at Runtime
    7. Using Bootstrap in Our Entrypoints
    8. Initializing DI in Our Tests
    9. Building an Adapter “Properly”: A Worked Example
      1. Define the Abstract and Concrete Implementations
      2. Make a Fake Version for your Tests
      3. Figure out how to Integration Test the Real Thing
    10. DI and bootstrap wrap-up
  18. Epilogue: What now?
    1. How do I get there from here?
    2. Separating entangled responsibilities
    3. Identifying Aggregates and Bounded Contexts
    4. An Event-driven Approach to go Microservices Via Strangler Pattern
    5. Convincing your stakeholders to try something new
    6. Getting started
    7. More required reading
    8. Questions our Tech Reviewers Asked That We Couldn’t Work Into Prose
    9. Footguns
      1. Reliable messaging is hard
      2. We explicitly choose small, focused transactions that can fail independently
      3. We don’t discuss idempotency
      4. Your events will need to change their schema over time
  19. A. Summary diagram and table
  20. B. A Template Project Structure
    1. Env Vars, 12-Factor, and Config, Inside and Outside Containers.
    2. Config.py
    3. Docker-Compose and Containers Config
    4. Installing Your Source as a Package
    5. Dockerfile
    6. Tests
    7. Wrap-up
  21. C. Swapping Out the Infrastructure: Do Everything with CSVs
    1. Implementing a Repository and Unit of Work for CSVs
  22. D. Repository and Unit of Work Patterns with Django
    1. Repository Pattern with Django
      1. Custom Methods on Django ORM Classes to Translate To/From Our Domain Model
    2. Unit of Work Pattern with Django
    3. API: Django Views Are Adapters
    4. Why was this all so hard?
    5. What To Do If You Already Have Django
    6. Steps along the way
  23. E. Validation
    1. What is validation anyway?
    2. Validating Syntax
    3. Postel’s Law and the Tolerant Reader Pattern
    4. Validating At The Edge
    5. Validating Semantics
    6. Validating Pragmatics
  24. Index
18.224.39.74