Why DDD Is Needed and How to Apply Domain-Driven Design in Front‑End Projects
The article explains the need for Domain‑Driven Design (DDD) to manage growing complexity caused by frequent requirement changes, describes DDD fundamentals such as bounded contexts and event storming, and shows how to structure React front‑end code, actions, reducers, and routing by organizing features to reduce coupling and improve maintainability.
Before answering why DDD is needed, the article outlines the typical software evolution process where frequent changes degrade quality, illustrating that software mirrors the complexity of the real world and that requirements inevitably evolve from simple to complex.
It argues that the root cause of rising project complexity is not the requirement changes themselves but the lack of timely decoupling and extensibility during those changes, and introduces DDD as a solution.
DDD (Domain‑Driven Design) is defined as a design approach that starts from extracting and transforming domain knowledge rather than focusing on database tables or services; real‑world entities, behaviors, and relationships become objects, methods, and associations in the model.
The article highlights the Single Responsibility Principle and the concept of Bounded Context, using the cell‑membrane metaphor to explain how contexts are isolated to avoid confusion and inconsistency.
It then outlines the general DDD workflow: establishing a ubiquitous language, conducting Event Storming workshops, and performing domain modeling to allocate models to bounded contexts and build a context map.
Applying DDD to front‑end projects, the article shows how typical React scaffold structures (components, actions, reducers) become tangled as features grow, and proposes organizing code by feature (domain model) to achieve high cohesion and low coupling.
Feature‑based organization includes:
Splitting the project by functional domains, placing related components, actions, and reducers together.
Further separating technical concerns within each feature (e.g., component, routing, Redux files).
Benefits include easier addition, refactoring, or removal of features without affecting others, and a more maintainable routing structure where each feature manages its own routes.
Examples of file structures and code snippets are provided. The component/action/reducer layout can be visualized as:
-components
component1
component2
-actions.ts
.allActions
-reducers.ts
.allReducersRouter organization is achieved by giving each feature its own route configuration and aggregating them at the top level via a JSON‑based routerConfig, as shown in the following snippets:
import { App } from '../features/home';
import { PageNotFound } from '../features/common';
import homeRoute from '../features/home/route';
import commonRoute from '../features/common/route';
import examplesRoute from '../features/examples/route';
const childRoutes = [
homeRoute,
commonRoute,
examplesRoute,
];
const routes = [{
path: '/',
component: App,
childRoutes: [
...childRoutes,
{ path: '*', name: 'Page not found', component: PageNotFound },
].filter(r => r.component || (r.childRoutes && r.childRoutes.length > 0))
}];
export default routes;And the rendering of the JSON routes into React Router:
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { ConnectedRouter } from 'connected-react-router';
import routeConfig from './common/routeConfig';
function renderRouteConfig(routes, path) {
const children = [];
const renderRoute = (item, routeContextPath) => {
let newContextPath;
if (/^\//.test(item.path)) {
newContextPath = item.path;
} else {
newContextPath = `${routeContextPath}/${item.path}`;
}
newContextPath = newContextPath.replace(/\/+/g, '/');
if (item.component && item.childRoutes) {
const childRoutes = renderRouteConfigV3(item.childRoutes, newContextPath);
children.push(
{childRoutes}
} path={newContextPath} />
);
} else if (item.component) {
children.push(
);
} else if (item.childRoutes) {
item.childRoutes.forEach(r => renderRoute(r, newContextPath));
}
};
routes.forEach(item => renderRoute(item, path));
return
{children}
;
}
function Root() {
const children = renderRouteConfig(routeConfig, '/');
return (
{children}
);
}The article concludes with references to DDD resources and tools such as Rekit for building scalable web applications.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.