不过,肯定比较简略,如果真的要深入理解,还是要读《领域驱动设计》《实现领域驱动设计》这两本书。
好啦,接下来进入正题。
首先是关于聚合的内容。我们认为,相关联的一些事物,构成了一种叫做聚合的概念。比如,说到书,他就会有封皮,书名,每页书,作者等等等等。他们是聚合在一起的,说到书,就代表了那一堆东西。而书这个概念,我们则称之为聚合。如果把每种东西抽象成类和引用的关系,则总会有一个东西在最上层,表达着整体的概念,我们称之为聚合根。
那么,聚合是由什么构成的呢?值得一提的是,我们现在在说的聚合的概念,是一种结构性质的东西,与之相对的,它不是一种流程。
而构成聚合的构件,我们使用两个东西,实体,值对象。
什么是实体?什么是值对象?以我的语言能力,解释起来略困难了一点。这么说吧,实体的特点是,具有生命周期,在其生命周期之中,其内部状态可能会发生变化。但是,实体具有其唯一的标志,即使两个实体除了标志外所有属性均完全相同,我们也认为他们是两个实体。那么,这个标志是什么呢?这也是我最近才领悟的,这个标志,是值对象。说说值对象吧。值对象,如字面所述,是表示值的对象。它表示了一种值,那么,它就具备了这么几个特征。首先,它不会改变自己的一部分,或者说,它的状态不会变,因为它没有生命周期,所以,它没有状态。它要么,是这个值,要么是那个值,而不存在这个值可以被变为什么的说法。其次,如果两个值对象的所有状态都相等了,那么,它们就是同一个值,这个与实体能够形成鲜明的对比。
那么,为什么说实体的标志是值对象呢?曾经,我们的框架中,为了通用,将实体的标志,设置成了字符串。这可以满足唯一ID的需求,而且,也有最佳实践告诉我们,这样管理所有的表是好的。但是,在我以往的实践中就遇见过这种情况。有的记录是使用组合主键标志自己的唯一性的。当然,我们可以通过添加一个唯一ID的方式来使其统一,但,这引起了两个后果。首先,这增加了复杂度,其次,事情不是这样的,而却偏要这样实现,后面会遇到麻烦,其实,也是增加了复杂度。而在读《实现领域驱动设计》时,里面强调的,即使只是一个字符串,其实,它也是一个值对象。这是我们很容易混淆的一点,而值对象的引入,无疑,增加了我们对值这一概念的掌控。我希望在我这次的框架里,可以达到这一目的。最后说明一点,聚合根,一定是实体。
而聚合由实体和值对象构成,实体可以通过自身的方法改变自身的状态,而这些操作在聚合范围内,也代表着实现了聚合自身的业务。由此,聚合表达了自己所代表的业务的含义。
其实,这里,是有问题存在的。聚合是需要被持久化的东西,我们需要长期得跟踪其生命周期,改变其状态。那么,它的持久化,和读取或者说加载是怎么进行的呢?是由仓储进行的。仓储对聚合的持久化进行支持。提供了有限的几个操作:创建,保存,删除,通过标志加载。而正如仓储的字面意思,仓储,是一个仓库,做储备只用。而其中存储的东西,就是聚合。这里还有几个东西需要注意,既然,仓储是操作聚合的东西,那么,仓储,就不会在聚合内部被调用。而聚合之间,也是相互独立或者说是离散的。聚合和聚合之间,并无法直接相互操作,否则,他们就应该是一个聚合。
那,聚合之间的写作是怎么完成的呢?通过领域服务。那领域服务和聚合是什么样的关系呢?或者说,有什么相似或者不同呢?聚合,表达了领域中的一些结构,他们有属性,有生命周期,会被长时间的维护。而领域服务,也表达了领域中的一些结构,所不同的是,它没有属性,单纯的,提供服务。比如说,转账。如果账户是一个聚合的话,那么转账这个操作就会牵扯至少两个聚合。显而易见,在聚合内部是无法完成这个操作,这里需要一个服务来协调两个账户进行转账,而这个服务,我们则称之为领域服务。
在我们团队的实践过程中,老大最初定义的领域服务是面向用例的,而我认为的领域服务是构成领域的一部分,而面向用例的那部分,不过是类似门户的存在。不过,最近的一次实践老大妥协了,允许两种同时存在。而其实,面向用例的那部分,我在我之前的框架中,是定义为命令,与应用服务进行通信的。
那么,什么是应用服务呢?
其实,应用服务是独立于领域之外的,它是面向应用的,面向用例的。我们领域中的模型并不能直接在应用中使用,因为应用要的不一定是他,他不一定够。所以,在应用服务中,会把领域中的东西,组装成应用想要的东西,再丢给应用。应用服务,大概就是这样的用处,转发,同时,也能够防止领域被应用所腐蚀。
其实,这里,还有两个构造块我们没有说,领域事件,查询。
先说说领域事件吧。还以上面转账的例子来说,这,其实,是发生了一件事情。事实上来说,它除了把钱从一个账户转到另一个账户外,还记录下了转账的流水。这是这个服务本身做的事情。而事实上,在之后,我们为了能够更加清晰的对系统进行监控,我们引入了日志。这里,是需要记录日志的。怎么记?修改原来的服务?可以。只是,似乎存在以下的问题。开闭原则,我们修改了原来的代码。单一职责原则,服务不仅要进行自己的业务,还要记录日志。高内聚低耦合,原来的服务需要认识并使用日志模块,看样子,日志模块会变得无所不在。那么,我们似乎看到,随着系统内容和复杂度的增加,这将会是一种灾难。可是,我们发现,事情并不是这个样子的。其实应该是,转账这件事发生了,因为这件事的发生,相关系统要进行对应的操作,比如,日志系统要记录日志。那么,发生的这件事,就是领域事件。领域事件的实现一般是发布订阅的模式来实现的,我们这里也是,这似乎是很自然的一件事情。
剩下的构造块,我称之为,查询。这个,是我在读了cqrs之后,引入的一个构造块。不了解的大家可以查一下cqrs的东西,以后有时间的话,我会整理一篇cqrs的文章。而cqrs采用事件溯源重构聚合,用领域事件更新查询源,供应用查询。而其解决的主要问题是,在经典DDD中,对业务的查询,是复杂的,低效的,因为应用需要的查询往往和领域自身的结构冲突,从而导致,连表查询,导致查询复杂度上升,效率降低。而起推崇以响应领域事件来维护一个供应用查询的数据源,而这个数据源中的数据,是我们为了满足应用的查询,而维护在那里的。值得注意的是,我们并没有定义新的构造块给这些数据,他们依然是,聚合,其实更接近dto,虽然有些不自然,但是,也算满足了我们的需要。另外,我们可以通过属性,来对这些数据进行查询。
原文链接:/javaschema/284848.html