之前在为什么要使用MVC+REST+CQRS架构我曾经提出DDD是核心,REST是壳的观点,我想在这里详细谈谈我的思路。

今天正好看看到老外一篇博文Why REST is so important:按这里,他认为,REST的核心概念应该是Representional State Transfer,中文意思是将状态转移显现出来,该文举例:

Marcus是一个农民。他有一个牧场,有4头猪,12只鸡和3头牛。

那么模拟客户端与之交谈,那么我肯定首先询问牧场的状态:“状态?”
Marcus 回答:“有4头猪,12只鸡和3头牛”。

这是最简单的将状态显现的案例,Marcus用语句“有4头猪,12只鸡和3头牛”将他的牧场状态转给了我。

那么如何以REST方式让Marcus加两头牛到它的牧场呢?
我们经常会范的错误是,你会说:“Marcus, 请加两头牛到你的牧场”。
请注意,我们在这里转换了状态吗?没有,我们这里表达的是动词,有面向函数风格,但是这种表述方式其实是RPC( remote procedure call 远程过程调用),这个过程就是:加两头牛到牧场。

Marcus会悲伤地回答: "400错误, Bad Request. 你是什么意思?"

那么让我们以REST方式请求,原来状态是:4头猪,12只鸡和3头牛,增加了两头牛的状态是:4头猪,12只鸡和3头牛。
那我就会说:“Marcus, 4头猪,12只鸡和5头牛, Please ”
Marcus: "正确!".
我: "Marcus, ...那么你现在状态是什么?". 
Marcus: " 4头猪,12只鸡和5头牛".
我: "Ahh, 很好"

这才是真正REST。

原文还提到,如果你希望以RPC调用,那么SOAP是一种RPC方式,但是很重量,性能差。

这里,我想补充的是,这个案例让我们明白REST是如何显现状态的,那么在通用需求中,我们如何表达显现状态呢?

也就是说,REST是将什么状态转移显现出来?首先,我们想到的是应该是将业务逻辑的状态显现出来,而DDD领域驱动设计就是分析设计业务逻辑的,那么,推理结果是,REST应该是将领域层聚合根实体的状态显现出来。

首先,我们看看DDD是如何对需求分析设计的,大致步骤如下:
1.找出需求中的上下文边界。
2.根据上下文切分成模块。
3.从每个上下文中划出聚合边界
4.确定每个聚合边界内的聚合根

其中聚合根的状态是业务逻辑状态的核心所在,REST作为一个接口壳,应该透明地将聚合根实体的状态显现给客户端,客户端通过接受用户发出的命令,透过REST来修改聚合根的实体状态,从而达到实现业务功能的结果。如下图:

下面以转账案例来说明REST+DDD的结合:

客户端向REST发出转账请求的REST应该怎么写?
1.先发帐号A的扣除命令
2.再发帐号B的增加命令
这种方式其实不是状态表达,而是函数方法调用,是动词,是一种RPC,而REST方式应该是针对状态进行发出命令。

那么关键问题是,在这里寻找什么状态?有一种方式:
1.查询帐号A余额状态是10元
2.发出请求帐号A余额状态是5元(扣除5元,剩余5元)
3.查询帐号B余额状态是20元
4.发出请求帐号B余额状态是25元(增加5元)

这样很符合上面的牧场的REST对话方式。但是问题来了:
如果客户端扣除了A帐号钱后,不申请B帐号,从第三步以后没有了,那么我们后端服务器业务逻辑就不一致了。

所以,根据DDD的聚合根用来维护聚合边界内一致性这个原则,显然,A帐号借出和B帐号贷入应该是借贷平衡的,这是基本财务规则约束,也就是一致性要求,是逻辑要求 。

如果我们这时进行DDD建模,会发现这里有一个聚合根实体:转账Transaction。

下面是REST+DDD:
1. 客户端先GET获得当前帐号余额状态

2.客户端发出转账的POST命令:
POST /transactions
注意,需要加入参数 from=1&to=2&amount=500.00
见:http://www.jdon.com/41716

3.第二步的POST命令直接递交到转账服务,transactionService.

在转账服务中,有二种实现方式:
1. DCI+面向函数风格,直接在转账服务的方法中实现,将源账户和目标帐号看成两个角色TransferMoneySourceAccount和TransferMoneyDestinationAccount,需要通过事务机制。见:http://www.jdon.com/37976

2.DDD聚合体+EventSourcing方式, 转账服务委托聚合根实现,TransactionAggregate作为聚合根实体,转账是这个实体的一个行为方法,转账命令到激活这个方法,在这个方法内部将产生一个转账事件。见:
http://simon-says-architecture.com/2013/03/07/modelling-accounting-ledger-event-driven/

http://simon-says-architecture.com/2013/03/22/modelling-accounting-ledger-event-driven-2/

一个REST的转账命令POST一般对应一个上游事件,一旦上游事件进入聚合根内部,变成很多事件流分支,这些事件流分支是改变了状态后发出的,因此必须被记录下来,出错回放才能重现状态转变历史。

var tx = new Transaction();
tx.Post(amount, fromAccount, toAccount);
transactionRepository.Store(tx);

这是调用Transaction这个聚合根的post方法。方法内部代码:

public void Post(decimal amount, string fromAccount, string toAccount)
{
this.Apply(new AccountDebited(amount, fromAccount));
this.Apply(new AccountCredited(amount, toAccount));
}

将转账命令(转账上游事件)分为两个事件,AccountDebited和AccountCredited,账户借款和账户贷款,这样保证借贷平衡。然后根据借贷状态切换,还有更多子状态,EventStore应该是这些和状态直接有关的事件,间接有关事件没有必要记录,这样在事件回放时才能重现状态真实改变历史。

总结,上面总体架构涉及到是:REST+CQRS+DDD Aggregate + Domain Events + EventSourcing

https://www.jdon.com/45622

REST与DDD的更多相关文章

  1. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  2. 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成

    阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...

  3. 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车

     阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...

  4. 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

    一.前言     DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...

  5. 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文

    阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...

  6. 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文

    阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...

  7. 如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发

    阅读目录 前言 单元测试 纠正错误,重新出发 结语 一.前言 实际编码已经写了2篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发 ...

  8. 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户

    阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...

  9. 如何一步一步用DDD设计一个电商网站(三)—— 初涉核心域

    一.前言 结合我们本次系列的第一篇博文中提到的上下文映射图(传送门:如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域.因为电子商务是以信息网络 ...

  10. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

随机推荐

  1. 值得细读!如何系统有效地提升Android代码的安全性?

    众所周知,代码安全是Android开发工作中的一大核心要素. 11月3日,安卓巴士全球开发者论坛线下系列沙龙第七站在成都顺利举办.作为中国领先的安卓开发者社区,安卓巴士近年来一直致力于在全国各大城市举 ...

  2. C++日志模块实现的经验之谈

    以类的方式对日志模块进行封装,可创建一个单实例的接口或创建一个全局的日志对象指针,同时提供相应的对外写日志接口. 写日志的接口采用可变参数来建立,可使用va_list类型和##args参数,同时在写日 ...

  3. 【1】循序渐进学 Zabbix :初识与基础依赖环境搭建( LNMP )

    写在前面的话 运维监控是一个很大的话题,在这一块个人接触的比较突出的服务主要有 Nagio 和 Zabbix 两款.而这几年跳过的公司中,Zabbix 一直都是首选且唯一选择,Nagios 没遇到. ...

  4. TigerVNC编译安装

    TigerVNC official site:http://www.linuxfromscratch.org/blfs/view/svn/xsoft/tigervnc.html TigerVNC版本: ...

  5. 等和的分隔子集(DP)

    晓萌希望将1到N的连续整数组成的集合划分成两个子集合,且保证每个集合的数字和是相等.例如,对于N=3,对应的集合{1,2,3}能被划分成{3} 和 {1,2}两个子集合. 这两个子集合中元素分别的和是 ...

  6. 一道面试题关于js中添加动态属性

    js中数据类型包含基本数据类型和引用类型,基本类型包括:string.null.undefined.number.boolean.引用类型即是对象比如:array  .function以及自定义对象等 ...

  7. POI2011 Tree Rotations

    POI2011 Tree Rotations 给定一个n<=2e5个叶子的二叉树,可以交换每个点的左右子树.要求前序遍历叶子的逆序对最少. 由于对于当前结点x,交换左右子树,对于范围之外的逆序对 ...

  8. bzoj 3131 [Sdoi2013]淘金(数位dp)

    题目描述 小Z在玩一个叫做<淘金者>的游戏.游戏的世界是一个二维坐标.X轴.Y轴坐标范围均为1..N.初始的时候,所有的整数坐标点上均有一块金子,共N*N块. 一阵风吹过,金子的位置发生了 ...

  9. 教主的花园 dp

    题目描述 教主有着一个环形的花园,他想在花园周围均匀地种上n棵树,但是教主花园的土壤很特别,每个位置适合种的树都不一样,一些树可能会因为不适合这个位置的土壤而损失观赏价值. 教主最喜欢333种树,这3 ...

  10. 架构师 AI 技术

    架构师是大忽悠吗?阿里技术大牛告诉你真相! - huangshulang1234的博客 - CSDN博客https://blog.csdn.net/huangshulang1234/article/d ...