有很多网友对这三者(Entities, Domain Services以及Repositories)有着疑惑,之前我也在我们的领域驱动设计QQ群里,解答了大家的疑惑,现在大致整理一下,供更多朋友参考。
领域服务与实体具有同等的地位
有些网友认为,仓储的相关操作应该放到领域服务中,而不应该放在实体中。认为实体是负责领域概念表述与业务逻辑处理的,而领域服务则是负责一些周边的工作,比如与位于技术架构层的外部持久化机制打交道等。其实这种观点是不正确的。经典DDD已经说得很明白,领域服务是为了处理那些无法从领域概念上归结为任何一个实体的业务逻辑,这是由通用语言引出的一种领域对象。简言之,领域服务也是需要负责领域概念表述与业务逻辑处理的,这一点与实体是完全相同的。于是,领域服务与实体具有同等地位,它们都是领域模型的组成部分。既然允许在领域服务中访问仓储,那么,在实体中直接访问仓储也是合理的。到底是将仓储访问的逻辑放在实体中合适,还是放在领域服务中合适?其实无所谓,根据个人喜好吧。
有朋友会问,仓储是技术架构上的事情,是与技术相关的,在领域模型中访问仓储,会不会打破领域模型的纯净性,使得领域模型中充斥着对基础结构层的访问,而这些内容本身是领域无关,技术相关的?其实这个问题早在我的《EntityFramework之领域驱动设计实践(七):模型对象的生命周期 - 仓储》一文中作过解释,可能有些网友没有仔细阅读文后【原文网友评论及回复信息】部分,在这部分中对上面这个问题做了解答:解决方案就是接口+IoC。有关这个问题的具体解释,请朋友们自己阅读该文。
仓储与事务
很多情况下,仓储的实现就是对外部存储机制(通常是关系型数据库)的技术实现,于是,处理事务在所难免。由于实体与领域服务都可以访问仓储,那么,事务处理的协调就应该在上层完成。领域驱动设计官方论坛推荐将事务处理放在应用(Application)层,这层的本职工作本身就是任务协调,不参与任何业务逻辑处理。将事务处理放在本层也算是合适的。这里有个问题需要进一步考虑,就是如何让实体或领域服务中访问仓储的方法(函数)能够限定在同一事务处理的上下文中,即需要实现类似下面的代码:
1: using (ITransactionContext ctx = IoC.GetService<ITransactionContext>()) 2: { 3: try 4: { 5: SalesOrder salesOrder = SalesOrder.Create(xxx); 6: customer.PlaceOrder(salesOrder); 7: ctx.Commit(); 8: } 9: catch 10: { 11: ctx.Rollback(); 12: } 13: }
而在这段代码中,customer.PlaceOrder方法应该知道当前的TransactionContext是哪个(如果需要知道的话),以便在同一个TransactionContext中所有与仓储相关的操作都被限定在这一相同的TransactionContext中。有兴趣的网友可以思考一下这个问题,我想,解决方案肯定是五花八门。
欢迎网友们就实体、领域服务以及仓储之间的关系进行更深一步的讨论。