今天主要跟大家分享一下什么是 CQRS,以及在项目中如何去使用。
CRUD系统
我们平常最熟悉的就是三层架构,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体。然后通过业务层来处理业务逻辑,将处理结果封装成DTO对象返回给控制层,再通过前端渲染。反之亦然。
这里基本上是围绕关系数据库构建而成的“创建、读取、更新、删除”系统(即CRUD系统),此类系统在一些业务逻辑简单的项目中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能问题。
我们经常用到的解决方案就是对数据库进行读写分离。让主数据库处理事务性的增、删、改操作,让从数据库处理查询操作,然后主从数据库之间进行同步。但是这只是从DB角度处理了读写分离,从业务或者系统层面上来说,读和写的逻辑仍然是存放在一起的,他们都是操作同一个实体对象。
这时候,CQRS 就该登场了。
CQRS系统
简单的说,CQRS(Command Query Responsibility Segration)就是一个系统,从架构上把 CRUD 系统拆分为两部分:命令(Command)处理和查询(Query)处理。其中命令处理包括增、删、改。
然后命令与查询两边可以用不同的架构实现,以实现CQ两端(即Command Side,简称C端;Query Side,简称Q端)的分别优化。两边所涉及到的实体对象也可以不同,从而继续演变成下面这样。
当然了,CQRS 作为一个读写分离思想的架构,在数据存储方面,也没有做过多的约束。所以 CQRS可以有不同层次的实现。
CQRS 实现方式
CQRS 可以有两种实现方式。
1)CQ 两端数据库共享,只是在上层代码上分离。这样做的好处是可以让我们的代码读写分离,更容易维护,而且不存在 CQ 两端的数据一致性问题,因为是共享一个数据库的。这种架构是非常实用的(也就是我上面画的那种)。
2)CQ 两端不仅代码分离,数据库也分离,然后Q数数据由C端同步过来。同步方式有两种:同步或异步,如果需要 CQ 两端的强一致性,则需要用同步;如果能接受 CQ 两端数据的最终一致性,则可以使用异步。C端可以采用Event Sourcing(简称ES)模式,所有C端的最新数据全部用 Domain Event 表达即可;而要查询显示用的数据,则从Q端的 ReadDB(关系型数据库)查询即可。
CQRS 的简单实现
说了这么多,该怎么实现呢?我们以上面提到的第一种方式为例:代码层面实现分离,数据库共享。这种方式在企业里也非常实用。
首先有几个概念需要介绍一下,CQRS 模式中,首先需要有 Command,这个 Command 命令会对应一个实体和一个命令的执行类。那整个系统中肯定有很多不同的 Command,那么还需要一个 CommandBus 来做命令的分发处理。
可能大家觉得比较抽象,我来写几行示例代码,一看就明白了。假设有个订单模块,我要新增一个订单信息。那么根据上文的分析,需要有个新增命令以及对应的订单实体(并不一定和数据库的订单实体完全对应)。首先先创建一个命令接口(绑定命令对应的实体),接口内部有个该命令的处理方法。
public interface Command<T> { Object execute(T commandModel); }
OK,接下来我们可以创建订单的新增命令了。
@Component public class CreateOrderCommand implements Command<CreateOrderModel> { @Override public Object execute(CreateOrderModel model) { // 具体的逻辑 } }
到这里,我们写好了具体的创建订单命令的逻辑,那么该命令需要放到 CommandBus 中去执行,所以我们要写这个 CommandBus。
@Component public class CommandBus { public <T> Object dispatch(Command<T> cmd, T model) { return cmd.excute(model); } }
可能大家会看着有点晕,甚至有点绕,没关系,我解释一下:这个 dispatch 方法就相当于分发执行,内部根据传入的具体 Command 以及对应的 model,去执行该 Command 实现的逻辑。
好了,那我们在熟悉的 Controller 层该如何去调用呢?很简单,如下:
@RestController @RequestMapping(value = "/order") public class OrderController { @Resource private GetOrderInfoService getOrderInfoService; @Resource private CreateOrderCommand createOrderCommand; @Resource private CommandBus commandBus; @PostMapping(value = "/getInfo") public Object getOrderInfo(GetOrderInfoModel model) { return getOrderInfoService.getOrderInfos(model); } @PostMapping(value = "/creat") public Object createOrderInfo(CreateOrderModel model) { return commandBus.dispatch(createOrderCommand, model); } }
我还写了一个获取订单信息的接口,大家有没有发现,查询和插入是不同的方式,插入走的是 CommandBus 分发到 CreateOrderCommand 去执行,而查询则是直接走 service 层去查。这就是 CQRS 模式。
当然了,当命令越来越多的时候,也可以将 CommandBus 抽象出接口,可以根据业务需求,实现多个不同的 CommandBus 来分发命令。
除此之外,CQRS 还可以用在任务调度模块中,不同的任务可以包含不同的 Command,实际中运用是非常广泛的。
总结
CQRS 是一种思想很简单清晰的设计模式,他通过在业务上分离操作和查询来使得系统具有更好的可扩展性及性能,使得能够对系统的不同部分进行扩展和优化。在 CQRS 中,所有的涉及到对 DB 的操作都是通过发送 Command,然后特定的 Command 触发对应事件来完成操作,也可以做成异步的,主要看业务上的需求了。
CQRS 虽然在思想上简单,但是实现上相对来说复杂些,也涉及到 DDD 的一些概念了,当然了,这篇文章主要是介绍以及演示 CQRS 模式的基本实践,更多知识需要大家再深入的去学习。
- 2020年我国到底有多少程序员?现在学习java还来得及吗?
中国有多少程序员?现在还值得学java吗? 跪求关注,祝关注我的人都:身体健康,财源广进,福如东海,寿比南山,早上贵子,从不掉发! JAVA起于1995年,经过20多年的发展,JAVA如今已经发展成为 ...
- Java程序员拥有这些工具,还怕干不出好活
老话说,工欲善其事,必先利其器. 那么,作为编程人员,你都用过哪些“兵器”呢?你的”兵器“够”锋利”吗? 近期,有很多新朋友问,都有什么画流程图的工具,给推荐推荐? 索性,就静下来,好好梳理一下,从事 ...
- [Mac A]为什么国外程序员爱用 Mac?
from http://www.vpsee.com/2009/06/why-programmers-love-mac/ Mac 在国外很受欢迎,尤其是在 设计/web开发/IT 人员圈子里.普通用户喜 ...
- IBM总架构师寇文东谈程序员的职业规划
有些年轻的程序员向我咨询,将来的路该怎么走?俗话说,条条大路通罗马.不同的路都能走向成功,到底选择哪条路,取决于自己的兴趣.可能有程序员会问:如果还没有找到自己的兴趣怎么办?我的建议是多尝试,努力做, ...
- .Net程序员面试 中级篇 (回答Scott Hanselman的问题)
继<.Net 程序员面试 C# 语言篇 (回答Scott Hanselman的问题)>跟<.Net程序员面试 每个人都应知道篇 (回答Scott Hanselman的问题)>之 ...
- 【常用软件】木木的常用软件点评(2)------VC程序员常用工具篇
摘自:http://blog.csdn.net/liquanhai/article/details/7215045 木木的常用软件点评(2)------VC程序员常用工具篇 分类: VC++经验总结 ...
- PHP程序员必须知道的两种日志
前言 作为一名程序员,比码代码还重要那么一点点的东西就是日志的分析和查询.下面列出常见日志及设置方法. php-fpm 慢日志 php慢日志需要在php-fpm.conf设置,如果使用源码包安装默认请 ...
- IBM 总架构师:话说程序员的职业生涯
作者:IBM 软件集团大中华区总架构师 寇卫东 有一些年轻的程序员向我咨询,将来的路应该怎么走?俗话说,条条大路通罗马.不同的路都能走向成功.到底选哪条路,取决于自己的兴趣.可能有程序员会问:如果还没 ...
- ios程序员6级考试(答案和解释)
http://blog.sunnyxx.com/2014/03/06/ios_exam_0_key/ 我是前言 上次发了个ios程序员6级考试题 ,还在不断补充中,开个帖子配套写答案和解释. 1. 下 ...
随机推荐
- Data Structure Binary Tree: Level order traversal in spiral form
http://www.geeksforgeeks.org/level-order-traversal-in-spiral-form/ #include <iostream> #includ ...
- 最受欢迎的牛 usaco
题面网上到处都是: 主要来谈谈怎么做,首先利用tarjan求强连通分量缩点,缩点后找到出度为0的点,若不止一个,则输出0,否则输出这个点包含的缩点前的点的个数: 为什么这么做,是由这道题的问法决定的, ...
- react服务端渲染
一.服务端渲染的好处 1.SEO, 让搜索引擎更容易读取页面内容: 2.首屏渲染速度更快(重点),无需等待JS文件下载执行过程: 3.更易于维护,服务端和客户端可以共享某些代码: 二.实现原理 服务端 ...
- POJ题目算法分类总结博客地址
http://blog.csdn.net/sunbaigui/article/details/4421705 又从这个地址找了一些:http://blog.csdn.net/koudaidai/art ...
- win8+sdk8+vs2012+freeglut+glew开发opengl
写给想要学习opengl的同学们. 刚开始学习opengl的时候,对于整个环境的搭建以及一些概念不太清晰,网上的资料又比较凌乱,因此在此总结一下,方便大家. 首先,是有一个windows系统,我用的是 ...
- 如何用命令行删除EasyBCD开机选择项?
用硬盘安装Ubuntu方法的windows双系统电脑上面,很多人都是用EasyBCD设置的开机启动选择.所以当我们不需要双系统的时候,或者已经删除双系统后,或者安装双系统失败的情况下,发现电脑的开机启 ...
- matlab的调试
MATLAB(1)——基本调试方法(Debug) 链接:http://www.cnblogs.com/xingshansi/articles/6477185.html 前言 之前经 ...
- matlab字符串链接的三种方式
1.fprintf()函数: a='I love you,'; b='China'; c=123; fprintf('%s%s\n',a,b); fprintf('%s%s*****%d\n',a,b ...
- NOIP 2016【蚯蚓】
好吧,我承认我是个智障-- 这道题一眼看上去就是个堆,然而实际上有单调性. 注意到,如果 \(q = 0\) 的话,将蚯蚓的左右两边分开丢进两个队列中,则两个队列都是单调不增的,因为每次取出的蚯蚓长度 ...
- 用linqpad来插入多条数据
其中Customers为数据库的某个表名, Custom自动被默认为单条记录的对象, 利用构造,InsertOnSubmit, 以及SubmitChanges实现插入数据. 注意:linqpad的la ...