千家信息网

如何使用CQRS避免查询对模型设计的影响

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,这篇文章主要为大家展示了"如何使用CQRS避免查询对模型设计的影响",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"如何使用CQRS避免查询对模型设计的影响"
千家信息网最后更新 2026年10月31日如何使用CQRS避免查询对模型设计的影响

这篇文章主要为大家展示了"如何使用CQRS避免查询对模型设计的影响",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"如何使用CQRS避免查询对模型设计的影响"这篇文章吧。

模型设计时不要考虑查询

按DDD设计领域模型时,有两个很有用的原则:

  • 模型对展现技术要无感知,比如展现是用WEB,还是APP端,在做模型设计时不要考虑

  • 不要关心复杂查询和报表的需求

那这样领域模型设计出来后,经常受到的挑战是查询性能不好。相比于以数据为中心的设计思维,以领域对象为中心的设计思维设计出的数据存储粒度更细,冗余更少,确实不利于查询。

另外一个对查询不友好的地方在于聚合这个概念。比如我想查一个订单列表,列表中的每一行只需要订单这个聚合根的数据,不需要把每一条订单下的子实体都被查询出来,那样性能会很差。

可以使用JPA提供的懒加载来实现只加载聚合根,但是会带来更多的其它的问题,懒加载已被认为是不被推荐使用的反模式。

大部分的业务系统都是读多写少,领域模型是重要,但能提供符合性能要求的查询也很重要。DDD是怎么解决这个矛盾的呢?

模型对查询不友好怎么办

DDD推荐使用CQRS( Command Query Responsibility Segregation, 命令职责分离)这种模式来解决这个问题。

https://martinfowler.com/bliki/CQRS.html

我们来看看IDDD_Sample的 com.saasovation.collaboration.application.forum 下的应用服务,这些服务分成了两类:

image.png

一类是 ApplicationService 一类是 QueryService 。

ApplicationService里的方法我们上篇文章里见过了,方法的入参是一个 Command 对象,返回值是一个 CommandResponse 对象。方法里通过 Repository 获取聚合根,操作聚合根后,再通过 Repository 来持久化。这就是CQRS里的"C",即命令(Command),它会改变系统的状态。

在collaboration这个上下文的实现里,没有把入参包装成Command对象,可以参考agilepm上下文中ApplicationService的实现。IDDD_Sample为了演示各种风格,不同上下文的实现方式不太统一。也有人是把这种ApplicationService里的一个个方法变成一个个CommandHandler,可读性更好,但本质上是一样的。

CQRS里的"Q"指的是Query,顾名思义,查询不会改变系统状态的。

那分离是什么意思呢?

最粗浅的理解是把这些查询方法放到 QueryService 里,而不是放到ApplicationService 里。但这只是个表象。我们看一个Query方法是怎么写的:

public ForumDiscussionsData forumDiscussionsDataOfId(String aTenantId, String aForumId) {         return this.queryObject(                 ForumDiscussionsData.class,                 "select "                 +  "forum.closed, forum.creator_email_address, forum.creator_identity, "                 +  "forum.creator_name, forum.description, forum.exclusive_owner, forum.forum_id, "                 +  "forum.moderator_email_address, forum.moderator_identity, forum.moderator_name, "                 +  "forum.subject, forum.tenant_id, "                 +  "disc.author_email_address as o_discussions_author_email_address, "                 +  "disc.author_identity as o_discussions_author_identity, "                 +  "disc.author_name as o_discussions_author_name, "                 +  "disc.closed as o_discussions_closed, "                 +  "disc.discussion_id as o_discussions_discussion_id, "                 +  "disc.exclusive_owner as o_discussions_exclusive_owner, "                 +  "disc.forum_id as o_discussions_forum_id, "                 +  "disc.subject as o_discussions_subject, "                 +  "disc.tenant_id as o_discussions_tenant_id "                 + "from tbl_vw_forum as forum left outer join tbl_vw_discussion as disc "                 + " on forum.forum_id = disc.forum_id "                 + "where (forum.tenant_id = ? and forum.forum_id = ?)",                 new JoinOn("forum_id", "o_discussions_forum_id"),                 aTenantId,                 aForumId);     }

来自 ForumQueryService

我们发现这个查询服务里即没有使用领域对象也没使用 Repository ,甚至 join 的两个表是代表两个聚合根的数据!在Query方法里可以直接查询数据库去返回一个查询显示用的DTO,这可以看做是分离的第一个意思,即Query里可以不使用领域对象和 Repository 。

我觉得可以不使用,意味着也可以使用

第二个意思是数据存储的分离。最简单的方式是处理 Command 的 ApplicationService/CommandHandler 访问的是主库,而 QueryService 访问的是从库。

在复杂一点,ApplicationService 可以访问MySQL,而 QueryService 可以访问Elasticsearch这种NoSQL,这时候需要做数据的同步。触发数据同步,可以监听领域事件来实现,也可以使用canal这种框架监听binlog来实现。除了专门为查询而设计的NoSQL本身有更好的查询性能,同时,我们在数据结构设计上也可以专门为查询来做优化,比如把多个关联聚合的数据放到一起。

image.png

其实,IDDD_Sample的例子中的 collaboration 这个上下文就是这么实现的——领域对象的存储使用的是LevelDB,查询用的是MySQL。

这种方式我很多年前就看到一个团队在用,他们也把Service分为WriteServcie和ReadService两种,但是并没有叫CQRS这个名字,也没有总结为一种模式,而是凭着经验这么做了。

事件溯源和CQRS

和CQRS经常一起出现的一种模式是事件溯源(Event Sourcing)。事件溯源是一种更"前卫"的模式,它不存储对象的状态,相反,存储影响其状态的所有事件。

需要查询对象的当前状态时,只要把所有事件回放一遍就能得到。但是,这种回放太昂贵了,所以可以保留一份对象的最新快照,这就需要和CQRS模式结合。

image.png

IDDD_Sample例子中的 collaboration 这个上下文实际上使用的就是事件溯源。我们看它的 Repository 实现,存储的是事件而不是实体本身:

事件溯源加上CQRS很"酷",但一般不建议使用,实现的成本太高。

public class EventStoreForumRepository         extends EventStoreProvider         implements ForumRepository {      @Override     public void save(Forum aForum) {         EventStreamId eventId =                 new EventStreamId(                         aForum.tenant().id(),                         aForum.forumId().id(),                         aForum.mutatedVersion());          this.eventStore().appendWith(eventId, aForum.mutatingEvents());     } }

即使是只使用CQRS这一模式,也建议循序渐进,从简单开始,最基本的,把QueryService 和 ApplicationService 分开,且在设计领域模型时不要受 QueryService 设计的影响。

以上是"如何使用CQRS避免查询对模型设计的影响"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

查询 设计 模型 对象 数据 事件 领域 方法 模式 存储 影响 上下 上下文 状态 性能 两个 内容 就是 意思 方式 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 金诺互联网科技 与互联网科技公司 互联网科技媒体 互联网 科技馆 互联网和高科技公司 欧洲十大科技互联网公司 金融科技与互联网金融相同和不同 华服互联网科技官网 河南猎农互联网科技有限公司 宝灵互联网科技有限公司 成都互联网科技公司工作好不好 用友工业互联网科技发展 高科技互联网套路 广州智享互联网科技 唯美互联网科技 银川新华互联网科技学校原画 第三届互联网大会安防黑科技 恒生科技指数 中概互联网 互联网科技对化妆品行业 银川新华互联网科技学校招聘 奇酷互联网科技公司 互联网科技小额贷款 互联网科技实习生岗位职责 互联网企业和科技型企业 深圳育鹏互联网科技有限公司招聘 山东子淼互联网科技有限公司 城市宝互联网科技有限公司 宇信科技在杭州互联网排第几 易租星互联网科技有限公司 米泉互联网科技专业怎么样
0