模式:AGGREGATE

目录

 

模式:AGGREGATE

所有事务应用一组规则(重要)

采购订单的完整性(例子)


模式:AGGREGATE

减少设计中的关联有助于简化对象之间的遍历,并在某种程度上限制关系的急剧增多。但大多数业务领域中的对象都具有十分复杂的联系,以至于最终会形成很长、很深的对象引用路径,我们不得不在这个路径上追踪对象。在某种程度上,这种混乱状态反映了现实世界,因为现实世界中就很少有清晰的边界。但这却是软件设计中的一个重要问题。

假设我们从数据库中删除一个Person对象。这个人的姓名、出生日期和工作描述要一起被删除,但要如何处理地址呢?可能还有其他人住在同一地址。如果删除了地址,那些Person对象将会引用一个被删除的对象。如果保留地址,那么垃圾地址在数据库中会累积起来。虽然自动垃圾收集机制可以清除垃圾地址,但这也只是一种技术上的修复;就算数据库系统存在这种处理机制,一个基本的建模问题依然被忽略了。

即便是在考虑孤立的事务时,典型对象模型中的关系网也使我们难以断定一个修改会产生哪些潜在的影响。仅仅因为存在依赖就更新系统中的每个对象,这样做是不现实的。在多个客户对相同对象进行并发访问的系统中,这个问题更加突出。当很多用户对系统中的对象进行查询和更新时,必须防止他们同时修改互相依赖的对象。范围错误将导致严重的后果。

在具有复杂关联的模型中,要想保证对象更改的一致性是很困难的。不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。然而,过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相干扰,从而使系统不可用。

换句话说,我们如何知道一个由其他对象组成的对象从哪里开始,又到何处结束呢?在任何具有持久化数据存储的系统中,对数据进行修改的事务必须要有范围,而且要有保持数据一致性的方式(也就是说,保持数据遵守固定规则)。数据库支持各种锁机制,而且可以编写一些测试来验证。但这些特殊的解决方案分散了人们对模型的注意力,很快人们就会回到“走一步,看一步”的老路上来。

实际上,要想找到一种兼顾各种问题的解决方案,要求对领域有深刻的理解,例如,要了解特定类实例之间的更改频率这样的深层次因素。我们需要找到一个使对象间冲突较少而固定规则联系更紧密的模型。

尽管从表面上看这个问题是数据库事务方面的一个技术难题,但它的根源却在模型,归根结底是由于模型中缺乏明确定义的边界。从模型得到的解决方案将使模型更易于理解,并且使设计更易于沟通。当模型被修改时,它将引导我们对实现做出修改。

人们已经开发出很多模式(scheme)来定义模型中的所属关系。下面这个简单但严格的系统就提炼自这些概念,其包括一组用于实现事务(这些事务用来修改对象及其所有者)的规则。

首先,我们需要用一个抽象来封装模型中的引用。AGGREGATE就是一组相关对象的集合,我们把它作为数据修改的单元。每个AGGREGATE都有一个根(root)和一个边界(boundary)。边界定义了AGGREGATE的内部都有什么。根则是AGGREGATE所包含的一个特定ENTITY。对AGGREGATE而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。除根以外的其他ENTITY都有本地标识,但这些标识只在AGGREGATE内部才需要加以区别,因为外部对象除了根ENTITY之
外看不到其他对象。

所有事务应用一组规则(重要)

汽车修配厂的软件可能会使用汽车模型。如下图所示。汽车是一个具有全局标识的ENTITY:我们需要将这部汽车与世界上所有其他汽车区分开(即使是一些非常相似的汽车)。我们可以使用车辆识别号来进行区分,车辆识别号是为每辆新汽车分配的唯一标识符。我们可能想通过4个轮子的位置跟踪轮胎的转动历史。我们可能想知道每个轮胎的里程数和磨损度。要想知道哪个轮胎在哪儿,必须将轮胎标识为ENTITY。当脱离这辆车的上下文后,我们很可能就不再关心这些轮胎的标识了。如果更换了轮胎并将旧轮胎送到回收厂,那么软件将不再需要跟踪它们,它们会成为一堆废旧轮胎中的一部分。没有人会关心它们的转动历史。更重要的是,即使轮胎被安在汽车上,也不会有人通过系统查询特定的轮胎,然后看看这个轮胎在哪辆汽车上。人们只会在数据库中查找汽车,然后临时查看一下这部汽车的轮胎情况。因此,汽车是AGGREGATE的根ENTITY,而轮胎处于这个AGGREGATE的边界之内。另一方面,发动机组上面都刻有序列号,而且有时是独立于汽车被跟踪的。在一些应用程序中,发动机可以是自己的AGGREGATE的根。


 

固定规则(invariant)是指在数据变化时必须保持的一致性规则,其涉及AGGREGATE成员之间的内部关系。而任何跨越AGGREGATE的规则将不要求每时每刻都保持最新状态。通过事件处理、批处理或其他更新机制,这些依赖会在一定的时间内得以解决。但在每个事务完成时,AGGREGATE内部所应用的固定规则必须得到满足,如图6-3所示。

现在,为了实现这个概念上的AGGREGATE,需要对所有事务应用一组规则。

  • 根ENTITY具有全局标识,它最终负责检查固定规则。
  • 根ENTITY具有全局标识。边界内的ENTITY具有本地标识,这些标识只在AGGREGATE内部才是唯一的。
  • AGGREGATE外部的对象不能引用除根ENTITY之外的任何内部对象。根ENTITY可以把对内部ENTITY的引用传递给它们,但这些对象只能临时使用这些引用,而不能保持引用。根可以把一个VALUE OBJECT的副本传递给另一个对象,而不必关心它发生什么变化,因为它只是一个VALUE,不再与AGGREGATE有任何关联。
  • 作为上一条规则的推论,只有AGGREGATE的根才能直接通过数据库查询获取。所有其他对象必须通过遍历关联来发现。
  • AGGREGATE内部的对象可以保持对其他AGGREGATE根的引用。
  • 删除操作必须一次删除AGGREGATE边界之内的所有对象。(利用垃圾收集机制,这很容易做到。由于除根以外的其他对象都没有外部引用,因此删除了根以后,其他对象均会被回收。)
  • 当提交对AGGREGATE边界内部的任何对象的修改时,整个AGGREGATE的所有固定规则都必须被满足。

我们应该将ENTITY 和VALUE OBJECT 分门别类地聚集到AGGREGATE 中, 并定义每个AGGREGATE的边界。在每个AGGREGATE中,选择一个ENTITY作为根,并通过根来控制对边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保AGGREGATE中的对象满足所有固定规则,也可以确保在任何状态变化时AGGREGATE作为一个整体满足固定规则。

有一个能够声明AGGREGATE的技术框架是很有帮助的,这样就可以自动实施锁机制和其他一些功能。如果没有这样的技术框架,团队就必须靠自我约束来使用事先商定的AGGREGATE,并按照这些AGGREGATE来编写代码。

采购订单的完整性(例子)

考虑一个简化的采购订单系统可能具有的复杂性。

上图展示了一个典型的采购订单(Purchase Order,PO)视图,它被分解为采购项(Line Item),一条固定规则是采购项的总量不能超过PO总额的限制。当前实现存在以下3个互相关联的问题。

  1. 固定规则的实施。当添加新采购项时,PO检查总额,如果新增的采购项使总额超出限制,则将PO标记为无效。正如我们将要看到的那样,这种保护机制并不充分。

  2. 变更管理。当PO被删除或存档时,各个采购项也将被一块处理,但模型并没有给出关系应该在何处停止。在不同时间更改部件(Part)价格所产生的影响也不明确。

  3. 数据库共享。数据库会出现由于多个用户竞争使用而带来的问题。多个用户将并发地输入和更新各个PO,因此必须防止他们互相干扰。让我们从一个非常简单的策略开始,当一个用户开始编辑任何一个对象时,锁定该对象,直到用户提交事务。这样,当George编辑采购项001时,Amanda就无法访问该项。Amanda可以编辑其他PO上的任何采购项(包括George正在编辑的PO上的其他采购项),如下图所示。

每个用户都将从数据库读取对象,并在自己的内存空间中实例化对象,而后在那里查看和编辑对象。只有当开始编辑时,才会请求进行数据库锁定。因此,George和Amanda可以同时工作,只要他们不同时编辑相同的采购项即可。一切正常,直到George和Amanda开始编辑同一个PO上的不同采购项,如下图所示。

从这两个用户和他们各自软件的角度来看,他们的操作都没有问题,因为他们忽略了事务期间数据库其他部分所发生的变化,而且每个用户都没有修改被对方锁定的采购项。

当这两个用户保存了修改之后,数据库中就存储了一个违反领域模型固定规则的PO。一条重要的业务规则被破坏了,但并没有人知道,如下图所示。

显然,锁定单个行并不是一种充分的保护机制。如果一次锁定一个PO,可以防止这样的问题发生,如下图所示。

直到Amanda解决这个问题之前,程序将不允许保存这个事务,Amanda可以通过提高限额或减少一把吉他来解决此问题。这种机制防止了问题,如果大部分工作分布在多个PO上,那么这可能是个不错的解决方案。但如果是很多人同时对一个大PO的不同项进行操作时,这种锁定机制就显得很笨拙了。

即便是很多小PO,也存在其他方法破坏这条固定规则。让我们看看“Part”。如果在Amanda将长号加入订单时,有人更改了长号的价格,这不也会破坏固定规则吗?

那么,我们试着除了锁定整个PO之外,也锁定Part。图6-9展示了当George、Amanda和Sam在不同PO上工作时将会发生的情况。

工作变得越来越麻烦,因为在Part上出现了很多争用的情况。这样就会发生下图中的结果:3个人都需要等待。

现在我们可以开始改进模型,在模型中加入以下业务知识。

  1. Part在很多PO中使用(会产生高竞争)。
  2. 对Part的修改少于对PO的修改。
  3. 对Price(价格)的修改不一定要传播到现有PO,它取决于修改价格时PO处于什么状态。

当考虑已经交货并存档的PO时,第三点尤为明显。它们显示的当然是填写时的价格,而不是当前价格。

按照上图,这个模型得到的实现可以确保满足PO和采购项相关的固定规则,同时,修改部件的价格将不会立即影响引用部件的采购项。涉及面更广的规则可以通过其他方式来满足。例如,系统可以每天为用户列出价格过期的采购项,这样用户就可以决定是更新还是去掉采购项。

但这并不是必须一直保持的固定规则。通过减少采购项对Part的依赖,可以避免争用,并且能够更好地反映出业务的现实情况。同时,加强PO与采购项之间的关系可以确保遵守这条重要的业务规则。

AGGREGATE强制了PO与采购项之间符合业务实际的所属关系。PO和采购项的创建及删除很自然地被联系在一起,而Part的创建和删除却是独立的。

AGGREGATE划分出一个范围,在这个范围内,生命周期的每个阶段都必须满足一些固定规则。接下来要讨论的两种模式FACTORY和REPOSITORY都是在AGGREGATE上执行操作,它们将特定生命周期转换的复杂性封装起来……

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章