DDD does not start with a `domain` folder
Eric Evans presents DDD as a body of patterns and definitions for working with domain models, while Martin Fowler emphasizes modeling a complex domain and evolving a language shared by developers and domain experts. [1][2][4]
A `domain/application/infrastructure` directory structure therefore does not make an application DDD. A codebase can have perfectly named folders and still contain only DTOs and procedural logic. Strategic DDD can also be applied without a canonical layered folder structure.
Strategic DDD and Tactical DDD operate at different levels
Strategic DDD deals with decomposing a larger domain into subdomains and Bounded Contexts and defining relationships between them. Microsoft describes domain analysis as identifying subdomains and bounded contexts, while Fowler identifies Bounded Context as a central strategic DDD pattern. [3][7]
Tactical DDD works inside a specific context and includes patterns such as Entities, Value Objects, Aggregates, and Domain Services. Microsoft explicitly separates strategic analysis from tactical modeling. [8]
In frontend work, it is often better to start with strategic design and decide later whether a particular context actually needs a rich tactical model.
A Bounded Context is not a technical layer
Fowler describes a Bounded Context as a boundary within which a model remains internally consistent and terms have unambiguous meaning. The same concept, such as `Customer` or `Product`, can legitimately have different meanings in different contexts. [3]
This matters in frontend architecture: `frontend`, `backend`, `React app`, `route`, and `micro-frontend` are not automatically Bounded Contexts. Context boundaries come from domain models and language rather than technology. Frontend DDD practitioners make the same caution in practical guidance. [15][16]
When DDD in frontend makes sense
| Signal | Direction | Why |
|---|---|---|
| Many changing business rules | DDD | A domain model reduces scattered rule logic |
| The same concepts mean different things across the product | DDD | Bounded Contexts support several coherent models |
| Several teams and domain experts collaborate | DDD | Ubiquitous Language reduces ambiguity |
| Simple CRUD and forms | Simpler modularity | Tactical DDD overhead can exceed its benefit |
| Frontend mostly mirrors an API | Simpler modularity | There is little client-side domain logic to model |
| Small application with one coherent domain | Simpler modularity | Extra boundaries and layers may provide little value |
The strongest signal is complex and frequently changing business behavior visible on the client: configurators, multi-step processes, pricing, permissions, state transitions, workflows, or different models of the same concept in separate product areas.
DDD is also valuable when several teams work on a large product and the same words mean different things in different areas. Bounded Contexts are specifically intended to manage multiple coherent models and teams. [3]
DDD Crew recommends first understanding the domain, identifying strategically important subdomains, then defining bounded-context responsibilities, and only after that coding the model. [10]
When DDD becomes overengineering
If the frontend mainly fetches data, renders it, edits forms, and sends straightforward CRUD commands with little client-side business behavior, the full set of tactical DDD patterns usually does not solve a meaningful problem.
Microsoft explicitly notes that for a simple CRUD context an anemic data model may be sufficient and richer DDD patterns may not be worth the cost. A richer model becomes more valuable when there are many changing business rules. [9]
The same principle applies to frontend architecture: match the sophistication of the design to domain complexity, not to the team's architectural ambition.
Ubiquitous Language should be visible in UI code too
Ubiquitous Language is a rigorous shared language built by developers and domain experts around the model. Fowler stresses that the language should evolve as the team's understanding of the domain grows. [4]
In frontend code, names for use cases, actions, types, modules, screens, and states should therefore use business vocabulary whenever they represent domain behavior.
If the business says `approveApplication` while the frontend uses names such as `setFlag2` or `handleData`, the code stops acting as an executable representation of the domain language.
Organize a large frontend by domains, not only by file types
Fowler notes that top-level `view/model/data` style layering may be adequate for smaller systems, but as an application grows, top-level modules should often become domain-oriented and contain their own internal layers. [5]
Current Nx documentation follows a similar direction: folders can become domain ownership boundaries, while libraries inside a domain can be classified as `feature`, `ui`, `data-access`, and `util`. [12]
A practical structure can therefore look like `libs/orders/...`, `libs/billing/...`, and `libs/identity/...` rather than one global `components/`, `services/`, and `models/` tree.
Separate presentation behavior from domain behavior
Fowler describes separating presentation, domain logic, and data access as an effective modularization technique. He also notes that UI code is often harder to test, which makes keeping domain logic outside presentation valuable. [5][6]
In a frontend, a component should primarily render, handle UI events, and delegate operations. Rules such as whether an order can be cancelled, how a discount is calculated, or which state transition is valid should have an explicit home outside JSX, templates, or component classes.
This does not mean presentation logic is forbidden in components. View validation, visibility, local focus, and animation behavior are different from business rules.
A practical layered structure inside one frontend context
| Layer | Responsibility | Example elements |
|---|---|---|
| presentation / ui | Rendering and interface behavior | components, routes, view state |
| application | Use-case coordination | commands, use cases, orchestration |
| domain | Domain rules, concepts, and invariants | entities, value objects, policies |
| infrastructure / data-access | Technical integrations | HTTP, storage, SDKs, mappers |
DDD does not mandate one folder structure. Separating `presentation`, `application`, `domain`, and `infrastructure` is a practical interpretation of responsibility separation, not a formal Evans requirement. [1][5]
The domain layer can contain framework-independent rules, concepts, and behavior; application can coordinate use cases; infrastructure can adapt HTTP, storage, and external SDKs; presentation can contain components, routing, and purely interface state.
The API model and frontend domain model do not have to be identical
A Bounded Context may have its own model of a concept, and different contexts can map between different representations. Fowler uses concepts such as `Customer` and `Product` as common examples of terms whose meanings vary across contexts. [3]
That means a backend DTO does not have to flow directly into every component. A mapper or adapter can transform the transport contract into the model used by a particular frontend context.
This boundary is most valuable when an API serves multiple clients, is legacy, combines several domains, or evolves independently. For a simple CRUD endpoint, an extra mapping layer may be unnecessary.
Entities, Value Objects, and Aggregates: use them selectively
DDD distinguishes concepts including Entities, Value Objects, Services, and Aggregates. Fowler highlights these as part of Evans's DDD vocabulary, while Microsoft describes aggregates as a tactical pattern for maintaining model consistency. [2][8]
A frontend should not copy a backend domain model one-to-one merely to have the same classes. A Value Object can be useful for `Money`, `DateRange`, or `Email` when it enforces invariants. An Aggregate makes sense only if the client really needs to preserve consistency rules for that model.
If a type is only data for rendering a table, a plain TypeScript type may be better than an entity, factory, repository, and service layer.
UI state is not the same as domain state
React guidance treats state organization as a design concern and recommends avoiding redundant or duplicated state. That is useful technical guidance, but reducers, stores, or signals do not themselves create a domain model. [14]
State such as `isModalOpen`, the active tab, or scroll position belongs to presentation. State representing an order lifecycle, allowed process transitions, or configuration rules may belong to the domain model.
Separating the two reduces global stores that mix server data, business behavior, and UI details into one undifferentiated state container.
Boundaries should be enforced, not only documented
Nx lets teams define project tags and declarative dependency constraints, such as blocking imports between scopes or library types. The `@nx/enforce-module-boundaries` rule can check imports during linting. [11]
Nx also supports multiple tag dimensions, allowing teams to model `scope`, library type, stability, client/server concerns, and other boundaries independently. [13]
For frontend DDD, architectural decisions can therefore become CI rules: `billing` cannot import `identity` internals, `ui` cannot depend on `data-access`, and `domain` can be kept framework-independent.
Frontend and backend can share a domain without sharing an identical model
A Bounded Context is a model and language boundary, so it should not be drawn automatically along the network boundary between browser and server. Practical frontend DDD guidance emphasizes that contexts can cut across technical layers and be implemented jointly by frontend and backend. [15][16]
At the same time, the client-side representation may differ from the server-side representation because interaction, local state, and presentation needs differ. The important part is preserving semantics and mapping explicitly rather than copying classes.
A micro-frontend should also not be created merely because a Bounded Context exists. A domain boundary and a deployable-artifact boundary are separate design decisions.
Testing is one of the strongest reasons to isolate domain logic
Fowler identifies testability as a benefit of separating presentation from the domain. Logic outside the UI can be tested without rendering components or depending on interface details. [5][6]
For frontend code, that enables fast tests of rules, Value Objects, use cases, and state transitions as ordinary TypeScript or JavaScript. Component tests are still needed, but they do not have to be the only place where business rules are verified.
A warning sign is when every business-rule change requires mounting a complete component and mocking the router, store, HTTP layer, and browser APIs.
How to introduce DDD into an existing frontend without a big rewrite
DDD Crew recommends an iterative process: understand the domain, identify important subdomains, define bounded-context responsibilities, and then code the model. [10]
In an existing frontend, a safer approach is to select one painful business area, name its language, establish a boundary, separate presentation from rules, define the module's public API, and only then move additional features.
The whole application does not need to be migrated. DDD can be applied where business complexity pays back the modeling cost, while simpler areas remain straightforward feature modules.
- Start with a business problem, not with folders.
- Identify terms, rules, and ambiguous concepts.
- Define one Bounded Context and its public contract.
- Separate domain rules from components and transport DTOs.
- Add only tactical patterns that solve concrete complexity.
- Encode boundaries as linting or CI rules.
- Measure dependencies, cycles, regressions, and cross-cutting change cost.
- Do not migrate simple CRUD areas merely for architectural symmetry.

