专栏名称: 石杉的架构笔记
专注原创、用心雕琢!十余年BAT一线大厂架构经验倾囊相授
目录
相关文章推荐
武汉本地宝  ·  湖北机场公安/中国铁投/理工大/字节跳动/湖 ... ·  2 天前  
武汉本地宝  ·  2025荆州马拉松延期举办! ·  2 天前  
武汉本地宝  ·  不用交房租!武汉这些地方可以免费住! ·  4 天前  
武汉本地宝  ·  下调!湖北当前油价是多少? ·  4 天前  
51好读  ›  专栏  ›  石杉的架构笔记

所有 CRUD 程序员,都应该知道的 CQRS 架构!

石杉的架构笔记  · 公众号  ·  · 2019-07-13 10:30

正文

公众号后台回复“ 学习 ”,获取作者独家秘制精品资料


多年好友心血力作, 阿里资深技术专家 十余年JVM生产实践经验

《从 开始带你成为 JVM 实战 高手》

限时优惠: 88元 正在进行ing

专栏目录 参见文末

扫下方海报进行 试读

通过我的海报购买, 再返你24元

领取方式: 加微信号:Giotto1245 ,暗号: 返现


本文来自 程序员私房菜 的投稿


阅读本文约需要5分钟


今天主要跟大家分享一下什么是 CQRS,以及在项目中如何去使用。


1 . CRUD系统


我们平常最熟悉的就是三层架构,通常都是通过数据访问层来修改或者查询数据,一般修改和查询使用的是相同的实体。


然后通过业务层来处理业务逻辑,将处理结果封装成DTO对象返回给控制层,再通过前端渲染。反之亦然。



这里基本上是围绕关系数据库构建而成的“创建、读取、更新、删除”系统(即CRUD系统)


此类系统在一些业务逻辑简单的项目中可能没有什么问题,但是随着系统逻辑变得复杂,用户增多,这种设计就会出现一些性能问题。


我们经常用到的解决方案就是对数据库进行读写分离。让主数据库处理事务性的增、删、改操作,让从数据库处理查询操作,然后主从数据库之间进行同步。


但是这只是从DB角度处理了读写分离,从业务或者系统层面上来说,读和写的逻辑仍然是存放在一起的,他们都是操作同一个实体对象。


这时候,CQRS 就该登场了。


2 . CQRS系统


简单的说,CQRS(Command Query Responsibility Segration)就是一个系统,从架构上把 CRUD 系统拆分为两部分:命令(Command)处理和查询(Query)处理。 其中命令处理包括增、删、改。



然后命令与查询两边可以用不同的架构实现,以实现CQ两端(即Command Side,简称C端;Query Side,简称Q端)的分别优化。


两边所涉及到的实体对象也可以不同,从而继续演变成下面这样。



当然了,CQRS 作为一个读写分离思想的架构,在数据存储方面,也没有做过多的约束。所以 CQRS可以有不同层次的实现。


3 . CQRS 实现方式


CQRS 可以有两种实现方式。


  1. CQ 两端数据库共享,只是在上层代码上分离。

    这样做的好处是可以让我们的代码读写分离,更容易维护,而且不存在 CQ 两端的数据一致性问题,

    因为是共享一个数据库的。这种架构是非常实用的(也就是我上面画的那种)

  2. CQ 两端不仅代码分离,数据库也分离,然后Q端数据由C端同步过来。

    同步方式有两种:同步或异步,如果需要 CQ 两端的强一致性,则需要用同步;如果能接受 CQ 两端数据的最终一致性,则可以使用异步。

    C端可以采用Event Sourcing(简称ES)模式,所有C端的最新数据全部用 Domain Event 表达即可

    而要查询显示用的数据,则从Q端的 ReadDB(关系型数据库)查询即可(详细可以参考文献3)。


4 . 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  Object dispatch(Commandcmd, 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)






请到「今天看啥」查看全文