在DDD实践过程中,我发现领域对象和持久化对象之间存在不匹配的情况。我理解DDD建议领域对象只暴露关键字段和领域方法,状态的改变是通过构造函数或领域方法来完成的。
但是对于存储库的实现来说,我们从数据库中获取的是持久化对象PO。当我将这个PO转换为领域对象DO时,我发现我无法在DO中设置私有字段。也许调用领域方法来设置是可行的,但是如果领域方法还有其他操作,比如发送事件怎么办?当我恢复时无法触发事件的发送,这里的最佳实践是什么?
我们存储的实现是基于mybatis,为了降低复杂度,暂时不引入事件溯源,目前团队无法控制。
// simple domain object
@Getter
public class Order extends AggregateRoot<Order> {
private Long id;
private String orderSn;
private Long userId;
private BigDecimal totalAmount;
private BigDecimal paidAmount;
private OrderStatus status = OrderStatus.CREATED;
private String address;
private Date orderTime;
private List<OrderLine> lines = new ArrayList<>();
public void payOrder() {
this.status = OrderStatus.PAID;
publish(OrderPaidEvent)
}
//... other domain methods
}
// and repository impl
public Order findById(long id) {
DemoOrderPo orderPo = demoOrderMapper.selectById(id);
Wrapper<DemoOrderLinePo> query = Wrappers.<DemoOrderLinePo>lambdaQuery()
.eq(DemoOrderLinePo::getOrderId, id);
List<DemoOrderLinePo> orderLinePoList = demoOrderLineMapper.selectList(query);
Order order = new Order();
// can not set private fields, what can i do in best practise?
}
通常的答案是您通常会使用“初始化程序” - 也就是说您将有一个构造函数,它接受您需要的信息作为参数,并且构造函数的主体将分配私有成员。
Order(/* your args here */) {
this.id = // ...
this.orderSn = // ...
// and so on
}
本质上,初始化器是一个工厂方法,具有分配数据成员的特殊权限,否则这些数据成员将无法访问。
从那里,您通常会使用某种工厂方法、构建器或解析器来转换从持久存储中接收的通用数据结构,以创建需要传递给初始化器的参数。
在您的示例中,您已经拥有生成 DTO 的方法,您只需要弥合 DTO 和初始化参数之间的差距
public Order findById(long id) {
DemoOrderPo orderPo = demoOrderMapper.selectById(id);
Wrapper<DemoOrderLinePo> query = Wrappers.<DemoOrderLinePo>lambdaQuery()
.eq(DemoOrderLinePo::getOrderId, id);
List<DemoOrderLinePo> orderLinePoList = demoOrderLineMapper.selectList(query);
return makeMeAnOrder(id, orderPo, orderLinePoList)
}
Order makeMeAnOrder(long id, DemoOrderPo orderPo, List<DemoOrderLinePo> orderLinePoList) {
// ....
return new Order(
id,
orderSn,
userId,
// ....
)
}
//现在我可能会这样做,但这样我会感到痛苦。
是的,这太恶心了。有时我们会陷入其他地方做出的错误设计决策,而在新设计中做出一些妥协是解决实际问题所需的大量返工的更好选择。
所以我们就这么做了。但我们不必为此感到高兴。
现在我可能会这样做,但这样我会感到痛苦。
// domain object
@Getter
@Setter(AccessLevel.PROTECTED)
public class Order extends AggregateRoot<Order> {
}
// repository impl
class MyBatisOrderRepository {
public Order findById(long id) {
DemoOrderPo orderPo = demoOrderMapper.selectById(id);
OrderInner order = new OrderInner();
order.setId(order.getId());
order.setStatus(OrderStatus.of(orderPo.getStatus()));
// ... other set method
return order;
}
private static class OrderInner extends Order {
@Override
protected void setId(Long id) {
super.setId(id);
}
@Override
protected void setStatus(OrderStatus status) {
super.setStatus(status);
}
// ...
}
}
我认为可以使用java反射工具来完成