Domain driven design 何时以及为什么我应该使用域服务?

Domain driven design 何时以及为什么我应该使用域服务?,domain-driven-design,Domain Driven Design,我有一个租赁实体,它是聚合根。除此之外,它还维护分配列表(保留的时间段) 如何添加新分配?由于租金是聚合根,任何新的分配都应该经过它,但在我们试图将分配保存到数据库中之前,无法确定是否可以分配租金。在此期间,另一个用户可能已经预订了它。我猜,我应该为此使用域服务吗 我不希望每次需要新的租赁时都必须注入任何东西,但是注入域服务(而不是存储库)与注入术语不同有什么区别?使用静态工厂方法创建租赁对象 public static class RentalFactory { public Rent

我有一个租赁实体,它是聚合根。除此之外,它还维护分配列表(保留的时间段)

如何添加新分配?由于租金是聚合根,任何新的分配都应该经过它,但在我们试图将分配保存到数据库中之前,无法确定是否可以分配租金。在此期间,另一个用户可能已经预订了它。我猜,我应该为此使用域服务吗


我不希望每次需要新的租赁时都必须注入任何东西,但是注入域服务(而不是存储库)与注入术语不同有什么区别?

使用静态工厂方法创建租赁对象

public static class RentalFactory
{
    public Rental CreateRental()
    {
        var allocationSvc = new RentalAllocationService();
        return new Rental(allocationSvc);
    }
}
存储库应该只关注底层存储的持久性。 域服务主要关注的是执行一些涉及实体或值对象的行为

何时以及为什么我应该使用域服务

您可以使用域服务来允许聚合运行查询。税务计算就是一个例子,它会不时出现。聚合将一些状态传递给计算器,计算器报告税收,聚合决定如何处理这些信息(忽略它,拒绝需要它的更新,等等)

运行查询不会以任何方式修改域服务实例,因此您可以随时重复查询,而无需担心计算会相互污染

想想只读服务提供商

由于租金是聚合根,任何新的分配都应该经过它,但在我们试图将分配保存到数据库中之前,无法确定是否可以分配租金。在此期间,另一个用户可能已经预订了它。我猜,我应该为此使用域服务吗

否-完全是错误的用例

如果分配是租金合计的一部分,则可以让租金合计创建自己的分配。您不需要为此提供服务(如果您喜欢分离关注点,您可以潜在地将工作委托给工厂)

如果“另一个用户可能同时保留了该分配”,那么就存在争用——两个用户试图同时更改相同的聚合。这通常通过以下两种方式之一进行管理

锁定:一次只允许一个用户修改租金合计。因此,在数据竞赛中,失败者必须等待胜利者完成,然后聚合可以拒绝失败者的命令,因为特定的分配已经完成

乐观并发:允许两个用户同时修改聚合的不同副本,但仅当原始状态不变时才允许保存。思考“比较和交换”;比赛在这两条指令之间的save中进行

state.compareAndSwap(originalState, loserState)
state.compareAndSwap(originalState, winnerState)
赢家的比较和交换成功,但输家的失败(因为originalState!=winnerState),因此输家的修改被拒绝

无论哪种方式,都只允许对数据库进行一次写入以保留分配


如果我理解正确的话,您是说在这种情况下,可以从租用域实体内部使用存储库

不,您不需要这样做-分配是租用聚合的一部分,由内存中的聚合创建,并在保存聚合时首次显示在数据存储中

如果所有重要的东西都必须提取到周围的代码或工厂中,那么为什么要使用聚合呢

这里的一些答案是关注点分离——聚合的主要关注点是强制执行业务不变量:确保创建具有特定状态的分配与正在进行的所有其他操作一致。工厂负责确保创建的对象正确连接


举个例子:工厂将负责在内存中创建分配,但不需要知道关于确保分配唯一性的任何信息。确保分配唯一性的规则由聚合描述和实施。

如果我理解正确,您是说在这种情况下,可以从租赁域实体内部使用存储库吗?我们正在运行这些服务的多个实例,因此在代码级别上的任何锁定都不会起作用。我可以研究乐观的并发性,但我的域模型感觉有缺陷。我对域实体的操作变得无关紧要,因为任何状态更改或返回的值都是猜测,直到周围的代码检查数据库为止。如果所有重要的东西都必须被提取到周围的代码或工厂中,为什么还要使用聚合呢?我仍然不完全确定我想如何处理我的具体问题,但现在我知道了什么不该做。谢谢。但是在这种情况下,检查然后保存是不可靠的。为了让租赁聚合返回任何可用的内容,它必须尝试将新的分配写入数据库,这意味着尝试持久化信息。否则,Rental.Allocate方法将只是“添加到内部列表并交叉手指”。您的系统是否如此庞大,以至于您需要负载平衡并运行域逻辑的各种实例?如果不是,那么每个客户机都将通过您的绑定上下文的同一实例进行租用,冲突可以在您的域逻辑代码中解决,而不受数据库写入的影响。是。我们使用的是微服务架构,因此系统设计为同时运行多个预订服务实例。