EntityFramework之领域驱动设计实践【扩展阅读】:服务(Services)
服务(Services)
从本讲开始,所涉及的DDD话题可能与EntityFramework关系不大了。网友千万别骂我是标题党,呵呵。由于这部分内容并非是特定于EntityFramework的,更多的是在介绍模式及实践心得,所以EntityFramework的内容就会偏少了。为了使得针对一些话题的讨论能够延续下去,我仍然将这些文章安排在本系列中,希望读者朋友能够谅解。我也在标题中标注了【扩展阅读】,表示所讨论的内容已经不仅仅局限于EntityFramework了。
为了表示补偿,透露一下EntityFramework 4.0的最新特性:EF CTP 4.0在“代码优先”开发模式以及提升开发生产率方面做了重要改进。EF CTP 4.0引入了两种新的类型:DbContext和DbSet。DbContext是ObjectContext的简化版。详情请见http://www.infoq.com/news/2010/07/EF-CTP-4。
言归正传,本文将对DDD中的又一重要角色:服务(Services)做一些简单的介绍。提起服务,很多朋友都会想到“SOA”。而在领域驱动设计里,服务贯穿于整个系统的各个层面。根据应用系统的领域驱动分层思想,服务被归类为:应用层服务、领域服务以及基础结构层服务。应用层服务为表现层提供接口,根据DDD的思想,应用层很薄,不承担任何业务逻辑的处理,仅仅是起到coordination的作用。因此,应用层服务也不会牵涉到业务逻辑。在CQRS模式中,Command Service以及Query Service就是应用层服务。基础结构层服务是显而易见的,比如,邮件发送服务、数据服务、事件总线等等。这些服务是与领域无关的,只跟技术实现相关。假想这样一个例子:将货物从仓库A转移到仓库B,如果转仓成功,则向仓库管理员及操作员发送SMS。这是仓储管理领域常见的业务需求,经典的写法类似如下:
1: public class TransferService : IDomainService2: {3: public void Transfer(Warehouse a,4: Warehouse b,5: Item item, Qty qty)6: {7: using (IRepositoryTransactionContext ctx = Ioc.GetService<IRepositoryTransactionContext>())
8: {9: Inventory oItemInA = a.GetInventory(item);10: if (oItemInA.Qty < qty)
11: {12: // raise not enough inventory event or exception
13: throw new Exception();14: }15: Inventory oItemInB = b.GetInventory(item);16: if (oItemInB == null)17: oItemInB = b.CreateInventory(item);18: oItemInA.Qty -= qty;19: oItemInB.Qty += qty;20: ctx.SaveChanges();21: }22: }23: }24:
在上面的伪代码中,我们已经看到了领域服务(Domain Service)的影子。在DDD里,领域服务用以处理那种“放在哪里都不合适”的业务逻辑。比如上面的转仓业务,从面向对象的角度看,既不是仓库应有的操作,也不是货物(Item)的行为。为了明确领域对象的职责,DDD将这样的业务逻辑“抽”出来,置于领域服务当中。对于发送SMS的需求,就需要由应用层服务通过“协调”进行处理了。比如:在调用了领域服务并获得响应以后,根据响应结果以及外部配置信息,进而调用基础结构层服务(SMSService)实现SMS的发送。
看到这里你会发现,其实哪些业务应该放在实体中,哪些需要使用服务来处理,并没有一个绝对的标准。还是那句老话:凭经验。你还会发现,如果从实体将业务逻辑全部“抽”到服务里,实体将成为仅包含getter/setter的对象,于是贫血模型产生了。正因为DDD提倡面向领域,并将通用语言和领域模型摆在很重要的位置,因此,DDD是不主张贫血模型的。
个人认为,领域服务的引入,增加了模型的抗需求变更的能力。我们可以通过需求分析,找出业务逻辑中易变的部分,以领域服务的方式“注入”到领域模型中,今后若有需求变更,则可以无需更改任何现有组件,完成业务处理逻辑的改变。[TBD: 这样的想法还有待进一步证实]
有关领域服务的内容,本文暂且讨论这些。读者朋友可以在实践中提出问题,然后在此与大家分享讨论。本文还引出了一个话题,就是应用层服务的协调问题。比如,本文的例子中,是在应用层服务中调用SMSService实现SMS发送,如果直接将这部分内容写在应用层服务中,势必降低系统的扩展性。比如,今后希望不仅要发送SMS,而且还要发送Email。DDD的解决方案是引入事件模型。在完成转仓操作时,向事件总线(Event Bus)发送事件,由事件订阅者Subscriber捕获并处理(Handle)事件。于是,今后我们只需要实现一个“WarehouseTransferSendEmailEventHandler”的事件处理器,并在Handle Event的调用中,向相关人员发送Email即可。NServiceBus就是一款经典的基于.NET的企业级应用通信的框架,在基于事件的DDD架构中,NServiceBus发挥了重要作用。
从下一讲开始,我将着重讨论领域事件以及Event Sourcing,并对DDD的CQRS模式作个引子。