确实,不能否定ddd,es,cqrs这些思想都有其正确的一面。在很多时候只要他们能被用在适合的场景,就能起到预想的良好的结果;好的软件不可能只用一种单一的模式,没有银弹。况且,实际上软件并不只是有模型组成,还有更高层次的技术架构、实际代码落实的质量、程序员或架构师后期代码重构的能力、还有项目管理,人员管理等,这些任意一方面有问题,都可能成为未来的绊脚石。

我们能做的除了尽量做好当前职责范围内你关注的事情外,如果大家也能多多探索其他可行的方案,那我们以后的选择也会更多,呵呵。

不管怎样,我觉得好的建模方法不是凭空想想就能确定是否真的好,还是要多些代码,即多动手,然后在做的过程中,当事人才能体会到其好处或坏处。之前我用标准的ddd设计实现了一个模型,准备近期用我上面提到的方法也实现以下,具体比较这两种方法的最终结果的具体差异,以及对未来业务变化的优势和缺点。
[该贴被tangxuehua于2012-09-09 14:25修改过]

2012-09-09 12:58 "@wee"的内容
我是这么认为的,即使你将实体平行化,也只是解决部分问题,event sourcing没有open to change,但是聚根是可以的 ...

同意这个观点,如果每个实体都是平等的,都要发送自己的事件,拥有维护自己内部一致性,那么是不是可以推定,这些一个个实体实际就是一个聚合根。

我们也可先抛开聚合根这个概念,Event的发生应该是有场景的,在这个场景中,参与实体接受命令Command或发出事件,那么,Event和场景的关系要大于Event和实体的关系,因为实体可以在另外其他场景再次发生其他性质的Event。

所以,如果Event只和场景发生依赖,而和实体没有关系,那么无论实体如何重构,都不会影响到Event,这就是open to change的意思啊。我可能没有理解错误吧?


banq,你说的我有点不理解。
什么叫事件只和场景关联?场景也是对象吗?它有唯一ID吗?事件的源不应该是具有全局唯一标识的对象(DDD中就是指AggregateRoot)吗?如果事件只和场景关联,那聚合根在整个交互过程中处于什么位置呢?

Event、场景、实体三者的关系应该这样理解:实体在某一场景中发出一定的事件。也就是说Event和实体并不直接关联,这也是为什么不能把Event直接写入实体中的原因。
[该贴被flyzb于2012-09-09 16:49修改过]
[该贴被flyzb于2012-09-09 16:55修改过]

2012-09-09 16:49 "@flyzb"的内容
这也是为什么不能把Event直接写入实体中的原因 ...

不能把Event直接写入实体,不能理解这句。那为什么EventSourcing中事件都是从实体发出来的。你们所说的Event与EventSourcing中的Event不同吗?

本帖提到的关于用“让领域模型内所有实体对象都平等”的思路去解决DDD+Event Sourcing带来的模型重构问题,我在昨天和今天通过用这种方法对一些小的例子进行建模,发现所有实体对象全部平等的思路有点过。通过进一步思考,我有了一些新的想法。
那就是我们应该用更平和的多角度思考的心态去建模,重点在于理性的识别对象之间的关系。


我的最新结论如下:

1. 如果对象之间是严格的内聚关系,则还是要尊重这种关系,不能强制让这些对象地位平等,即不能对那些被内聚的子实体也采用Event Sourcing;如订单与订单明细,订单必须内聚至少一个订单明细才是一个有效的订单;即订单会关注和管理其内部维护的订单明细;对于订单明细而言,只能属于一个订单,而且永远只能属于这个订单。这种情况下,我也认为只有订单才有必要有事件,订单明细不需要有;

2. 如果对象之间是组合的关系,则需要让这些对象地位平等,对组合关系中的所有对象都应该采用Event Sourcing;如汽车和轮子,虽然从汽车的角度去看,也是和订单一样,汽车必须具有四个轮子才是一辆有效的汽车;但是从轮子角度去看,轮子可以属于这辆汽车,也可能属于另外一辆汽车。比如之前属于汽车A,后来汽车A坏了,但是轮子没坏,所以我们把该轮子装在另外一辆汽车B上了。从这个例子可以看出,汽车与轮子之间的关系没有像订单与订单明细那么内聚,汽车与轮子是组合关系。实际上,现实生活中完全有只造轮子的公司,它们制造出来的轮子被一些汽车制造商买去,然后组装成一辆辆汽车;所以,显然轮子在某些场景下有独立存在的意义,因此我们就有必要独立关注它的生命周期,即我们也需要对轮子采用EventSourcing;

3. 如果对象之间是一种普通的1:N的关联关系,则需要分析N端的对象的特征,而后进行判断它是否应该采用
Event Sourcing;如论坛中的帖子和回复,帖子与回复是1:N的关系,帖子可以没有回复,即帖子不关心它是否有回复;但是回复则不一样,回复一旦创建就只能关联这个帖子,它永远不可能再去关联其他帖子。
所以,从这个角度看,可以理解为回复是帖子的一部分,虽然帖子不关心它下面有多少个回复。所以,这种情况下,我们不应该对回复使用Event Sourcing,让回复被帖子内聚即可。所以与回复相关的操作都由帖子负责即可;再看另外一个例子:商品分类与商品,也是1:N的关系,但是他们的关系与帖子回复的关系不同,和帖子一样,商品分类不关心它下面是否有商品,另外商品也不关心它属于哪个分类,即便关心,商品所属的分类也可以调整;所以可以看出,商品分类没有聚合商品,商品也不会总是属于一个分类,商品的分类可能会改变;因此,可以得出,商品分类和商品都应该被独立跟踪,即都有采用Event Sourcing的必要;

所以,通过以上一些简单例子的分析,我们似乎可以找出一些规律,那就是:如果一个对象存在的意义只因为某个特定对象,并且其被创建时的出发点也是因为那个特定对象,那这个对象通常时需要被内聚在那个特定对象中,即这个对象不需要采用Event Sourcing机制。

另外,发现一个比较细微的差别是,以前我们在分析DDD的聚合的边界时,总是先找出聚合根,然后分析该聚合根应该内聚哪些实体;而现在我的思路是,直接从对象之间关系的双向角度,去分析每个对象是否有独立存在的意义。仔细想想,从思路上还是有些区别的。

所以,我认为我们还是要采用一种平和自然地心态去理解事物,分析其关系,不能一味的只采用DDD中聚合的这种结构去建模。如果采用DDD中那种聚合的思路去建模,你经常会发现,有些实体,在一些场景下是聚合内的一个子实体,而在另外一些场景下它又可以独立存在,以独立个体的方式与别的对象发生关系。这种情况下,会让你感觉非常别扭。实际上,这里的根本原因是你在尝试用一种只表示某种特定概念的聚合关系去表达所有其他的对象关系;正确的方法应该是,抛弃DDD中聚合概念的束缚,用一种开放平和的心态去分析和处理每一种对象关系,从关系双方的不同角度出发进行分析,分析它们对另一端对象的依赖程度和内聚程度,从而最终得出正确的设计;
[该贴被tangxuehua于2012-09-10 10:03修改过]

说说我对这些问题的理解,仅供参考(很久没有接触这东西了,解释的方式还是用四象图)。

事件可用来描述模型在特定场景下的某个行为特征;一个模型在某个场景下,可能表现出多种行为特征。

比如一个篮球球员(模型),在球赛(场景)中,可以产生扣篮、传球、突破、盖帽等事件(行为特征)。
“事件”在“场景”中的粒度太小、太多,可以“角色"进行“凝聚”为更大的粒度,提炼出得分后卫、控球后卫、中锋、前锋、小前锋等角色。

球员(模型)以后卫、中锋、前锋等身份(角色)进行球赛(场景)。

刻画模型行为特征的事件依赖于场景,不宜与模型进行绑定。
想像一下,模型参与了多种场景,那要绑定多少事件呢?

最为重要的事,业务的变更,可能需要引入新的场景,就要修改模型,而实际上只需要添加相应的事件或角色即可;同样如果一个旧的业务场景已经不再需要,只需要删除相应的事件或角色即可,不需要修改模型。(此外,场景是有生命周期的,球员可能退役!!)

在我看来,聚合根描述的是整体/部分的关系,比如树根是树干、树叶的聚合根,只要客观世界存在这种关系,建模就不能回避它。

如果系统需要提供事件回溯的能力,对Event本身的描述应该提供一些基本的方法,比如do/redo/undo等方法,而你的代码中却缺少这些东西。

聚合根与事件回溯可以说没什么关系,只是对聚合根相关事件中的redo/undo方法实现要从根写到叶子(与深度拷贝deep copy有点相似),因为聚合根存放着子节点(实体)的引用。

事件和实体都可以聚合(当存在包含关系时),事件聚合后成为某个角色,实体聚合后成为聚合根。

我能不能这样理解,“让领域模型内所有实体对象都平等”,至于你说的订单和订单明细,只要把订单明细当成值对象(结构体)处理就行了,结构体是属于实体的一部分,自然不能单独进行Event Sourcing。

http://www.jdon.com/43810

看第二张图,实体之间的关系应该是网状的类似TAG结构,维系这个结构的节点应该是某种架构,例如EVENT BUS。