Domain-Driven Design in Python — Core Concepts

What Domain-Driven Design is

Domain-Driven Design (DDD) is a software design approach introduced by Eric Evans in 2003. Its central thesis: complex software should be modeled around the business domain, not around technical infrastructure. The code should reflect the problem space, not the solution space.

Ubiquitous language

The most impactful DDD concept is ubiquitous language — a shared vocabulary between developers and domain experts. If the business calls it a “policy,” the code should have a Policy class, not a RuleSet. If they say “underwriting,” the method should be underwrite(), not evaluate_risk_v2().

This is not just about naming. When the code and the business speak different languages, requirements get lost in translation. Bugs emerge from misunderstandings, not from logic errors.

Strategic patterns

Bounded contexts

A large system has multiple subdomains, and each subdomain has its own model. In an e-commerce system, “Product” means something different to the catalog team (title, description, images) than to the warehouse team (weight, dimensions, shelf location). Each team works within a bounded context — a boundary where a particular model applies.

Context mapping

Bounded contexts need to communicate. Context maps define how: shared kernel (common code), customer-supplier (one depends on the other), or anti-corruption layer (translation between incompatible models).

Tactical patterns

Entities

Objects with a unique identity that persists across time. A Customer with ID “C-1234” is the same customer even if they change their name and address. Identity, not attributes, defines equality.

Value objects

Objects defined by their attributes, with no identity. A Money(amount=100, currency="USD") is equal to another Money(amount=100, currency="USD"). They are immutable and interchangeable.

Aggregates

Clusters of entities and value objects treated as a single unit for data changes. An Order aggregate might contain OrderLine entities and an Address value object. The Order is the aggregate root — all modifications go through it, ensuring business rules are enforced consistently.

Domain events

Records of something that happened in the domain: OrderPlaced, PaymentReceived, ShipmentDispatched. Events enable loose coupling between bounded contexts.

Repositories

Interfaces for retrieving and storing aggregates. The domain defines what a repository does (get, save); the infrastructure layer decides how (SQL, NoSQL, in-memory).

How DDD maps to Python

DDD ConceptPython Implementation
EntityDataclass with id field, __eq__ based on id
Value ObjectFrozen dataclass or NamedTuple
AggregateClass with methods that enforce invariants
Domain EventFrozen dataclass with timestamp
RepositoryProtocol class
Bounded ContextSeparate Python package or service

Common misconception

“DDD is only for microservices.” DDD works perfectly well inside a monolith. Bounded contexts can be separate Python packages within a single codebase. Microservices are one deployment option, not a prerequisite.

When DDD is worth the investment

DDD shines when the domain is complex — insurance, logistics, finance, healthcare. If the hardest part of your project is understanding the business rules rather than the technology, DDD will pay for itself.

For simple CRUD applications where the domain is thin, DDD adds ceremony without proportional benefit.

The one thing to remember: DDD is fundamentally about aligning your code with the real business domain so that changes in business rules translate directly into changes in code, with minimal friction and miscommunication.

pythonarchitectureddd

See Also