六边形架构Port层请求和响应用的是 DTO吗
目录
背景和价值
在六边形架构中,Port 层(接口层)的请求和响应是否使用 DTO,取决于具体场景和设计选择,但通常建议遵循以下原则:
1. Port 接口的输入/输出是否用 DTO?
情况 1:输入端口(驱动端口)
- 定义:外部(如用户、API)调用应用核心的接口(如
OrderService.placeOrder())。 - 是否用 DTO:
✅ 推荐使用 DTO- 原因:
- 隔离外部请求与领域模型,避免污染核心逻辑。
- 适配不同外部协议(如 REST、GraphQL)时,DTO 可灵活转换。
- 示例:
// port 层接口(输入端口) public interface OrderService { OrderResponse placeOrder(OrderRequest request); // 使用DTO } // DTO 定义(通常放在 port 模块或独立模块中) public class OrderRequest { private String productId; private int quantity; // getters/setters } public class OrderResponse { private String orderId; private String status; // getters/setters }
- 原因:
情况 2:输出端口(被驱动端口)
- 定义:应用核心调用外部服务(如数据库、第三方API)的接口(如
OrderRepository.save())。 - 是否用 DTO:
⚠️ 视情况而定- 如果外部数据模型与领域模型差异大:
✅ 使用 DTO(如数据库表结构与领域对象不一致时)。public interface OrderRepository { void save(OrderDbEntity dbEntity); // 数据库专用DTO } - 如果外部模型与领域模型一致:
❌ 可直接传递领域对象(保持简洁)。public interface OrderRepository { void save(Order order); // 直接使用领域对象 }
- 如果外部数据模型与领域模型差异大:
2. DTO 应该放在哪个模块?
- port 模块:
如果 DTO 是接口契约的一部分(如 API 请求/响应),建议放在port模块。 - adapter 模块:
如果 DTO 是技术特定的(如 JPA 实体、Feign 客户端请求),则放在adapter模块。
3. 为什么推荐 Port 层使用 DTO?
- 解耦领域模型
- 避免外部请求参数(如 REST API 的 JSON)直接映射到领域对象,防止非法状态污染业务逻辑。
- 协议独立性
- 同一接口可适配不同协议(如 REST 和 gRPC),DTO 可分别转换。
- 安全控制
- DTO 可隐藏敏感字段(如数据库主键、内部状态),仅暴露必要数据。
4. 反例:不用 DTO 的问题
// 反例:直接使用领域对象作为接口参数
public interface OrderService {
void placeOrder(Order order); // Order 是领域对象
}
- 问题:
- 外部调用者可能绕过业务规则,构造非法的
Order对象。 - API 变更会直接影响领域模型。
- 外部调用者可能绕过业务规则,构造非法的
5. 完整示例
port 模块
// 输入端口接口 + DTO
public interface OrderService {
OrderResponse placeOrder(OrderRequest request);
}
// 输出端口接口(直接使用领域对象)
public interface OrderRepository {
Order findById(OrderId id);
}
adapter 模块
// REST 适配器(输入适配器)
@RestController
public class OrderController implements OrderService {
@Override
@PostMapping("/orders")
public OrderResponse placeOrder(@RequestBody OrderRequest request) {
// 将 DTO 转换为领域对象
Order order = convertToDomain(request);
// 调用领域逻辑...
return convertToResponse(order);
}
}
// 数据库适配器(输出适配器)
@Repository
public class JpaOrderRepository implements OrderRepository {
@Override
public Order findById(OrderId id) {
OrderEntity jpaEntity = jpaRepo.findById(id);
return convertToDomain(jpaEntity); // 数据库DTO转领域对象
}
}
6. 总结
| 场景 | 是否用 DTO | 推荐位置 |
|---|---|---|
| 输入端口(API 请求/响应) | ✅ 必须 | port 模块 |
| 输出端口(数据库访问) | 视情况(模型差异大时用) | adapter 模块 |
| 内部领域逻辑传递 | ❌ 不用 | domain 模块 |
通过合理使用 DTO,能严格隔离六边形架构的各层,保持核心业务与技术细节的解耦。

浙公网安备 33010602011771号