浅谈CQRS模式

x33g5p2x  于2021-10-10 转载在 其他  
字(3.1k)|赞(0)|评价(0)|浏览(395)

背景:

笔者在刚开始尝试实践DDD模式时,时常会觉得DDD模式相当笨重,尤其是在面对来自UI的各种不同维度的查询时,相当的繁琐:
如果遵循DDD的设计原则,那么就应该只有聚合根才能持有Repository,我们需要先根据VO构建聚合根,再通过聚合根操作Repository去做查询操作,拿到查询的domain,再转换为VO返回给UI。如果UI查询涉及到多个聚合根,那么情况将会更加复杂。
仅仅只是做一次查询操作,就要做至少两次domain和vo的转换&校验,实在是大可不必,所以笔者在开始处理查询时,就没有完全按照DDD的规则去做,只是将查询单独做了service,在那时还觉得这种只是自己偷懒的处理方式,直到后来看到CQRS模式,才有了一种拨云见日,找到归属的感觉。

CQRS简介:

20多年前,Bertrand Meyer在他的《Object-Oriented Software Construction》一书中提出了CQS(Command Query Seperation,命令查询分离)的概念,指出:
Every method should either be a command that performs an action, or a query that returns data to the caller, but never both.
(一个方法要么作为一个“命令”执行一个操作,要么作为一次“查询”向调用方返回数据,但两者不能共存。)

后来,Greg Young在此基础上提出了CQRS(Command Query Resposibility Segregation,命令查询职责分离),将CQS的概念从方法层面提升到了模型层面,即“命令”和“查询”分别使用不同的对象模型来表示。
《重构:改善既有代码的设计》的作者Martin Fowler,他提供了使用CQRS和不使用CQRS的对比架构图:
简单架构:

使用CQRS后:

原作者Greg Young对CQRS的说明:

CQRS Command and Query Responsibility Segregation Starting with CQRS, CQRS is simply the creation of two objects where there was previously only one. The separation occurs based upon whether the methods are a command or a query(the same definition that is used by Meyer in Command and Query Separation, a command is any method that mutates state and a query is any method that returns a value).
CQRS 命令和查询职责分离从 CQRS 开始,CQRS 只是创建了两个对象,而以前只有一个对象。分离的发生取决于方法是命令还是查询(这里和Meyer在Command and Query Separation理论中使用的定义相同,命令是任何改变状态的方法,查询是任何返回值的方法)。

Greg Young 还列出了一段伪代码用于特别说明什么是CQRS模式:
使用CQRS之前:

public class CustomerService{

void MakeCustomerPreferred(CustomerId);

Customer GetCustomer(CustomerId)

CustomerSet GetCustomersWithName(Name)

CustomerSet GetPreferredCustomers()

void ChangeCustomerLocale(CustomerId, NewLocale)

void CreateCustomer(Customer)

void EditCustomerDetails(CustomerDetails)
}

使用CQRS之后:

public class CustomerWriteService{

void MakeCustomerPreferred(CustomerId)

void ChangeCustomerLocale(CustomerId, NewLocale)

void CreateCustomer(Customer)

void EditCustomerDetails(CustomerDetails)
}

public class CustomerReadService{

Customer GetCustomer(CustomerId)

CustomerSet GetCustomersWithName(Name)

CustomerSet GetPreferredCustomers()
}

说穿了CQRS非常简单,就是将原本的一个服务按照command和query维度拆分成两个,二者职责相互独立。command负责CRUD中的CUD,query负责其中无副作用的R,如果是比较大的服务,还可以将command和query单独部署。拆分的好处非常明显:

  1. 代码的条理更清楚,运维程序更简单。
  2. 可以单独对command或query进行扩展,一般来说,系统的query和command压力都是不对称的,可以针对压力大的query进行单独扩展或者升级。
  3. 在DDD中,可以更好地应对来自UI的不同维度的查询。

当然,在简单的场景下,读写使用共享对象,通常是最简单的做法。我们引入CQRS只是为了应对日益复杂的业务场景,在不断迭代的复杂场景中,如果将查询逻辑与业务逻辑糅合在一起会使软件迅速腐化,将会导致诸如逻辑混乱、可读性变差以及可扩展性降低等等一系列的问题。

对CQRS的一些误读:

诚然,CQRS的使用者可以根据实际情况,将读写分离开单独部署,然后引入领域事件,使用消息队列做通信,但是这些都是基于不同业务场景的架构选择,而非CQRS本身的要求,实际上CQRS只是一种非常简单的模式而已,并没有和事件、消息队列这些有强关联。
读写分离部署+消息通信,无疑会带来额外的系统复杂性和更高的运维成本,《重构:改善既有代码的设计》的作者Martin Fowler也提醒了要小心使用CQRS,就连Greg Young本人都不推荐将CQRS复杂化处理。
Greg Young的原话:

Many people have been getting confused over what CQRS is. They look at CQRS as being an architecture; it is not. CQRS is a very simple pattern that enables many opportunities for architecture that may otherwise not exist. CQRS is not eventual consistency, it is not eventing, it is not messaging, it is not having separated models for reading and writing, nor is it using event sourcing.
许多人一直对 CQRS 是什么感到困惑。 他们将 CQRS 视为一种架构, 它不是。 CQRS 是一种非常简单的模式,它为架构提供了许多可能不存在的机会。 CQRS 不是最终一致性,不是事件处理,不是消息传递,它没有用于读取和写入的分离模型,也不是使用事件源。

总结:

CQRS是一种非常简单的模式,将command和query分离开,不仅仅适用于DDD中,只要业务场景足够复杂,都可以使用。个人建议:
业务非常复杂 ⇒ 单独拆分service,使用不同VO对象;
业务一般复杂 ⇒ 单独拆分service,复用VO对象;
业务简单 ⇒ 不必使用CQRS。

相关文章