接触领域驱动设计DDD有一年多的时间了,中间看过很多书,参与过一些讨论(ENode QQ群)。目前对DDD的认知还停留在理论阶段,因此对领域建模很是感兴趣,这里说的建模是指以DDD的思想为指导再加上DDD的工具,好比聚合、实体、值对象等等。html
昨天有群友分享了一个建模的案例,我想在这儿记录下来,有两个目的:一、为本身学习DDD储存素材 二、锻炼本身写文章的能力并发
关于DDD中的一些概念,请见以下文章连接:app
http://www.cnblogs.com/ivanzheng/p/5533162.html(Ivan翻译)框架
http://www.cqrs.nu/Faq(这是原文连接,英文好的能够直接读这个)ide
你们确定都据说过视频课程网站吧,好比慕课网。这儿要说的场景跟这个网站的后台管理比较相关。好比,课程的做者能够上传一些课程视频(Video)到该网站,而后再建立一个课程(Course),把以前上传的视频放到该课程下,等全部的课程视频都审核经过了,则就能够发布这个课程了,学习者就能够在网上看到该课程。高并发
上面那段话只是对“视频课程”场景作了大体的描述,下面我将逐条以用例(Use Case)的方式作一个列表:工具
从Use Case中能够很容易的获得这几个概念(名词):视频、课程。(由于本次建模只关注课程与视频的关系,不考虑用户);业务的行为概念也很容易获得:审核、发布。性能
业务模型很简单,课程和视频是典型的多对多关系。学习
经过场景能够分析出以下两个实体:视频、课程网站
必须知足的业务规则:
一、发布课程时,要求该课程下的关联的视频个数≥3
二、发布课程时,其下的全部视频必须都是已经审核经过的状态
很显然,在场景描述时,咱们隐含的表达了如下这些概念:
一、视频能够单独上传,管理,审核等,从业务规则上来看,它不依赖于任何课程的状态
二、课程能够单首创建,在发布时须要依赖视频的个数和状态
因此,视频显然是一个聚合根。在现有的业务场景中,课程也是一个聚合根,这个应该没有歧义。业务规则1和2也是在课程这个聚合下体现,并保证业务规则被知足。
有一个最容易想到的非DDD的设计,代码可能以下:
class Course { public string Id { get; } public ICollection<Video> Videos { get; } public void Publish() { if (Videos.Count < 3) throw Exception("One course must associate 3 videos."); if (Videos.Any(v => !v.Approved)) throw Exception("All videos need to be approved."); ... } } class Video { public string Id { get; } public bool Approved { get; set; } public ICollection<Course> Courses { get; } }
先不说这个设计的好坏,咱们直接来看从DDD的角度如何设计这两个聚合根。
首先一点,聚合之间不可直接引用(内存引用),必须以Id的方式引用,由于根据定义聚合是不容许接触到他们外部的. (若是容许了那意味着聚合再也不是一个事务边界, 咱们也再也不能充分的推导出聚合有能力确保他的不变性;这也将妨碍到聚合的分片处理)
由于课程发布时,须要验证其下所关联视频个数和是否所有审核,因此须要一个视频信息的实体聚合在课程下。
class Course { public string Id { get; } public ICollection<VideoInfo> VideoInfos { get; } //视频信息的实体集合 public void Publish() { if (VideoInfos.Count < 3) throw Exception("One course must associate 3 videos."); if (VideoInfos.Any(v => !v.Approved)) throw Exception("All videos need to be approved."); ... } } //视频信息,至少包含视频Id和是否已审核的状态 class VideoInfo { public string VideoId { get; } public bool VideoApproved { get; set; } } class Video { public string Id { get; } public bool Approved { get; set; } public ICollection<string> Courses { get; } //Course Id的集合 }
为了保证高并发及性能,聚合之间采用最终一致性,且聚合之间的交互经过Saga来完成。当视频审核经过后,视频聚合根发消息通知全部关联它的课程,以便更新课程中的VideoInfo的状态,因此Video聚合根下要有课程Id集合的缘由。
在DDD中,有个例子常常被用来说解聚合、实体、值对象的概念,那就是电商领域的订单(Order)、订单项(OrderLine)、商品(Product)。其实上面的场景很相似于Order、OrderLine、Product之间的关系,课程至关于Order,视频至关于Product,视频信息至关于OrderLine。这也许就是触类旁通的体现吧。
注:此图来源于汤雪华的博客
欢迎你们加入ENode群讨论DDD和牛逼的ENode框架,QQ群号:185916873。群主(汤雪华,netfocus)博客:http://www.cnblogs.com/netfocus/