You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DTO Assembler与Factory的区别及聚合创建最佳实践咨询

DDD分层下跨层创建聚合的标准实现

你对两个核心组件的职责认知是准确的:

  • 领域层Factory的核心定位是封装复杂聚合的创建逻辑,保证返回的聚合实例始终满足业务不变式,不能依赖应用层的任何数据结构
  • 应用层DTO Assembler的原生职责是做已构建完成的领域对象与出/入参DTO的结构转换,不应该承载聚合创建的业务校验逻辑

核心实现思路

核心原则非常明确:层间依赖只能是外层依赖内层,内层绝对不能感知外层的存在,参数适配逻辑放在外层完成,核心创建逻辑收敛在内层Factory,落地分3步走:

1. 应用层完成入参适配,不向领域层透传DTO

入参DTO是应用层/接口层的专属数据结构,本身可能携带序列化注解、前端参数校验注解、冗余透传字段等和领域逻辑无关的内容,绝对不能传到领域层。
你只需要在应用服务的业务方法里,先完成应用层维度的参数校验(比如手机号格式、字段非空这类和核心业务规则无关的入参合法性校验),再把DTO里创建聚合需要的有效字段拆解出来,转换成领域层定义的基础类型、值对象,再传给领域层Factory即可。
举个订单聚合创建的落地代码示例:

// 应用服务层代码
public OrderDTO create(CreateOrderDTO request) {
    // 应用层参数校验:只做格式、非空这类无业务含义的入参校验
    checkRequestValid(request);
    
    // 拆解DTO,转换为领域层定义的类型,没有任何DTO依赖
    UserId buyerId = new UserId(request.getBuyerId());
    DeliveryAddress deliveryAddress = DeliveryAddress.builder()
            .province(request.getProvince())
            .city(request.getCity())
            .district(request.getDistrict())
            .detail(request.getDetailAddress())
            .contactName(request.getReceiverName())
            .contactPhone(request.getReceiverPhone())
            .build();
    List<OrderItemCreateParam> itemParams = request.getSkuList().stream()
            .map(sku -> new OrderItemCreateParam(sku.getSkuId(), sku.getPurchaseCount(), sku.getActualUnitPrice()))
            .toList();
    
    // 调用领域层Factory创建聚合,传入的所有参数都是领域层自身定义的类型
    Order orderAggregate = OrderFactory.create(buyerId, deliveryAddress, itemParams, request.getOrderRemark());
    
    // 后续持久化、通过Assembler转换为出参DTO返回
    orderRepository.save(orderAggregate);
    return orderAssembler.toDTO(orderAggregate);
}

这一步的参数拆解本质是外层的适配工作,不属于核心业务逻辑,完全不需要塞给Assembler或者Factory,放在应用服务里是最合理的。

2. 领域层Factory收敛所有创建相关的核心业务规则

Factory不需要关心入参来自HTTP接口的DTO、MQ消息还是数据库重建,它只识别领域层自身定义的参数类型,只负责和聚合核心规则相关的校验与初始化:比如订单条目不能为空、收货地址信息满足配送范围要求、订单初始状态为「待支付」、订单总金额计算和明细金额一致这类核心业务不变式,校验通过后返回完整、合法的聚合实例。

3. 多参数场景的优化方案

如果创建聚合需要的参数过多,直接在Factory方法上传递一长串参数可读性很差,可以选择两种合规的优化方式,都不会违反分层规则:

  • 在领域层定义专属的聚合创建命令对象(比如CreateOrderCommand):注意这个Command是定义在领域层的模型,和应用层DTO有本质区别——它没有任何应用层专属的注解、依赖,只承载创建聚合需要的业务字段和基础字段约束,应用层拆解DTO后组装成这个Command传给Factory即可。
  • 对高内聚的参数组抽值对象:比如把收货地址相关的7、8个字段封装为DeliveryAddress值对象,把优惠相关的字段封装为OrderDiscount值对象,既减少参数个数,也符合领域模型的抽象原则。

常见错误做法避坑

  • 不要为了省事把DTO传到领域层:哪怕给DTO改个名字,只要它定义在应用层、携带应用层专属逻辑,就会导致领域层产生对外层的依赖,后续如果入参来源从HTTP换成MQ、RPC,就要改动领域层代码,完全违背DDD隔离核心领域逻辑的初衷。
  • 不要让DTO Assembler承担聚合创建职责:Assembler的定位是无业务逻辑的结构转换器,如果在Assembler里嵌入聚合创建、业务校验逻辑,会导致核心业务规则散落在转换层,后续维护时根本找不到逻辑入口。
  • 不要在应用服务里直接new聚合:聚合创建往往包含大量默认值初始化、业务规则校验逻辑,直接散落在应用服务里会导致领域逻辑泄露,后续其他场景(比如从数据库重建聚合、从历史数据迁移重建聚合)要重复编写创建逻辑,很容易出现规则不一致的bug。

内容的提问来源于stack exchange,提问作者NiYanchun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 13:57:08