之前在为什么要使用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. js setTime()详解

    来源:http://www.jb51.net/article/35535.htm#t1 setTimeout setTimeout 语法例子 用 setTimeout 来执行 function 不断重 ...

  2. java中计算一段时间内白天的时间和夜晚的时间

    之前,采用拼接字符串的形式,不断地在Date类型和Long类型之间转换,实在是太过于麻烦,后来采取了这种思路:假设我们将22:00 ~ 10:00 视为夜间时间,则我们先计算出10:00 相对于当天的 ...

  3. Installing the .NET Framework 3.5 on Windows 8, Windows 8.1 and Windows 10

    Installing the .NET Framework 3.5 on Windows 8, Windows 8.1 and Windows 10 .NET Framework (current v ...

  4. NSNull空值

    1.前言 作为占据空间的一个空值,如用在数组或字典中占据一个没有任何值的空间. 1.1 NULL & nil 的区别: nil 是 OC 的,空对象,地址指向空的对象,指针地址指向的是 NUL ...

  5. day05.3-Linux进程管理

    1. 通过top指令可查看系统当前进程信息. 2. 通过free指令可查看系统内核信息.其中 free   -m:以M为单位查看内核:                 free   -h:以G为单位查 ...

  6. Hawk-and-Chicken 强连通

    题意:一群人投票  票具有传递性  求出累计和最大的数和 哪几个人最大 强连通好题!!! 毫无疑问先强连通缩点 一开始打算拓扑排序求dis  但是发现拓扑排序会有重复累加的情况 那么就反向建图   当 ...

  7. CF431B Shower Line

    Many students live in a dormitory. A dormitory is a whole new world of funny amusements and possibil ...

  8. springboot整合actuator,进行运维监控

    首先引入依赖: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="htt ...

  9. vue.js路由学习笔记二

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  10. Exadata扩展

    所谓Exadata扩展,也即向现有的Exadata环境中增加新的数据库服务器或存储服务器. 扩展原则 可以依循以下规则扩展Exadata: (1).可以将Exadata从某种固定配置扩展到另一种固定配 ...