引言

Udi Dahan曾在2017年阿姆斯特丹的DDD欧洲年会上发表过一篇演讲——if (domain logic) then CQRS, or Saga。视频是UP主从Youtube搬运的,我听力水平一般,所以以下内容有所偏颇的话,还请见谅。

在演讲中,他提到了Sandbox、Private Domain、Public Domain和Collaboration Domain等一些概念,为更好地应用DDD开辟了不同视角。以下便是我的思考与收获。

正文

Udi用级联删除的例子,引出了沙盒Sandbox。错误的级联删除操作,特别是在数据库中Table级别上的删除操作,对任何一个系统而言都可谓灭顶之灾。所以顺理成章的,我们会使用给数据行打上某种删除标志的方法,称之为“软删除”,避免数据被彻底删除。这就象操作系统里的回收站一样,给了我们一次反悔的机会。如果在所有的软件系统里都有这样一个安全沙盒,是不是就完全解决了删除问题?

  • 对此,我认为,对于系统中的所有删除操作,首先都应该有领域行为上的具体含义,比如解雇员工、取消订单、停产产品等等。其次,还要考虑受到删除操作影响的数据,要限定在哪个范围,它们是否有留存下来的业务价值。比如解雇员工后,与之相关的薪酬记录就不能删除,否则会导致账面错误。反之,只在薪酬记录里有个员工ID,却查不到这名员工的姓名、职务等具体信息,也会发生错误。再或者,这名员工先被裁员,之后在招聘时被优先聘用时,如何处理其原有信息,等等。所以,“删除”操作不能简单论之,必须是从业务领域的角度去思考和命名——其究竟是何种业务操作,这种操作在业务上应导致何种后果。比如雇员被解雇后,其所有数据被冻结,其雇员信息不再会更新,其薪酬不会再发放。

随后,Udi用博客作为例子,引出了Private DomainPublic Domain的概念,并阐述了Sandbox与二者之间的联系。一篇随笔被正式发布前,博主可以在编辑页面随便折腾。而在随笔正式发布后,所有的修改和删除操作就需要慎重了。此处,发布前的随笔处于Private Domain,发布后则进入了Public Domain。对处于Private Domain范围内的私有数据,我们可以随意地增删查改,而不用顾忌任何的业务规则和约束。而当私有数据被推送到Public Domain时,则必须顺利通过各种规则的审核与验证,进而成为构成系统的固定组成部分。其中,每个Private Domian对应一个Sandbox,用户可以为所欲为,而不必担心对系统造成实质影响。

  • 对此,我认为,每个Sandbox都对应某个具有领域含义的实体,并可以用与之相关的领域事件作为Private Domain与Public Domain的划分界线。比如一份菜谱,你可以随意修改菜谱里各种菜品的价格、名称,甚至新增或者撤掉某个菜品。而在正式提交更新前,所有人仍旧会使用旧的定价和菜名。这里的菜谱,就是所有菜品所在的Sandbox边界。而最终MenuUpdated事件的触发,才标志着操作对系统产生了实质性改变,数据已经进入Public Domain。如果没有这个提交的步骤,让所有的增删查改都立即产生效果,那顾客一定会开骂了。另一方面,如果没有暂存修改结果的这个Sandbox,也意味着完全不给饭店老板活路——要改就一口气改完,还不能自相矛盾。回到前述的删除操作也是同理。当数据还在Private Domain时,我们可以放心地删除之。而当数据已经进入Public Domain后,就必须去发掘删除操作背后的领域含义了,并由此衍生出更复杂的领域逻辑和领域行为。

Udi接着商品被删除的例子,延伸到购物车中已加入的商品售罄或被停售的情况,提出Collaboration Domain的概念。在竞态条件(即并发条件下的竞争条件)下,即表明有多个参与者需要对同一个数据进行操作,此时属处的领域即Collaboration Domain。这样的协作领域,通常可以围绕if语句进行发掘。有if判断涉及其他的实体,则通常表明该数据也可能会被其他参与者改变。此时,CQRS成为很自然的选择。因为命令执行的环境是经过事先检验的,所以命令总是能成功执行。在这样的设定下,把一个用户下单的操作分割为多个步骤,在放入购物车和提交订单时分别进行一次商品有效性的检验。

  • 对此,我认为,引入CQRS是因为将命令与查询分离后,检验执行环境的部分将由一组查询构成,改变系统状态的部分则由一组命令组成,这样将更容易发现竞态条件,从而做出合理的选择。

为了减少检验的次数,Udi借Shopping Cart Timeout的例子引出了Collaboration Timeout的概念。给购物车一个活动状态的超时设定:用户放入商品时,购物车进入激活状态,跨入协作领域,此时因商品在售,订单可以成功提交;用户未在超时前提交订单的,购物车进入失活状态,退出协作领域,之后商品将因售罄或停售而无法再加入购物车。

  • 对此,我认为,此时的购物车更象是一个愿望清单(Udi在演讲里也有提及),这样的Timeout与超卖都是解决最终一致性条件下的不同解决方案。MSDN CQRS Journey第164页提到的方案,是根据代价大小,选择对客户做出赔偿或者在付款前加锁判定,让其他人等待一小段时间。当席位较多时,因为竞争风险小,可以将席位是否够用的查询交付异步执行,使用户等待时间减少;当席位紧张时,允许提交失败的用户修改订单。唯品会采用的方式,是在商品加入购物车后,将该商品暂时锁定,保证用户在超时前能成功下单。

由于Collaboration Domain和CQRS的存在,Udi指出,必须改变传统的思考方式,因为Saga将更普遍地出现在模型之中。对此不能有退缩和犹豫,应当与领域专家深入交流,使自己也成为业务专家,确保每个流程都完全可控。最后,Udi提出消息中间件是实现Saga的重要工具,于是顺手推介了一下自己维护的NServiceBus框架。

  • 对此,我认为,以用户购物这个Saga为例,将由用户注册->查看商品详情->放入购物车->提交订单->完成支付等多个流程协作完成,而不是简单地放在一个或几个数据库事务里完成。在不同聚合之间以消息或事件为纽带,是处理Saga的核心与关键。所以CQRS、Event Sourcing以及Message Queue都将是实现DDD的利器。

观《if (domain logic) then CQRS, or Saga?》所悟的更多相关文章

  1. domain logic approaches

    领域逻辑组织可以分为三种主要的模式:事务脚本(Transaction Script).领域模型(Domain Model)和表模块(Table Module)” 1.domain logic appr ...

  2. Domain logic approachs

    1.transaction script(事务脚本) 概述: 很多企业应用可以看成一系列的事务,每一个事务可以通过使用一个Transaction Script来处理. 用法: 使用Transactio ...

  3. CQRS FAQ (翻译)

    我从接触ddd到学习cqrs有6年多了, 其中也遇到了不少疑问, 也向很多的前辈牛人请教得到了很多宝贵的意见和建议. 偶尔的机会看到国外有个站点专门罗列了ddd, cqrs和事件溯源的常见问题. 其中 ...

  4. 关于CQRS(老外经典好文)

    CQRS means Command Query Responsibility Segregation. Many people think that CQRS is an entire archit ...

  5. Introduction to CQRS

    原文链接:  http://www.codeproject.com/Articles/555855/Introduction-to-CQRS What is CQRS CQRS means Comma ...

  6. 翻译:wiki中的business logic词条

    Business logic 业务逻辑 From Wikipedia, the free encyclopedia 来自Wikipedia,自由的百科全书 In computer software, ...

  7. Domain Driven Design and Development In Practice--转载

    原文地址:http://www.infoq.com/articles/ddd-in-practice Background Domain Driven Design (DDD) is about ma ...

  8. 领域驱动设计(Domain Driven Design)参考架构详解

    摘要 本文将介绍领域驱动设计(Domain Driven Design)的官方参考架构,该架构分成了Interfaces.Applications和Domain三层以及包含各类基础设施的Infrast ...

  9. [转载]领域驱动设计(Domain Driven Design)参考架构详解

    摘要 本文将介绍领域驱动设计(Domain Driven Design)的官方参考架构,该架构分成了Interfaces.Applications和Domain三层以及包含各类基础设施的Infrast ...

随机推荐

  1. 【JAVA并发第一篇】Java的进程与线程

    1.进程与线程 1.1.进程 进程可以看作是程序的执行过程.一个程序的运行需要CPU时间.内存空间.文件以及I/O等资源.操作系统就是以进程为单位来分配这些资源的,所以说进程是分配资源的基本单位. ( ...

  2. [极客大挑战 2019]HardSQL updatexml报错注入小结

    报错注入链接: https://www.cnblogs.com/richardlee97/p/10617115.html报错原因: 其原因主要是因为虚拟表的主键重复.按照MySQL的官方说法,grou ...

  3. Python追加文件内容

    测试中需要造几百个账号,写了个脚本可以自动生成账号,但想把生成的账号写入一个文件, 开始用的如下的write()方法,发下会先把原文件的内容清空再写入新的东西,文件里面每次都是最新生成的一个账号 mo ...

  4. ActionResult的返回类型

    ActionResult是控制器方法执行后返回的结果类型,控制器方法可以返回一个直接或间接从ActionResult抽象类继承的类型,如果返回的是非ActionResult类型,控制器将会将结果转换为 ...

  5. LeetCode初级算法之数组:26 删除排序数组中的重复项

    删除排序数组中的重复项 题目地址:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ 给定一个排序数组,你需要在 ...

  6. socket和http有什么区别?

    socket是网络传输层的一种技术,跟http有本质的区别,http是应用层的一个网络协议.使用socket技术理论上来讲, 按照http的规范,完全可以使用socket来达到发送http请求的目的, ...

  7. AcWing 332. 股票交易

    大型补档计划 题目链接 \(f[i][j]\) 表示前 \(i\) 天,手里有 \(j\) 个股票挣得最多钱 买股票.枚举 \(u < i - W\) \(f[i][j] = max(f[u][ ...

  8. HBase删除数据

    hbase官方文档中描述了,hbase删除数据可以总结为下面三种(Java API有很多接口,可以总结下面的几种): 删除一个列的指定版本 删除一个列的所用版本 删除指定列族的所有列 hbase删除数 ...

  9. spring的基础面试内容

    1.什么是spring? Spring 是个Java企业级应用的开源开发框架.Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring 框架目标是简化Java ...

  10. SpringBoot从入门到精通教程(七)

    今天,我们继续讲SpringBoot整合Redis ,也就缓存,它将与我们的Springboot整合 Redis 简介 Redis 是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语 ...