Browse narratives

EN · · 3 min read

DDD Is Not Folder Structure

Why domain-driven design begins with language and boundaries rather than Entities, Services, and Repositories folders.

Engineering EssayIntermediateEvergreen

Four architecture folders do not make a domain model. A solution can contain Domain, Application, Infrastructure, and Api projects while its business rules remain scattered across controllers, database constraints, and conditionals that nobody can explain in domain language.

Domain-driven design begins before that structure. It begins when a team tries to understand which problems belong together, what its words mean, where those meanings stop, and which rules must remain true.

Start with language that can disagree

Words reveal boundaries. “Account” may mean a login identity in one part of a system, a billing relationship in another, and a ledger container somewhere else. Forcing those meanings into one universal class creates a model that is technically shared and conceptually unstable.

A ubiquitous language is not a glossary written once. It is the vocabulary used in conversations, examples, tests, APIs, and code inside one context. When the same phrase produces different rules, that tension is evidence that the model may cross a boundary.

A bounded context owns meaning

A bounded context defines where a model applies. It is a semantic boundary before it is a deployment boundary. Two contexts may describe the same real thing differently because they need different facts and enforce different rules.

flowchart LR Identity["Identity context<br/>account = login principal"] Billing["Billing context<br/>account = commercial relationship"] Audit["Audit context<br/>account = attributed actor"] Identity -->|"stable subject ID"| Billing Identity -->|"actor claim"| Audit

The contexts exchange explicit contracts rather than sharing one mutable Account model. They may initially live in the same process. Splitting them into services is a separate operational decision.

Behavior belongs near the invariant

An anemic model stores data while application services reconstruct every rule. Moving all logic into entities is not automatically better; some policies span several objects or depend on external information. The useful question is where a rule can be expressed with the fewest invalid intermediate states.

public sealed class Membership
{
    public MembershipStatus Status { get; private set; }

    public void Suspend(SuspensionReason reason)
    {
        if (Status is MembershipStatus.Closed)
            throw new InvalidOperationException(
                "A closed membership cannot be suspended.");

        Status = MembershipStatus.Suspended;
    }
}

The snippet is illustrative and still requires tests. Its point is that the transition protects its invariant. A controller should not be able to assign an arbitrary status and bypass the rule.

Repositories do not create a domain

A repository can present aggregate-oriented persistence operations, but wrapping every table in IRepository<T> only hides the database behind generic vocabulary. Query needs, transaction boundaries, and aggregate ownership remain.

Use a repository when it protects domain language or a consistency boundary. Use a direct query abstraction when the job is projection. Avoid returning a composable database query from a boundary that claims to hide persistence.

Folder structure follows change pressure

Once the model has meaningful boundaries, code organization can make them visible. A feature or bounded-context structure often communicates more than global technical buckets:

Authorization/
  Policies/
  Decisions/
  Evaluation/
  Persistence/

This is not a required DDD layout. It is one consequence of organizing code around language and change.

DDD has a cost

Domain modeling requires access to knowledge, repeated conversations, and the patience to revise names. For a small CRUD surface with stable rules, a rich model may cost more than it returns. For a domain where policy changes, definitions conflict, and invalid transitions are expensive, the investment can make change safer.

The folder tree is the residue of those decisions, not their source.

Further reading