为什么要使用MVC+REST+CQRS架构

13-07-20 banq
具体来说,前端浏览器:angular.js等MVC框架;后端: REST+ CQRS

angular.js等MVC框架是指前端浏览器的MVC框架,而不是类似Struts 或SpringMVC之类的服务器端后端MVC框架。

关于后端MVC框架的问题可见《MVC模式已死 》http://www.jdon.com/38448

基于任务的UI(Task-Based UI)

服务器端的MVC框架主要问题是粒度太粗,只能适合传统的CRUD简单粗放的应用,当我们的软件系统转向以用户体验为主,而不是以企业自身资源管理为主的模式时,响应式设计必然拥有良好的用户感受,而传统后端MVC固定的框架针对大量客户端并发事件处理无疑是力不从心,想象一下,用户鼠标一移动就可能发出一个事件,这么多事件如果每个都走Model-View-Controller这样一个流程,仅Controller就要编制多少?编程成本极高,维护拓展起来很不方便。

将MVC框架移植到浏览器前端(Rich Client),则可以巧妙回避以上问题,javascript的函数风格使得其处理事件来得更简单,对于围绕模型的操作则可以使用MVC实现。见:JavaScript大型可扩展的设计模式,而模型则是从后端领域层以JSON方式推送过来。

后端架构是:REST+CQRS,将REST和CQRS组合在一起成为CQREST架构。

REST的好处是针对资源进行简单轻量的操作,REST核心两个概念是资源和状态,而我们可以认为这个资源实际就是领域模型,通常是DDD领域驱动设计中的聚合根Aggregate,而状态是则是聚合根的状态,驱动状态变化的是REST的POST/PUT/GET/DELETE四个方法。这样REST和DDD无缝吻合在一起了。

再看看REST的四个方法实际也分两种类型:读和写。其中POST/PUT/DELETE属于对领域模型资源进行写操作的命令,属于CQRS的Command路线;而GET属于客户端发出Query查询,属于CQRS的Query读路线。

这样REST又和CQRS无缝吻合在一起。

我曾经在《没有人真正理解REST or HTTP》http://www.jdon.com/41716中说:

将来是否有一种技术思想,将DDD REST以及面向函数三者完美捆绑一起,通过URL代表领域模型类图的结构关系,比如/forum/thread,代表Forum类的子类Thread,领域模型被显式地用URL表达出来,用户访问形式和我们分析设计的模型合二为一,大道至简。

而时隔两年的今天已经实现了:《使用Yoga灵活实现REST》http://www.jdon.com/45544

如果说DDD的领域模型是核心,那么REST就是核心外的轻量壳,而CQRS则是壳和核心之间的组织,三者如有机体一样天然组合在一起,简单,易用,灵活,可伸缩,易于维护。

从缓慢的历史变化中我们应该发现一个主脉方向:MVC + REST + CQRS,如此坚定一路走来,无疑他们代表未来一种即将普及的主流架构风格。

参考:

J2EE死了 javacript + 后端JSON服务方式胜出 :http://www.jdon.com/44690

CQREST英文: http://prezi.com/svfmvrx9dq_x/cqrs-and-rest/

使用 Angular.js, Node.js 和 MongoDB开发简单案例:http://www.jdon.com/45599

[该贴被admin于2013-07-20 19:21修改过]

11
banq
2013-07-20 16:48
2013-07-20 16:09 "@banq

"的内容

如果说DDD的领域模型是核心,那么REST就是核心外的轻量壳,而CQRS则是壳和核心之间的组织,三者如有机体一样天然组合在一起 ...

这个“鸡蛋”只是我个人心中目前完美的架构,如果要再披上UI这层彩衣,我认为windows 8带来的Metro风格相当于将这个外表有点土的鸡蛋变成“彩蛋”。

windows 8的Metro风格比苹果模拟真实效果的风格好处是:它将用户脑中的模型直观化简单化,而我们使用DDD领域驱动分析是将用户脑中的模型取出来变成代码,现在又有了Metro风格的展示,这样,我们就可以将领域模型直接用Metro UI联系在一起。

例如:一个紫红色方框代表后端领域层的一个聚合的根实体模型,而紫红色方框中的更多信息则是内存中实体的实时状态,这样可以将业务模型实体信息通过Metro UI的框直接显示出来。

如果说苹果的拟真更倾向于外部的操作用户,那么Metro 则更倾向于内部的模型,能够引导用户。

jdon007
2013-07-20 21:54
2013-07-20 16:09 "@banq

"的内容

如果说DDD的领域模型是核心,那么REST就是核心外的轻量壳,而CQRS则是壳和核心之间的组织,三者如有机体一样天然组合在一起,简单,易用,灵活,可伸缩,易于维护。 ...

与这个帖子的观点基本一致。如果条件允许,我会尽量采用这种方式进行开发与协作。

不过对于REST与DDD的关系,个人有不同的看法。REST API是领域模型一种极其简洁的描述方式,相对传统的数据建模或Eric Evans的对象建模(DDD),其更易于理解、交流与协作。

REST API最易于勾勒出领域模型的灵魂,但有时候它看起来太简单,以至于我们可能漫不经心,随意为之。而个人认为,这恰恰是最值得精心雕琢的地方。提炼一组有效的描述领域模型的REST API,做到API最小、最少,但又相当完善,即“增之一分则太胖,减之一分则太瘦”,并不容易。

REST API将服务的实现与接口彻底分离,一套相同的REST API,其实现可以使用Java、.NET、PHP等各种各样语言,其数据存储可以使用NoSQL或SQL各种各样的数据库。

不依赖于具体的语言、具体的数据库,其实现可以自由替换,这难道不是领域模型该具有的特征吗?看看那些UML类图、交互图,看看那些聚合根、值对象,再看看那些表、视图、外键等等,这些描述方式,有哪一种可以比REST API更简单易懂,更易于交流与协作?

REST API不仅给服务的实现者自由,也给服务的使用者自由,客户端可以是Android(Java), iOS(Object-C), WP7(C#)或Browser(Html/css/js)等等。

除了以更易于交流与协作和提供给实现者和使用者更多的自由,由于REST API本身具有正交组合的特征,其对业务的切分,自然且与现实相吻合。试问,对象建模、数据建模,要做到这点容易吗?至少要比REST API困难的多。

之前那个小小的图书馆领域建模,有如此之多的争议,不是DCI、DDD这些工具不好,可能是用错了地方,相对于REST API其更适合于领域建模的实现,而不是领域建模本身(描述接口)。

总的说来,我更提倡这样的架构: 【MVC --> Ajax <=========>REST <-- DCI】。

[该贴被jdon007于2013-07-20 21:55修改过]

hbbbs
2013-07-21 12:57
赞同banq老师的这种架构

最近也在思考互联网上 如何能保证后端REST资源调用的安全。

期待banq老师 开帖发表高见!

[该贴被hbbbs于2013-07-21 12:59修改过]

tecentID36F90
2013-07-21 15:56
文章写的很好,受教了。另外,发现一些有趣的事情,即文中所提出的架构、方向和方法,惊人地和 UNIX 哲学符合。

首先,我们先来了解一下 UNIX 编程哲学中的三个原则:

1)一个程序只做一件事情,并做好。

2)程序要能协作。

3)程序要能处理文本流,因为这是最通用的接口。

在输入输出方面,UNIX 传统极力提倡采用简单、文本化、面向流、设备无关的格式。在经典的 UNIX 下,多数程序都尽可能采用简单过滤器的形式,即 将一个输入的简单文本刘处理为一个简单的文本流输出。

抛开世俗眼光,UNIX 程序员偏爱这种做法并不是因为他们仇视图形用户界面,而是因为如果程序不采用简单的文本输入输出流,它们就极难衔接。

然后,仔细想想就会发现,文中提出的架构惊人地符合了这三个准则。看,“命令查询的责任分离Command Query Responsibility Segregation (简称CQRS)模式是一种架构体系模式,能够使改变模型的状态的命令和模型状态的查询实现分离” 符合 “一个程序只做一件事情,并做好”;“REST API将服务的实现与接口彻底分离,一套相同的REST API,其实现可以使用Java、.NET、PHP等各种各样语言,其数据存储可以使用NoSQL或SQL各种各样的数据库” 符合 “程序要能协作”,“将MVC框架移植到浏览器前端(Rich Client),则可以巧妙回避以上问题,javascript的函数风格使得其处理事件来得更简单,对于围绕模型的操作则可以使用MVC实现。见:JavaScript大型可扩展的设计模式,而模型则是从后端领域层以JSON方式推送过来” 符合“程序要能处理文本流,因为这是最通用的接口”。

banq
2013-07-23 07:16
2013-07-21 15:56 "@tecentID36F90

"的内容

发现一些有趣的事情,即文中所提出的架构、方向和方法,惊人地和 UNIX 哲学符合。 ...

呵呵,其实这些都属于面向函数风格FP。横看成岭侧成峰。

如果从CQRS的读写分离角度看,MVC一般只有在写操作情况下才可能会有用处,也就是REST的PUT/POST/DELETE操作下才可能需要MVC。准确地说,是对模型进行写操作下才需要MVC。

而在只有读操作,也就是查询功能情况下, 大部分查询涉及到几个领域模型,这种情况其实MVC已经没有效果,显得累赘而重量。

所以,从以上分析来看,MVC其实占据整个系统的分量大概是1/3,如果在服务器后端使用MVC框架,会导致整个系统被MVC绑架,而移植到客户端,无疑是明智而简单的。

将MVC移植到客户端,无论是浏览器客户端,还是手机移动原生客户端,这样后台都有一个统一的REST服务风格。

手机客户端设计:http://www.jdon.com/45605

[该贴被admin于2013-07-23 07:34修改过]

gameboyLV
2013-07-23 21:30
2013-07-20 16:09 "@banq

"的内容

REST的好处是针对资源进行简单轻量的操作,REST核心两个概念是资源和状态,而我们可以认为这个资源实际就是领域模型 ...

客户端MVC+REST这个架构我很赞同,但是REST连接的应该是服务端预渲染的数据结构,而不是CQRS

举个例子,网站首页的今日热点TOP10,这有可能是新闻、微博、产品或者任意领域对象。领域对象和REST之间的渲染层将领域对象按页面区域渲染为合适的数据结构,存入NOSQL。

MVVM => REST => Present Layer => CRQS => DDD

            ||

            v

          NOSQL

[该贴被gameboyLV于2013-07-23 21:31修改过]

hbbbs
2013-07-24 18:40
客户端MVC+REST这个架构我很赞同,但是REST连接的应该是服务端预渲染的数据结构,而不是CQRS。
<p>

赞同,出于安全的考虑,也不应该把后台的Model暴露给客户端。与客户端交互的Model只应该是渲染的数据结构

SpeedVan
2013-07-24 18:51
2013-07-23 07:16 "@banq "的内容
呵呵,其实这些都属于面向函数风格FP。横看成岭侧成峰。 ...

其实也不算FP,只是借鉴了FP了思想,地址是资源的标识,但是却不能保证每次资源都相同,这是违背FP的,从HTTP的四个动作get put post delete可知只有一个动作没副作用。REST实际上是回到原始的HTTP,让HTTP协议直接支持,不需要中间件。

然而,非要REST函数化是可以的,因为我们可以把资源分为fuction+states,最后变成 address->fuction+value/states->page。我们每次修改value/states = 我们每次都在修改整个函数f' (f' = f states),而f'就是资源。我们必须认清一点,尽管每次都是同一个指向的资源,但该资源不一定跟之前一样,所以REST只能是借鉴了FP,而不是FP。

FP在WEB中很特殊,对DB的处理是基于monad的。所以想真正了解FP的现实意义,monad必须过关。

oojdon
2013-07-25 12:49
2013-07-20 16:09 "@banq

"的内容

想象一下,用户鼠标一移动就可能发出一个事件,这么多事件如果每个都走Model-View-Controller这样一个流程,仅Controller就要编制多少?编程成本极高,维护拓展起来很不方便。 ...

我现在有点controller恐惧证,项目中那一堆的controller越看越不爽,老师的这个帖子对我很有启发啊,前段时间jdonmvc的代码也有更新,demo代码用extjs4来做大量的客户端工作,后端代码退化为:

public class TestController {

    private static DB db = new DB();

    @In
    private RequestBody body;

    @Path("/")
    public Represent index() {
        return new Html("index");
    }

    @Path("/users")
    public Represent get() {
        return Json.create(db.all());
    }

    @Path("post:/users")
    public Represent add(User user) {
        db.add(user);
        return Json.create(new Result(true, "add success"));
    }

    @Path("put:/users/:id")
    public Represent update(int id) {
        db.update(body.json2Object(User.class));
        return Json.create(new Result(true, "update successful"));
    }

    @Path("delete:/users/:id")
    public Represent delete(int id) {
        db.del(id);
        return Json.create(db.all());
    }

    @Path("post:/upload")
    public Represent upload(FormFile formFile) {
        return Json.create(new Result(true, "upload successful"));
    }

}
<p>

后台就是一个Json构建函数

那么这个TestController有必要没?用java非要写个类,其实重点是那些打了注解的函数。所以理想应该类似这样的代码,全是函数,没有类

get "/" {
 render => index
}

get "/" {
"hello world"
}
<p>

于是我看上了sinatra,当然

app.get('/api', function (req, res) {
  res.send('Our Sample API is up...');
});
这种js代码反应的本质是一样的

js + nodejs + 文档数据库,这是一个很酷的组合

上面的代码可在这里获得https://github.com/oojdon/mvcdemo

[该贴被oojdon于2013-07-25 12:56修改过]

banq
2013-07-26 06:22
2013-07-25 12:49 "@oojdon

"的内容

前段时间jdonmvc的代码也有更新 ...

好久不见,JdonMVC这次更新得好啊,REST功能更强大,JdonREST,哈哈。

查询读操作时:可以直接对DDD的Domain Service加上REST标注了,将领域服务直接暴露给外界作为API调用。

写操作命令时:可以对command handler进行REST标注。

摘录

如果说DDD的领域模型是核心,那么REST就是核心外的轻量壳,而CQRS则是壳和核心之间的组织,三者如有机体一样天然组合在一起,简单,易用,灵活,可伸缩,易于维护。

是不是可以这么说:如果说Jdon框架是核心和组织,那么JdonMVC则壳。为理想加油。

相关:

http://www.jdon.com/45619

关于本文进一步分析:http://www.jdon.com/45622

[该贴被banq于2013-07-30 17:10修改过]

lshoo
2013-07-27 09:21
又开拓思路了,把DDD和REST的URL结合在一起。

shanhestm
2013-11-05 19:08
banq老师,能不能给发给架构实例,让我们学习学习

w438418754
2013-11-06 10:44
CQRS+REST 我觉得还是有点问题的,虽然C对应写操作 Q对应读操作,但是REST中写操作是粗粒度的,而CQRS中写操作是细粒度的

比如:一个用户(用户作为资源) CQRS中你可以有ChangePassWrord(修改密码),ChangeAddress(修改地址)等等命令,而REST中你只能是SET 整个USER

猜你喜欢