Bounded Contexts in Python — Core Concepts
What a bounded context is
A bounded context is a boundary within which a particular domain model is defined and applicable. The term comes from Eric Evans’ Domain-Driven Design. It solves a fundamental problem: in any non-trivial system, the same concept means different things to different teams or subsystems.
An Account in the authentication context is a username, password hash, and login history. An Account in the billing context is a payment method, balance, and invoice history. Forcing both meanings into one class creates a bloated, fragile model that serves neither purpose well.
Why bounded contexts matter
Avoiding the “God model”
Without explicit boundaries, teams tend to build a single shared model that tries to represent everything. This “God model” grows until:
- Every change requires coordinating across teams
- Simple features take weeks because of unexpected side effects
- Nobody fully understands the model anymore
Bounded contexts prevent this by giving each subdomain its own focused model.
Enabling team autonomy
Each bounded context can be owned by a different team, developed at its own pace, and deployed independently. The only contract between contexts is the communication protocol at the boundary.
How to identify bounded contexts
Follow the language
When the same term means different things to different groups, you have found a boundary. If the warehouse team and the customer support team argue about what “order status” means, they are in different contexts.
Follow the data
When two parts of the system need different attributes for the same concept, they belong in different contexts. Catalog needs product descriptions and images. Warehouse needs product dimensions and bin locations.
Follow the teams
Conway’s Law suggests that system architecture mirrors team structure. Bounded contexts often align with team boundaries — and when they do not, friction appears.
Bounded context patterns
Shared Kernel
Two contexts share a small common model. Both teams agree on the shared definitions and coordinate changes. Use sparingly — it creates coupling.
Customer-Supplier
One context (supplier) provides data that another (customer) consumes. The supplier designs the interface; the customer adapts. Example: the catalog context supplies product data that the search context indexes.
Anti-Corruption Layer
A translation layer that protects one context from another’s model. Used when integrating with legacy systems or external APIs. The ACL translates foreign concepts into your domain language.
Published Language
Contexts communicate through a well-documented, versioned data format — like a shared event schema or API contract. Neither context exposes its internals.
Implementing bounded contexts in Python
As packages in a monolith
src/
catalog/ # Bounded context 1
models.py
services.py
events.py
warehouse/ # Bounded context 2
models.py
services.py
events.py
shared/ # Shared kernel (minimal)
events.py
types.py
Each package has its own models.py with its own Product class. Import rules prevent catalog from importing warehouse internals and vice versa.
As separate services
Each bounded context becomes a microservice with its own database, deployed independently. Communication happens through HTTP APIs, message queues, or event streams.
The monolith approach is usually better to start with. Extract into services later when the boundaries are proven stable.
Context mapping in practice
Context maps visualize how bounded contexts relate to each other. For a Python project, document this in your architecture decisions:
[Catalog] --publishes--> ProductCreated event
[Search] --subscribes--> ProductCreated event (anti-corruption layer translates)
[Warehouse] --customer of--> [Catalog] (reads product dimensions via API)
[Billing] --shared kernel--> [Identity] (both use CustomerId value object)
Common misconception
“Each bounded context must be a microservice.” This is the most damaging misconception. Bounded contexts are a logical design concept. They can be packages in a monolith, modules in a monorepo, or separate services. The boundary is about model separation, not deployment topology.
When to introduce bounded contexts
- Early signs: Different teams argue about what a concept means. A model class has 30+ fields serving different purposes. Changes in one area frequently break another.
- Right time: When you can identify at least two distinct subdomains with different vocabularies.
- Too early: A small project with one team and one clear domain. Adding boundaries where none are needed creates unnecessary complexity.
The one thing to remember: Bounded contexts draw clear lines between different parts of your domain, letting each part have its own model and vocabulary without contaminating the others.
See Also
- Python Aggregate Pattern Why grouping related objects under a single gatekeeper prevents data chaos in your Python application.
- Python Bulkhead Pattern Why smart Python apps put walls between their parts — like a ship that stays afloat even with a hole in the hull.
- Python Circuit Breaker Pattern How a circuit breaker saves your app from crashing — explained with a home electrical fuse analogy.
- Python Clean Architecture Why your Python app should look like an onion — and how that saves you from painful rewrites.
- Python Connection Draining How to shut down a Python server without hanging up on people mid-conversation — like a store that locks the entrance but lets shoppers finish.