架构设计前端工程化

DDD & 整洁架构
在前端的落地实践

领域驱动设计不只是后端专属!让我们探索如何将 DDD 核心思想与 Clean Architecture 结合, 构建真正可维护、可测试、框架无关的前端应用。

领域模型
分层架构
依赖反转
边界隔离
Why DDD?

前端为什么需要 领域驱动

前端常见痛点

  • !业务逻辑散落在组件、Hooks、Redux 各处
  • !一个需求改动,牵一发动全身
  • !代码无法复用,换框架就要重写
  • !难以编写有效的单元测试
  • !前后端概念不一致,沟通成本高

DDD 带来的改变

  • 业务逻辑集中在领域层,职责单一
  • 修改局限在特定层次,影响范围可控
  • 核心逻辑与框架解耦,迁移无痛
  • 纯函数式的领域对象,测试友好
  • 统一语言 (Ubiquitous Language) 消除隔阂

框架无关

核心业务逻辑独立于 React/Vue,可自由迁移

可测试性

纯函数式的领域逻辑,单元测试覆盖率极高

可维护性

职责清晰分离,修改一处不影响其他层次

团队协作

前后端共享领域模型语言,沟通更高效

Clean Architecture

整洁架构的 同心圆

依赖方向始终向内,外层依赖内层,内层对外层一无所知

Presentation
Infrastructure
Application
Domain
Domain领域层

核心业务逻辑,不依赖任何外部框架

Application应用层

用例编排,协调领域对象完成业务流程

Infrastructure基础设施层

框架适配、API 调用、状态管理实现

Presentation表现层

React 组件、Hooks、UI 状态

依赖方向:Presentation → Infrastructure → Application → Domain

Domain 层不依赖任何外部层!

DDD Patterns

DDD 核心 构建块

点击下方模式查看详细说明和代码示例

Entity

具有唯一标识的业务对象,身份标识贯穿生命周期

关键特征:有唯一 ID,可变状态,封装业务规则

entity.ts
class Order {
  constructor(
    private readonly id: OrderId,
    private items: OrderItem[],
    private status: OrderStatus
  ) {}

  // 业务逻辑封装在实体内部
  addItem(item: OrderItem): void {
    if (this.status !== 'draft') {
      throw new Error('只能修改草稿订单');
    }
    this.items.push(item);
  }

  get total(): Money {
    return this.items.reduce(
      (sum, item) => sum.add(item.subtotal),
      Money.ZERO
    );
  }
}
Project Structure

推荐的 目录结构

src/
src/
├─domain/核心
├─entities/
├─value-objects/
├─aggregates/
├─repositories/
├─services/
├─events/
├─application/用例
├─use-cases/
├─dto/
├─interfaces/
├─infrastructure/适配
├─api/
├─repositories/
├─mappers/
├─presentation/UI
├─components/
├─hooks/
├─pages/
domain/

纯业务逻辑,零框架依赖。Entity、Value Object、领域服务都在这里。这是整个应用的心脏。

禁止 import React/Redux/Axios 等任何外部库
application/

用例编排层,协调领域对象完成特定业务流程。每个 Use Case 通常对应一个用户操作。

只能 import domain 层
infrastructure/

实现领域层定义的接口(如 Repository),处理 API 调用、数据映射、第三方集成。

实现 domain 层接口,可使用任何库
presentation/

React 组件、Hooks、页面路由。组件通过调用 Use Case 与业务逻辑交互。

不直接操作领域对象,通过 DTO 通信
Interactive

交互实验场: 数据流向

点击按钮观察一个用户操作如何在各层之间流转

1Presentation

用户点击「添加商品」按钮

const addToCart = useAddToCartUseCase();

组件调用自定义 Hook,Hook 内部实例化 Use Case

2Application

AddToCartUseCase.execute()

await this.cartRepo.findById(cartId);

Use Case 编排业务流程:获取购物车 → 添加商品 → 保存

3Domain

ShoppingCart.addItem()

cart.addItem(product, quantity);

聚合根执行业务规则校验,维护一致性约束

4Infrastructure

ApiCartRepository.save()

await api.put('/carts/${id}', dto);

Repository 实现调用 API,DTO Mapper 转换数据格式

核心洞察

注意依赖方向:组件 → Use Case → Domain ← Repository(接口)
Domain 层完全不知道 HTTP、React、Redux 的存在!这就是依赖反转的力量。

Code Example

完整的 用例示例

add-to-cart.use-case.ts
// application/use-cases/
export class AddToCartUseCase {
  constructor(
    private cartRepo: CartRepository,
    private productRepo: ProductRepository,
    private eventBus: EventBus
  ) {}

  async execute(
    input: AddToCartInput
  ): Promise<AddToCartOutput> {
    // 1. 获取聚合根
    const cart = await this.cartRepo
      .findById(input.cartId);

    // 2. 获取商品信息
    const product = await this.productRepo
      .findById(input.productId);

    // 3. 调用领域逻辑
    cart.addItem(product, input.quantity);

    // 4. 持久化
    await this.cartRepo.save(cart);

    // 5. 发布领域事件
    await this.eventBus.publish(
      new ItemAddedToCartEvent(cart.id, product)
    );

    return {
      cartId: cart.id,
      totalItems: cart.itemCount,
      totalPrice: cart.total
    };
  }
}
useAddToCart.ts
// presentation/hooks/
import { useState } from 'react';
import { AddToCartUseCase } from '@/application';
import { useCartRepository } from '../providers';

export function useAddToCart() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error>();
  const cartRepo = useCartRepository();

  const addToCart = async (
    productId: string,
    quantity: number
  ) => {
    setLoading(true);
    setError(undefined);

    try {
      const useCase = new AddToCartUseCase(
        cartRepo,
        productRepo,
        eventBus
      );

      const result = await useCase.execute({
        cartId: getCurrentCartId(),
        productId,
        quantity
      });

      // 更新 UI 状态
      showNotification('已添加到购物车');
      return result;

    } catch (err) {
      setError(err as Error);
      showErrorToast(err.message);
    } finally {
      setLoading(false);
    }
  };

  return { addToCart, loading, error };
}
ProductCard.tsx
// presentation/components/
'use client';

import { useAddToCart } from '../hooks';

interface Props {
  product: ProductDTO;
}

export function ProductCard({ product }: Props) {
  const { addToCart, loading } = useAddToCart();

  const handleClick = () => {
    addToCart(product.id, 1);
  };

  return (
    <div className="product-card">
      <img src={product.image} alt="" />
      <h3>{product.name}</h3>
      <p className="price">
        ¥{product.price}
      </p>
      <button
        onClick={handleClick}
        disabled={loading}
      >
        {loading ? '添加中...' : '加入购物车'}
      </button>
    </div>
  );
}

// 组件只关心 UI 和用户交互
// 业务逻辑完全委托给 Use Case
Component
Hook
Use Case
Domain
Repository

开始你的 DDD 之旅

DDD 不是银弹,但对于复杂业务场景,它是让你的前端代码保持长期可维护性的最佳实践之一。 从一个小模块开始,逐步应用这些原则。

从领域模型开始
定义清晰边界
持续重构