Domain-Driven Design, introduced by Eric Evans in 2003, has gained renewed attention with the rise of microservices. The concepts of aggregate, bounded context, and ubiquitous language provide a vocabulary and structure for breaking down microservices. In practice, however, what works differs from the ideals outlined in Evans' book.
The bounded context is the most useful DDD concept for microservices. It defines a clear linguistic and data boundary within which a model is consistent. A bounded context maps well to a microservice or a small group of tightly related microservices. This aligns with Team Topologies, where stream-aligned teams own their bounded contexts. The bounded context explicitly defines what each team owns and what the interfaces between contexts are.
I've seen teams carve a bounded context around an order‑processing domain and then spin a separate inventory microservice. The two services each own their own tables in PostgreSQL, which means we duplicate product identifiers to avoid cross‑service joins. In one deployment we were handling 200 k requests per second and the async Kafka topics that glue the services together added an average of 45 ms latency. When we tried to shrink the boundary and expose a synchronous gRPC call instead, the latency dropped to 12 ms but the coupling grew, and a single schema change in the inventory DB forced a coordinated rollout across both teams. The lesson was that the boundary should be drawn with both business semantics and operational cost in mind, not just the cleanest diagram.
The ubiquitous language concept is a shared vocabulary between developers and domain experts, used in code, tests, discussions, and documentation. This practice offers a high payoff for most teams, with a relatively small investment in workshops to define the language and code reviews to enforce it. The benefits include reduced translation costs between business requirements and code, fewer misunderstandings in requirements sessions, and code that is readable by domain experts.
The language part can bite you when the glossary drifts. In a project where the business called a 'client' what we coded as a 'customer', the mismatch showed up in Swagger specs and caused a handful of integration tests to fail in CI. We introduced a small lint rule in our CI pipeline that flags any new class or field that does not appear in the shared glossary stored in Confluence. After three sprints the rule caught 12 violations and we measured a 30 % drop in ticket churn related to naming confusion. It is a cheap guard, but it only works if the team treats the glossary as the single source of truth.
Strategic DDD, which includes bounded contexts, context maps, and core domain identification, is widely applicable. Tactical DDD, on the other hand, adds significant design complexity that is not justified for every application. The aggregate pattern, for example, ensures transactional consistency within an aggregate and eventual consistency across aggregates. This is the right model for complex, collaborative domains but adds overhead for simpler CRUD applications.
Aggregates are tempting to model after every entity, but the runtime cost can be surprising. Using Axon Framework for event sourcing, we wrapped a simple product catalog entry in an aggregate that emitted a ProductCreated event. The extra write‑ahead log grew the write latency from 3 ms to 9 ms and doubled the heap usage for a service that was already close to its 2 GB container limit. For a read‑heavy catalog we switched to a plain JPA entity and saw a 15 % reduction in CPU and a 40 % increase in throughput. The trade‑off is clear: if the domain needs rich invariants across multiple rows, the aggregate pays off; otherwise the overhead is hard to justify.
DDD requires a sustained investment in domain modelling, including workshops with domain experts and EventStorming sessions to map domain events and commands. Organisations that skip this investment and apply DDD patterns to a domain they do not thoroughly understand produce models that reflect the database schema rather than the domain. This results in complexity without design clarity.