架构设计前端工程化
DDD & 整洁架构
在前端的落地实践
Why DDD?
前端为什么需要 领域驱动?
Clean Architecture
整洁架构的 同心圆
依赖方向始终向内,外层依赖内层,内层对外层一无所知
Presentation
Infrastructure
Application
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/
Interactive
交互实验场: 数据流向
点击按钮观察一个用户操作如何在各层之间流转
核心洞察
注意依赖方向:组件 → 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 CaseComponent
Hook
Use Case
Domain
Repository
开始你的 DDD 之旅
DDD 不是银弹,但对于复杂业务场景,它是让你的前端代码保持长期可维护性的最佳实践之一。 从一个小模块开始,逐步应用这些原则。
从领域模型开始
定义清晰边界
持续重构