Skip to content
Leo Jung
Go back

[DDD Intro #30] My understanding of DDD: Good design starts from good questions

Edit page

When first encountering DDD, the terminology appears first. Entity, Value Object, Aggregate, Repository, Domain Service, Domain Event, Bounded Context, Context Map. If CQRS, Event Sourcing, Hexagonal Architecture, and Microservice follow, DDD feels like a huge technical system.

So studying DDD initially feels like understanding terminology. We wonder what Entity is, what Value Object is, how far an Aggregate Root should be responsible, and how Repository differs from DAO.

But over time, I have come to think that the core of DDD is not the terminology itself. DDD is less a methodology that gives answers and more a way of thinking that helps us ask better questions.

DDD makes us see the domain before the code

Before knowing DDD, it is easy to think of design mainly as a problem of code structure. We first think about how to divide packages, where to put interfaces, and which architecture to apply.

Of course, these questions are important. But DDD asks questions that come before them.

What problem are we solving?
What are the most important rules in this domain?
What words do domain experts use to explain this situation?
Is the same word being used with different meanings?
What must be kept consistent at once?

These questions start outside code, but eventually return to code. If we do not properly understand the domain, the model will not last long no matter how clean the code structure is.

DDD makes developers move closer to business language. Developers are not merely people who receive requirements and implement them. They become people who explore the domain together and leave that understanding in code.

Good names are the beginning of good design

One thing I felt strongly while studying DDD is the importance of names. A good name is not simply a readability issue. A good name creates model boundaries, limits responsibility, and enables conversation.

setStatus(CANCELED) and cancelByMember(reason, now) can create the same result. But the two pieces of code say different things. The former changes state, and the latter expresses domain behavior.

String planCode and PlanCode planCode are also different. The former is a string, and the latter is a domain concept. PaymentService and RefundPolicy are also different. The former looks like a service that can do anything, while the latter reveals the responsibility of refund policy.

Good names reduce guessing. They also help domain experts and developers speak the same words.

This is ultimately why Ubiquitous Language matters in DDD. Design is revealed through names. When names are vague, the model is vague too.

Models keep changing

The first model cannot be perfect. As we understand the domain more deeply, the model also changes. At first, one User may seem enough, but later we learn that Member, Subscriber, Buyer, and Operator are different concepts.

At first, one CANCELED state may seem enough, but later we learn that user cancellation, administrative termination, suspension due to payment failure, period expiration, and refund completion must be distinguished.

This change is not failure. It is a signal that we understand the domain better.

The problem is insisting on the first model. If we keep adding conditionals and exception handling when the model should change, the code gradually moves away from the domain.

DDD reminds us that a model is not a fixed deliverable, but knowledge that keeps being refined.

Splitting boundaries is hard

The hardest thing in DDD may not be making one class well. The harder thing is splitting boundaries.

Splitting Bounded Contexts means declaring, “This word means this here.” It means accepting that the same word can be a different model in a different context.

In a subscription service, “customer” may be an account in the member context, a subscriber in the subscription context, and a billing target in the settlement context. If we do not acknowledge this difference, we create one giant common model.

Splitting boundaries is not only a technical decision. Organization, team responsibilities, deployment structure, data ownership, and business language are all involved. That is why it is hard.

But if we do not split boundaries, complexity does not disappear. It only mixes inside one large model.

DDD is not needed everywhere

One point I want to keep emphasizing throughout this series is this: DDD is not an answer that must be applied everywhere.

For simple CRUD, simple design is better. Not every condition needs to become a Specification, and not every feature needs to publish Domain Events. Not every system needs to use CQRS and Event Sourcing.

DDD is a way to focus design energy on complex domains. We need to find the Core Domain and go deep there. Conversely, we should avoid excessive design in Generic Subdomains or simple supporting features.

Good design is not using many complex structures. It is choosing a structure that matches the complexity and value of the problem.

Domain knowledge should remain in code

The most practical value of DDD is that domain knowledge remains in code.

Requirement documents and meeting notes become outdated over time. People leave teams, and policies change. In the end, code remains the longest. But if the code cannot speak domain language, domain knowledge disappears.

A good domain model tells the reader:

How does this subscription start?
When is it renewed?
When can it not be canceled?
What does payment failure mean?
Where is the plan change policy?

If code can answer these questions, the code itself becomes a store of domain knowledge.

DDD is a way to preserve domain knowledge in code. I think this is a more important essence than the patterns.

Good questions create good design

If I had to close this series in one sentence, I would say this:

Good design starts from good questions.

DDD provides many concepts and patterns, but those patterns do not replace questions. Rather, they help us ask better questions.

Is this value enough as a primitive type, or is it a domain concept?
Does this object have identity and lifecycle?
Where should this rule be protected?
Inside which Aggregate should this change be consistent?
Is this event a meaningful event in the domain?
Within which boundary is this model valid?
Is this area really the Core Domain?

As we keep answering these questions, the model improves little by little. We cannot create a perfect model from the beginning, but we can move toward a better model.

Closing

DDD is not an answer. Applying DDD does not automatically create good design. Creating Entity, Value Object, Aggregate, and Repository does not mean we are doing Domain-Driven Design.

But DDD points in a good direction. It tells us to see the domain first. It tells us to leave the language of domain experts in code. It tells us to be conscious of model boundaries. It tells us to focus on important domains. And it tells us to keep refining the model.

The way I understand DDD is ultimately a way of conversation. Conversation with domain experts, conversation with teammates, conversation with code, and conversation with a model that keeps changing.

Good design is not completed at once. It is gradually made through the process of continuously asking good questions and honestly reflecting the answers in code. DDD is a useful language that guides that process.


Edit page
Share this post:

Next Post
[DDD Intro #29] DDD adoption checklist