前端开发者学习DDD:API调用应置于架构何处?
DDD中前端API调用的位置划分
核心原则:外部请求归属于基础设施层
所有和外部系统(包括后端API)的交互,都应该封装在基础设施层——目的是彻底隔离领域逻辑与外部依赖,让领域层只聚焦业务规则,无需关心数据的传输方式或来源渠道。
具体战术设计范畴的划分
1. 用于领域数据的持久化/查询 → 仓库(Repository)
如果API是用来获取、保存或更新领域实体的数据(比如拉取用户信息、提交订单数据),就把API调用封装在仓库实现中:
- 领域层只定义仓库的接口契约(比如
UserRepository),声明getUserById(id)、saveUser(user)等方法; - 基础设施层实现这个接口,在实现逻辑里调用对应后端API,同时完成API返回数据与领域实体的双向转换。
- 示例代码:
// 领域层 - 仓库接口(仅定义契约) class UserRepository { getUserById(id) { throw new Error("需在基础设施层实现"); } } // 基础设施层 - 仓库实现(封装API调用) class ApiUserRepository extends UserRepository { async getUserById(id) { const response = await fetch(`/api/users/${id}`); const data = await response.json(); // 转换为领域实体 return new User(data.id, data.name, data.email); } }
2. 作为跨实体业务逻辑的一部分 → 领域服务(Domain Service)
如果API调用是某个跨实体业务操作的必要环节(比如调用支付API完成订单支付,涉及订单、用户、支付系统三方逻辑),则在领域服务中依赖基础设施层的API封装:
- 领域服务本身不直接写API调用代码,而是通过依赖注入使用基础设施层提供的服务(比如
PaymentGateway),API调用的具体逻辑由基础设施层实现。 - 示例代码:
// 基础设施层 - 支付网关实现(封装API调用) class ApiPaymentGateway { async processPayment(orderId, amount) { const response = await fetch("/api/payments", { method: "POST", body: JSON.stringify({ orderId, amount }) }); return response.ok; } } // 领域层 - 订单领域服务 class OrderService { constructor(paymentGateway) { this.paymentGateway = paymentGateway; } async completeOrder(order) { if (!order.canComplete()) { throw new Error("订单无法完成"); } // 调用基础设施层的支付服务 const paymentSuccess = await this.paymentGateway.processPayment(order.id, order.totalAmount); if (paymentSuccess) { order.markAsCompleted(); } else { throw new Error("支付失败"); } } }
3. 用于应用层流程编排 → 应用服务(Application Service)
如果API调用是为了协调多个领域操作,或是处理用户交互的整体流程(比如页面加载时,先拉取用户信息,再拉取该用户的订单列表),则在应用服务中调用基础设施层的API封装:
- 应用服务负责串联业务流程,不包含核心领域逻辑,仅作为领域组件与外部服务的协调者。
关键避坑点
- 绝对不要在**实体(Entity)**中直接编写API调用——实体只应包含自身的业务属性与内聚行为,不能依赖任何外部系统;
- 基础设施层需与领域层解耦,通过依赖注入的方式向领域层提供实现,后续若更换API类型(比如从REST切换到GraphQL),只需修改基础设施层代码,无需改动核心领域逻辑。
内容的提问来源于stack exchange,提问作者Daniel Benisti
相关产品推荐
相关产品推荐

