Frequently Asked Questions
Real developer concerns about DDD, answered pragmatically.
Getting Started
Isn't DDD just moving complexity around?
The difference is discoverability and isolation. With structured code by domain and layers, problems are easier to locate and don't spread across the application. When something breaks, you know exactly where to look. When requirements change, you modify one place instead of hunting through scattered logic.
When does DDD justify the ceremony?
Ask yourself: will this logic appear in multiple places? Does it have business rules beyond simple assignment? For example, booking.status = "cancelled" seems simple, but if cancellation involves calculating fees, sending notifications, or validating timing constraints—and this happens from multiple entry points—the ceremony pays for itself immediately.
Do I need to master everything before starting?
No. AI can generate the boilerplate while you focus on business logic. The book teaches patterns progressively—you'll understand the "why" as you apply them. Modern AI tools make the technical implementation nearly automatic.
Why generate IDs manually instead of using auto-increment?
Auto-increment IDs have limitations that appear at scale:
- Distributed systems: Auto-increment requires a single source of truth. In distributed architectures, this becomes a bottleneck.
- Eventual consistency: You can't know the ID until after the database insert. With generated IDs, you know immediately.
- Testing: Generated IDs make tests deterministic without database round-trips.
The "Commands return void" rule conflicts with REST conventions
REST says POST should return 201 with the created resource. The solution:
- Generate ID before the command:
booking_id = BookingId.generate() - Dispatch command (void):
command_bus.dispatch(CreateBookingCommand(id=booking_id, ...)) - Query for response:
booking = query_bus.ask(GetBookingByIdQuery(booking_id)) - Return 201 with the queried booking
Technical Decisions
Why ABC instead of Protocol for interfaces?
We recommend abc.ABC with @abstractmethod for repositories because:
- Shared code: Base classes can include common methods
- Runtime validation: Missing abstract methods fail immediately at instantiation
- Explicit dependencies: FastAPI injection clearly shows inheritance relationships
- Easier mocking: Test doubles inherit from the same ABC
Why dataclasses instead of Pydantic for domain objects?
Two reasons:
- Framework independence: The patterns work beyond FastAPI/Pydantic ecosystems
- Avoiding anemic domains: When Pydantic handles validation, there's temptation to move business rules to infrastructure
You can use Pydantic for DTOs and API schemas. Just don't use it to implement business rules.
How do you enforce aggregate invariants without private fields?
Python lacks real encapsulation. Pragmatic enforcement:
- Convention over enforcement: Document what's allowed and what isn't
- AI validation: Configure AI tools to flag violations during code review
- Team awareness: Developers understand boundaries even without technical guardrails
How do I handle transactions across multiple aggregates?
In Python with SQLAlchemy, the session acts as an implicit Unit of Work:
with session.begin():
booking_repo.save(booking)
customer_repo.save(customer)
# Both committed atomically For eventual consistency (microservices), use domain events instead.
Migration & Team Adoption
How do I introduce DDD without stopping feature development?
Yes, but with a critical prerequisite: the CTO must be the promoter, not the skeptic.
If leadership sees DDD as "developer indulgence," every delay will be blamed on architecture. Without executive buy-in, don't start.
The migration strategy: apply DDD only to new features, refactor on touch, and use AI-assisted refactoring for legacy code with tests.
Will juniors break everything if we adopt DDD?
Actually, juniors break less with DDD than without it.
Why? DDD provides a reference. Instead of "figure out where this logic goes," juniors have clear patterns:
- Business rule? → Domain entity or value object
- Database access? → Repository
- Orchestration? → Command handler
How do DDD and traditional CRUD coexist?
80% of code is simple CRUD, only 20% has real business logic. The hybrid approach:
- Separate by subdomain: DDD apps for complex logic, traditional for CRUD
- Document the boundary: An
ARCHITECTURE.mdexplaining when to use which style - Clear naming: Prefixes like
ddd_billing/vscrud_users/
Testing
Can domain tests run without infrastructure?
Yes, but be pragmatic about what "infrastructure" means:
- Domain/business logic tests: No database, no HTTP. Pure Python. Test rules like "party size can't exceed 20."
- Integration tests: Use the real database (not SQLite). In-memory databases behave differently than production.
The split: fast unit tests for business rules, slower integration tests for persistence.
ROI & Skepticism
What's the concrete ROI of DDD?
Writing new features is easy—maintaining them is hard. The ROI appears over time:
- Faster bug fixes (logic is localized)
- Safer modifications (changes don't cascade unexpectedly)
- Better AI collaboration (structured code generates better AI suggestions)
Plus: you're not the one writing most code anymore—AI is. AI produces better results with well-structured patterns.
Why not let AI generate simple code instead?
AI struggles to generate maintainable simple code. Unstructured code quickly becomes a maze that even AI can't navigate reliably. DDD patterns give AI the constraints it needs to produce consistent, predictable results.
Have more questions? The book covers these topics in depth.
Promotional Price - $9.99