专注于面向对象、领域驱动设计,及软件架构方面的学习

要学会站在巨人的肩膀上让自己成长。QQ:94388050

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  39 随笔 :: 0 文章 :: 355 评论 :: 0 引用

阅读本文的前提是假设你已经熟悉领域驱动设计(DDD)和CQRS架构。 

 

领域模型图如下:


说明:
  1. 上面的领域模型在设计时借鉴了DDD和CQRS的思想;
  2. 利用DDD的思想来设计实体、值对象、聚合、聚合根;图中有三个聚合根,分别是Forum、Thread、User;其中Thread聚合根聚合了Post和ViewCounter两个对象;Post是Thread的回复,显然Post离开Thread没有意义,但是Post在Thread聚合内有一个本地标识,即只要在当前Thread下唯一即可,不需要全局唯一。
  3. 由于CQRS思想的引入,可以确保我们在设计领域模型时不必考虑由于对象关联而产生的统计信息该如何存放,从而让领域模型更精简明了;如帖子的总回复数、最新回复时间、最新回复人,等等,这些信息只是统计信息,只用于在界面上显示,即我们只有在查询时才需要这些信息,因此可以在CQRS的Q端实现。
  4. 由于CQRS思想的引入,也可以让仓储更精简,不需要提供用于查询领域对象并在界面上显示结果的接口,而只需要提供用于查询单个聚合根或Add以及Remove的操作;
  5. 上面的领域模型只关注一个标准论坛的基本功能;

之前学了很多的理论知识,现在是该通过一些建模例子锻炼一下的时候了。相信大家一定有很多疑问,所以希望大家能多多讨论。今晚有点晚了,不能把整个思考过程写下来,到时我再补上。

posted on 2011-10-27 01:02 netfocus 阅读(1539) 评论(11) 编辑 收藏

评论

#1楼 2011-10-27 08:03 duson      
查询单个聚合根。这样会查询出很多根本不需要的数据,拖慢查询速度和增加内存开销的
 回复 引用 查看   

#2楼 2011-10-27 08:46 Astar      
支持楼主继续
 回复 引用 查看   

#3楼 2011-10-27 09:42 吾爱孟夫子      
好文,短小精悍。清晰明了,容易理解。
 回复 引用 查看   

#4楼[楼主] 2011-10-27 09:53 netfocus      
@duson
恩,如果直接让帖子(Thread)维护回复(Post),那么当我们要添加一个回复时,必须把帖子的所有回复取出来,然后才能添加,这样势必会导致性能低下。本来依据信息专家模式,我们应该将添加回复的职责交给帖子来完成,但是因为遇到性能问题,我们必须考虑选择其他方案来即能在业务上保持帖子和回复的业务关系完整性,又不会有性能问题。

经过我的思考,我认为在ThreadRepository中完成这个职责比较合理。原因是:ThreadRepository是维护Thread的,因此它知道所有的Thread以及每个Thread聚合的内部细节,否则ThreadRepository无法对Thread进行持久化。所以,我们可以给ThreadRepository增加一个职责:AddThreadPost(threadId, post),该职责表示为某个帖子增加一个回复。AddThreadPost内部实现时不必先把Thread取出来到内存,而是可以直接操作关系数据库;依照这个推理,UpdatePost方法也可以这样实现;

我的这个解决方案如何呢?
 回复 引用 查看   

#5楼[楼主] 2011-10-27 10:37 netfocus      
从我上面的分析,大家有没有看到一个规律呢?那就是分析业务时,领域对象之间的关系并不代表必须在领域对象之间通过引用来表达,因为这通常会导致性能低下。实际上我认为领域对象之间的关系是从业务角度理解的一种业务关系,而在OO设计阶段并非一定要通过对象关联来表达这种关系,而可以采用其他替代方案但必须同样也要能表达业务关系。

从中我总结的规律是:
1)这种情况发生在1:N的时候;
2)当N这一端的明细项会在1这一段的对象之后逐步添加时;
3)N很大时;

这种情况下,我们往往需要采用上面的方案。

举个例子:
电子商务系统中,创建一个订单Order时,我们可以把Order的明细项OrderItem作为一个集合放在Order内,因为OrderItem总是和Order一起创建,不会在Order被创建后再逐步添加到Order中;
而在论坛中则不同,我们首先创建一个帖子Thread,然后回复会在Thread被创建后慢慢逐个增加,此时在当Post很多的时候,如果还是为Thread建立一个Post的集合的话,会导致性能低下。
 回复 引用 查看   

#6楼 2011-10-27 10:50 Shpix      
@netfocus
其实有时候不一定非要规规矩矩的按照模式去设计,根据目前的技术,在设计和效率间做一些让步。如楼主所说的方案,我觉得是不可能在Thread里增加AddPost,UpdatePost等行为了,因为这必导致Repository侵入到Entity。不如把Post也独立成聚合根
 回复 引用 查看   

#7楼[楼主] 2011-10-28 12:29 netfocus      
@Shpix
确实,其实设计是一个在设计、效率、维护性等方面权衡的过程;

不过你提到的关于在Thread里设计AddPost、UpdatePost等行为并不意味着要引入Repository来实现,而是Thread在内存中包含Post的一个List。但这样就像我上面所说的,会造成性能问题。所以在ThreadRepsitory中实现这两个职责;

另外,如果把Post设计为聚合根肯定不妥,因为业务上来说Post确实离开Thread没有任何意义。
 回复 引用 查看   

#8楼 2011-10-28 16:56 Shpix      
@netfocus
想请教下,如果我要对Post进行分页,过滤,排序等操作,我应该如何处理查询,我只了解DDD的架构,对CQRS不了解
 回复 引用 查看   

#9楼[楼主] 2011-10-29 08:50 netfocus      
你去看一下CQRS架构就可以了,将查询部分单独实现,与DDD无关。
 回复 引用 查看   

#10楼 2011-12-20 09:07 安度      
按照设计,存储库不能和模型分置到不同的类库中,因为这样的话,会产生互相引用的问题。而如果不放到不同的库,通过文件夹组织的方式,则会不会把领域层做的很臃肿?
 回复 引用 查看   

#11楼[楼主] 2011-12-20 18:55 netfocus      
@安度
模型只引用仓储的接口,如IProductRepository,而IProductRepository的实现在另外的dll中。

运行时通过IOC注入IProductRepository。
 回复 引用 查看