一种简单的CQRS架构设计及其实现
一、为什么要实践领域驱动?
近一年时间我一直在思考一个问题:“如何设计一个松耦合、高伸缩性、易于维护的架构?”。之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现业务逻辑。在项目初期使用这样的方式野蛮开发似乎显得很高效,但是大家其实都清楚,正是这样的项目让大家拖入了加班的深渊。这种系统维护性差,无法扩展,无法编写有效的单元测试,质量基本没有保证。
一个符合我心理预期的架构,一定不是靠使用某个代码生成工具来完成的,这样的项目把码农培养成了彻头彻尾没有思想的个体。一个有追求的码农,请远离只会让你使用代码生成工具的“架构师”。
既然数据库驱动的开发方式存在一些致命的问题,我们很自然的想到了领域驱动开发(DDD)。在《我眼中的领域驱动开发》一文中,我提到了领域驱动设计不同于传统软件开发的一些思考方式。这些思想看起来很简单,但是真正实践起,思维的转变还是有一定的难度。
在我职业生涯里遇到的每一个同事,在讨论问题时都能够表现出自己擅长的一面,有的人思路比较清晰,有的人想法比较特别,跟这些人在一起讨论方案往往都会够给你灵感。领域驱动开发这种方式需要融入有生命力的想法,每一个人都可以参与到设计当中,软件开发才会变成一件有意思的事情。
二、使用EF来实践领域驱动
我曾尝试使用单纯的EF来实践领域驱动开发,老实说,我没法实践成功。我想忘掉数据库,但是很难。为什么这样说?我们知道领域驱动讲求以领域模型为基本单位思考问题,比如一次购物过程可能是一个领域模型,我抛开了数据库,建立的模型。这时来了一个需求,一个界面需要能够查询一个范围内的购物记录,可能还有一些复杂的条件,这时候我的领域模型并不能很好的支持这样的查询,因为我在这之前压根没有考虑表结构如何设计,我的领域模型不是为查询设计的,所以这样的一个查询很容易存在性能问题。所以忘掉数据库这一命题很难实现。
三、为什么会存在这样的问题?
发生了这样的问题我就在思考到底是哪里出了问题,是领域驱动开发这种软件开发方式存在问题吗?
造成这种局面的根本原因在于“我们给Domain强加了查询的职责,长久以来的软件开发模式太过于依赖于关系数据库”,关系数据库的首要任务是持久化数据,查询只是他的部分能力,长久以来我们即想让关系数据库存储海量数据,又想让查询变得更高效。显然,我们对关系型数据库的要求有点过分了。
我想这也是Greg Young提出了CQRS架构的原因,当我们在领域模型中去掉搜索和查询的职责后,问题引刃而解。
四、实现CQRS有哪些难点?
既然CQRS才是实践DDD的最佳途径,那么我们就可以使用CQRS了么?提到CQRS,不得不提到博客园的两位CQRS元老级人物,dax.net和netfocus,他们两位都贡献了自己的CQRS框架,但是我猜测,即便是他们两位大神也不会轻易在真实场景下使用CQRS架构。在我看来要真正实践CQRS需要天时、地利、人和三者具备。
1、经典的CQRS是通过Event Sourcing来实现领域模型的还原和Query端的同步,这就要求领域模型的设计一定要一次到位,一旦Event被持久化到生产环境,这时候再修改设计就会带来极大的难度。
2、对开发人员要求非常之高、要建立这样的一直团队需要长时间的磨合和培养。
3、采用CQRS的DDD由于对设计要求较高,所以在开发初期并不能很快看到效果,这就要求公司能够容忍、理解并且支持,这条似乎是最难符合条件的。
五、实现一个简单的CQRS方案
经过分析,要想成功实践领域驱动,查询与命令职责一定要分离,我们还是要实现CQRS,但是不再通过Event Sourcing来实现,从而减少复杂度。下面就是我的一个不太成熟的想法:
1、Domain通过EF持久化。
2、每个Domain逻辑在实现中都将产生两种固定类型的Event,***CreatedEvent和***UpdatedEvent。
3、由于取消了Event Sourcing,所以降低了Domain设计的复杂度,此时的关系型数据就相当于最新的快照。
4、在EF的UnitOfWork执行成功后注册发布事件的回调,确保领域模型持久化成功才发布事件。
5、事件发布到双工ServiceBus中,双工ServiceBus包含一个InMemory的同步ServiceBus和一个支持消息队列的异步ServiceBus。
6、领域发布事件后,先有InMemory的同步ServiceBus更新由Redis实现的QueryModel,确保界面会同步刷新,单个Domain的读取将通过Redis来实现。紧接着消息会发送到支持消息队列的异步ServiceBus。
7、消息进入消息队列后供其他子系统消费,同时更新Elastic Search,界面的复杂搜索将通过Elastic Search来实现。
8、此时的系统已经演化为一个松耦合、可扩展、基于领域模型的分布式架构方案。
当查询和读取Domain的职责被分摊到Elastic Search和Redis后,这时候再设计领域模型就不会有外界因素来干扰你。
另外整个系统不会出现一句sql,让sql这种反人类的语言去见鬼吧,此时要是还有谁要是觉得“必须得写sql,sql效率高”。你过来,我保证不打死你。o(^▽^)o
目前的Demo基于以上的想法正在一步步实现中,请关注。另外,由于水平有限,整个架构设计难免有不合理的地方,欢迎提出宝贵意见。
https://git.oschina.net/richieyangs/BookLibrary
一种简单的CQRS架构设计及其实现的更多相关文章
- CQRS架构设计及其实现
CQRS架构设计及其实现 一.为什么要实践领域驱动? 近一年时间我一直在思考一个问题:“如何设计一个松耦合.高伸缩性.易于维护的架构?”.之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现 ...
- .net mvc中一种简单的工作流的设计
开篇前的废话:工作流是我们在做互联网应用开发时经常需要用到的一种技术,复杂的工作流我们基本是借助一些开源的 工作流项目来做,比如 ccflow等,但是有时候,我们只需要实现一些简单的工作流流程,这时候 ...
- SpringBank 开发日志 一种简单的拦截器设计实现
当交易由Action进入Service之前,需要根据不同的Service实际负责业务的不同,真正执行Service的业务逻辑之前,做一些检查工作.这样的拦截器应该是基于配置的,与Service关联起来 ...
- 架构设计:一种远程调用服务的设计构思(zookeeper的一种应用实践)
在深入学习zookeeper我想先给大家介绍一个和zookeeper相关的应用实例,我把这个实例命名为远程调用服务.通过对这种应用实例的描述,我们会对zookeeper应用场景会有深入的了解. 远程调 ...
- DDD CQRS架构和传统架构的优缺点比较
明天就是大年三十了,今天在家有空,想集中整理一下CQRS架构的特点以及相比传统架构的优缺点分析.先提前祝大家猴年新春快乐.万事如意.身体健康! 最近几年,在DDD的领域,我们经常会看到CQRS架构的概 ...
- ENode框架Conference案例分析系列之 - 架构设计
Conference架构概述 先贴一下Conference案例的在线地址,UI因为完全拿了微软的实现,所以都是英文的,以后我有空再改为中文的. Conference后台会议管理:http://www. ...
- 微软&中科大提出新型自动神经架构设计方法NAO
近期,来自微软和中国科学技术大学的刘铁岩等人发表论文,介绍了一种新型自动神经架构设计方法 NAO,该方法由三个部分组成:编码器.预测器和解码器.实验证明,该方法所发现的架构在 CIFAR-10 上的图 ...
- .net架构设计读书笔记--第三章 第10节 命令职责分离(CQRS)简介(Introducing CQRS)
一.分离查询命令 Separating commands from queries 早期的面向DDD设计方法的难点是如何设计一个类,这个类要包含域的方方面面.通常来说,任务软件系统方法调用可以 ...
- 一个简单可参考的API网关架构设计
网关一词较早出现在网络设备里面,比如两个相互独立的局域网段之间通过路由器或者桥接设备进行通信, 这中间的路由或者桥接设备我们称之为网关. 相应的 API 网关将各系统对外暴露的服务聚合起来,所有要调用 ...
随机推荐
- [高并发]Java高并发编程系列开山篇--线程实现
Java是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相 ...
- BZOJ 1911: [Apio2010]特别行动队 [斜率优化DP]
1911: [Apio2010]特别行动队 Time Limit: 4 Sec Memory Limit: 64 MBSubmit: 4142 Solved: 1964[Submit][Statu ...
- .NET Core RC2/RTM 明确了时间表
.NET Core 经过了将近2年的开发,去年12月份发布的RC1版本,明确来说那只是一个beta版本,自从RC1发布以来,看到github里的RC2分支,整个工具链都发生了很大的变化,大家都在焦急的 ...
- Syscan360会议胸牌破解揭秘
Syscan360会议胸牌破解揭秘 背景 有幸参加今年11月份的上海Syscan360安全会议,会议期间有一个亮点就是360的独角兽团队设计了一款电子badge(胸牌)供参加人员进行破解尝试,类似于美 ...
- C++中的命名空间
一,命名空间(namespace)的基本概念以及由来 1.什么是标识符: 在C++中,标识符可以是基本的变量,类,对象,结构体,函数,枚举,宏等. 2.什么是命名空间: 所谓的命名空间是指标识符的可见 ...
- 浅谈web攻防
CSRF 跨站请求伪造(Cross-Site Request Forgery) -原理- 从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤: 1.登录受信任网站A,并在本地生成Coo ...
- WPF中Grid实现网格,表格样式通用类
/// <summary> /// 给Grid添加边框线 /// </summary> /// <param name="grid"></ ...
- JAVA 分页工具类及其使用
Pager.java package pers.kangxu.datautils.common; import java.io.Serializable; import java.util.List; ...
- 报错:You need to use a Theme.AppCompat theme (or descendant) with this activity.
学习 Activity 生命周期时希望通过 Dialog 主题测试 onPause() 和 onStop() 的区别,点击按钮跳转 Activity 时报错: E/AndroidRuntime: FA ...
- 两个变量交换的四种方法(Java)
对于两种变量的交换,我发现四种方法,下面我用Java来演示一下. 1.利用第三个变量交换数值,简单的方法. (代码演示一下) class TestEV //创建一个类 { public static ...