DDD中的Assembler(装配器):对象转换的桥梁
在领域驱动设计(DDD)中,Assembler(装配器) 是负责不同层对象之间转换的组件,主要作用是在领域对象(如实体、值对象)与数据传输对象(DTO)、数据库实体(DO)等不同类型对象之间进行属性映射和转换,同时保持各层之间的解耦。
Assembler的核心价值
在DDD分层架构中,不同层次使用的对象模型有着不同的职责:
- 领域层使用领域对象(实体、值对象),包含业务逻辑和规则
- 应用层和接口层使用DTO(数据传输对象),用于数据传递
- 基础设施层使用数据库实体(DO),用于数据持久化
Assembler的核心价值在于:
- 隔离不同层的对象模型,避免层与层之间的紧耦合
- 集中管理对象转换逻辑,提高代码复用性
- 隐藏对象转换的复杂细节,使各层聚焦于自身职责
Assembler的主要职责
-
领域对象与DTO之间的转换
- 将领域对象(实体/值对象)转换为DTO,供应用层和接口层使用
- 将DTO转换为领域对象或命令对象,供领域层处理
-
领域对象与数据库实体(DO)之间的转换
- 将领域对象转换为数据库实体,用于持久化
- 将数据库实体转换为领域对象,用于业务处理
-
处理复杂对象关系
- 处理聚合对象的嵌套转换
- 处理集合类型对象的批量转换
- 处理不同命名规范和数据类型的映射
Assembler的实现方式
1. 手动实现的Assembler
最常见的方式是创建专门的Assembler类,手动编写对象转换逻辑:
// 订单领域对象
public class Order {
private OrderId id;
private UserId userId;
private Money totalAmount;
private List<OrderItem> items;
private OrderStatus status;
// 业务方法...
}
// 订单DTO
public class OrderDTO {
private String orderId;
private String userId;
private BigDecimal totalAmount;
private String currency;
private List<OrderItemDTO> items;
private String status;
// getter和setter...
}
// 订单Assembler
public class OrderAssembler {
// 领域对象转DTO
public OrderDTO toDTO(Order order) {
OrderDTO dto = new OrderDTO();
dto.setOrderId(order.getId().getValue());
dto.setUserId(order.getUserId().getValue());
dto.setTotalAmount(order.getTotalAmount().getAmount());
dto.setCurrency(order.getTotalAmount().getCurrency().getCode());
dto.setStatus(order.getStatus().getName());
dto.setItems(order.getItems().stream()
.map(this::toOrderItemDTO)
.collect(Collectors.toList()));
return dto;
}
// DTO转领域对象
public Order toDomain(OrderDTO dto) {
Order order = new Order(
new OrderId(dto.getOrderId()),
new UserId(dto.getUserId()),
new Money(dto.getTotalAmount(), Currency.of(dto.getCurrency())),
OrderStatus.fromName(dto.getStatus())
);
// 添加订单项
if (dto.getItems() != null) {
dto.getItems().forEach(itemDTO ->
order.addItem(toOrderItem(itemDTO))
);
}
return order;
}
// 订单项转换(辅助方法)
private OrderItemDTO toOrderItemDTO(OrderItem item) {
// 转换逻辑...
}
private OrderItem toOrderItem(OrderItemDTO itemDTO) {
// 转换逻辑...
}
}
2. 使用工具类的Assembler
可以利用对象映射工具(如MapStruct、ModelMapper)简化转换逻辑:
// 使用MapStruct注解的Assembler
@Mapper(componentModel = "spring")
public interface OrderAssembler {
OrderAssembler INSTANCE = Mappers.getMapper(OrderAssembler.class);
@Mapping(source = "id.value", target = "orderId")
@Mapping(source = "userId.value", target = "userId")
@Mapping(source = "totalAmount.amount", target = "totalAmount")
@Mapping(source = "totalAmount.currency.code", target = "currency")
@Mapping(source = "status.name", target = "status")
OrderDTO toDTO(Order order);
@Mapping(source = "orderId", target = "id.value")
@Mapping(source = "userId", target = "userId.value")
@Mapping(target = "totalAmount", expression = "java(new Money(dto.getTotalAmount(), Currency.of(dto.getCurrency())))")
@Mapping(source = "status", target = "status", qualifiedByName = "statusFromString")
Order toDomain(OrderDTO dto);
@Named("statusFromString")
default OrderStatus statusFromString(String status) {
return OrderStatus.fromName(status);
}
}
Assembler的设计原则
-
单一职责原则:一个Assembler只负责一类对象的转换(如OrderAssembler只处理订单相关对象)
-
无业务逻辑原则:Assembler只负责属性映射,不包含业务逻辑判断
-
双向转换能力:通常需要实现正向(领域对象→DTO)和反向(DTO→领域对象)转换
-
处理空值安全:考虑源对象或属性为空的情况,避免空指针异常
-
命名规范:通常以"对象名+Assembler"命名(如OrderAssembler、UserAssembler)
Assembler与其他组件的关系
-
与Repository:Assembler常被Repository实现类使用,用于领域对象和数据库实体的转换
-
与Application Service:应用服务使用Assembler在领域对象和DTO之间转换
-
与Controller:控制器可能直接使用Assembler将DTO转换为命令对象,或将结果DTO返回给前端
何时需要使用Assembler
-
当对象转换逻辑复杂,包含嵌套对象或特殊转换规则时
-
当需要在多个地方复用相同的转换逻辑时
-
当希望保持领域对象的纯净性,不包含与其他对象的转换方法时
对于简单的对象转换,有时也会直接在DTO中实现fromDomain()
和toDomain()
方法,但对于复杂场景,专门的Assembler是更好的选择。
总之,Assembler在DDD中扮演着对象转换的桥梁角色,通过它可以有效隔离不同层次的对象模型,保持各层的独立性和内聚性。