强化你的域:聚合的构建【翻译】

我们的应用复杂性已经达到它的临界点,于是我们决定把以前的贫血领域模型转成丰富的行为性的模型。但是什么是贫血模型?让我们看看Folower的6年前的定义:

一个贫血领域模型的基本特征是乍一看它挺像一个真实的对象。在一个领域里一些对象是用名词命名的,这些对象通过与真实领域模型相同的丰富的关系和结构来建立连接。当你观察行为的时候问题就来了,你会意识到这些对象几乎没有任何行为,它们仅仅比一袋访问器和设置器多一点点东西。事实上这些模型常常是根据一种设计规则创建的—不能把任何领域逻辑放到域对象中。取而代之的是有一些能够处理所有领域逻辑的服务对象。这些服务对象在域模型之上活动并且使用域模型作为数据。

对于CRUD应用,这些”域服务“数量应该很少,但是随着域服务数量开始增长,这对我们来说应该就是一个信号,我们需要已领域驱动设计的方式获取更丰富的行为。基于DDD思想创建一个应用和基于模型架构很不一样。基于模型架构,我们是从数据库表结构或者ERDs开始的,然后构建对象去匹配。基于DDD,我们是从交互和行为开始的,然后构建模型去匹配。但是我们遇到的第一个问题是,我们在第一个地方如何创建实体?我们的第一个单元测试需要创建一个实体,它应该从哪里来?

创建有效聚合体

有效性校验可能是应用中一个棘手的难题,因为我们常常看到有效性在使用数据时很少用,而是在使用命令时用的多。举个例子,一个人可能在屏幕上有一个”出生日期“的必填字段,但是我们有一个需求是遗留以及被导入的用户没有出生日期。所以很明显出生日期的要求取决于谁在创建这个人。

但是除了有效性之外就是实体的不变量。不变量是一个实体是实体的本质。我们问我们的客户,在我们的系统中没有出生日期的人是人吗?是的,有时是。没有姓名呢?不,在我们的系统中一个人必须有一些标示身份的特征,它们结合在一起定义了这个”人“。一笔订单需要一个订单号和一位客户。如果业务人员拿到一个没有客户信息的订单表单,他们会扔出去!注意这个不是有效性,但是其它一些完全是。我们现在要问了,一笔订单是一笔订单意味着什么呢?那就是不变量。

假设我们有一个相当简单的一组逻辑。如果发票省份和客户的省份是一样的话,我们会标明我们的订单是”本地“。非常简单的方法:

public class Order

{

          public bool IsLocal()

          {

               return Customer.Province==BillingProvince

           }

}

我简单的询问客户的省份与订单的发票省份。但是现在我进入一个相当奇怪的场景。

[Test]

public void Should_be_a_local_customer_when_provinces_are_equal()

{

          var order=new Order

          {

                BillingProvince=“Ontario”

           };

          var customer=new Customer

          {

                 Province=“Ontario”

           };

            var isLocal=order.IsLocal();

            isLocal.ShouldBeTrue();

}

没有正常的成功或失败的断言,我得到一个空引用异常!我忘记在订单对象上设置一位客户。

但是等一下—我怎么能创建一个没有客户的订单呢?一个没有客户的订单不是一个订单,因为我们的领域专家是这样对我们解释的。我们可以稍微走一条荒唐的路,加一个空引用检查。

但是等一下—这个永远不会在产线发生。我应该仅仅修复我们的测试代码然后继续,对吗?是的,如果你是构建一个基于CRUD系统(90%情况)的交易脚本的话我是同意的。虽然是这样,但是如果我们使用DDD,我们想让聚合根满足这个要求,也就是说聚合根的不变量必须满足所有操作。创建一个聚合根是一个操作,而且在代码中”new”是一个操作。现在,不变量肯定被满足。

让我们修改一个我们的订单类:

public class Order

{

          public Order(Customer customer)

          {

                Customer=customer;

           }

}

我们添加一个构造器,因此当创建订单时,所有的不变量都是满足的。我们的测试现在要修改。

[TestFixture]

public class Invariants

{

     [Test]

      public void Should_be_a_local_customer_when_provinces_are_equal()

      {

                var customer=new Customer

                {

                      Province=“Ontario”

                 };

                 var order=new Order(customer)

                  {

                          BillingProvince=“Ontario”

                   };

                   var isLocal=order.IsLocal();

                   isLocal.shouldBeTrue();

}

现在我们的测试通过了!

总结和其它选择

从数据驱动的方式转到这种方式,将会有一些痛苦。如果我们首先写一些持久层的测试,我们会进入“为什么我们测试订单持久化得时候需要一位客户?“,或者,为什么测试订单的所有逻辑时需要一位客户?当你开始基于一个没有强制它自己的不变量的领域模型写代码时,这种问题是不是很明显。如果不变量仅仅通过域服务来满足,对于理解在任何时候一个真正”订单“是什么的问题时非常的棘手。我们写代码的时候应该一直假设客户存在吗?我应该仅仅在使用它的时候再写吗?

如果我们的实体一直满足它的所有不变量因为它的设计不允许不变量被违背,那么违背的不变量就永远不会发生。我们不再需要考虑缺失客户的可能性,并且能够在这种强制的规则下构建我们的软件。实践中,我发现这种方式真的需要较少的代码,因为我们不允许进入那种需要思考的荒谬的场景。

但是通过构成器创建实体并不是唯一的方法。我们也有:

构造器模式

创建方法

通过已存在的聚合根

底线是—如果我们的实体需要特定信息才能被认为是一个实体时,不能创建一个不变量不被满足的实体。

相关文章:

实体内还是外有效性验证

使用传递管道处理横截关注点

更好的领域事件模式

使用Entity Framework scorecard领域建模

内部与外部事件比较


原文链接:Strengthening your domain:Aggregate Construction

作者:Jimmy Bogard

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容