曾几什么时候,你是否疑惑于VO、PO、DTO、BO、POJO、Entity、MODEL的区别?html
你是否有过疑问,为何Java里有这么多的以O为名称结尾的对象?!web
你是否也厌倦了编写从这个O对象到那个O对象之间的转换代码?!数据库
你有没有想过,这一切的根源在哪里呢?有没有办法解决这个问题呢?markdown
本文试图给你答案!数据结构
在架构风格:万金油CS与分层一文中提到,分层架构是个万金油架构,当你没法肯定该使用哪一种架构风格的时候,那么能够先使用分层架构。而实际上确实是这样,大部分的应用都采用了分层架构,特别是web应用。架构
以最简单的三层架构来讲:异步
每一层都负责各自的任务、职责单一,开发也就相对简单。每一层相对独立,因此都可以独立进化,这是分层架构所宣称的优点!也是其「原罪」!oop
分层架构虽然将系统按层进行划分,可是层与层之间仍是须要进行交互的。交互就须要有接口或协议以及传输的数据。性能
对于外部调用,咱们可使用TCP、HTTP、RPC、WebService等方式来进行通讯;而对于内部交互来讲,咱们能够直接使用方法调用,使用接口来进行解耦。优化
可是传输的数据结构该如何定呢?
各层的独立进化,致使了交互的额外操做!这就是分层架构的「原罪」!也是须要这么多传输对象的其中一个缘由!
而另一个缘由是表现力差别!
在领域设计:聚合与聚合根聊到了表现力问题,「数据设计」的表现力要弱于「对象设计」!相对应的,其实「数据展示」的表现力也是弱于「对象设计」的!
咱们仍是以订单来举例!假设我下单购买了多个商品,也就是说一个订单包含了多个明细。那么订单与订单明细的这层关系在「持久层」是经过主键来表现的:
订单明细包含了订单的主键,表示哪些订单明细是属于哪一个订单的。
而这层关系在「逻辑层」是经过对象引用来表现的:
订单对象中持有了指向订单明细列表的引用。
而到了「展现层」,订单和订单详情之间的关系就彻底靠展现方式来表现了:
若是你不了解业务,光看代码,是看不出订单与订单明细之间的关系的。上面只是纯粹的展现了订单明细在订单信息的下面。
也就是说,当咱们访问页面的时候,请求从「持久层」将扁平的数据查询到了「逻辑层」,组装成告终构化的对象,最后被传递到了「展示层」,又被拍扁了展现在咱们面前。
因为每层表现形式的不一样,亦致使了须要数据传输对象。
既然横向封层不可避免的须要数据传输对象来解耦各层之间的关系,那咱们是否不使用横向封层,而使用纵向切分呢?这就是CQRS架构模式!
CQRS经过对系统进行纵向切分:将「数据读」和「数据写」分离开,使得数据读写独立进化,来解决数据显示复杂性问题。
CQRS架构以下:
流程以下:
这又什么优点呢?
咱们以订单保存和展现流程来详细的看一下CQRS的优点!
对于普通分层架构来讲,在保存订单时须要一个DTO用于存储相关信息,而后转成多个对应的Model来进行持久化;而查询订单的时候,你须要查询出多个Model,而后组装成另外一个DTO来存储查询的信息,由于展现的时候可能要展现更多的信息,好比买家和卖家相关信息。
同时因为数据都存储在数据库中,且表结构与Model是对应的,你能作的优化就是数据库相关的优化手段。
而在CQRS中,数据库被分红了读库和写库。那存在读库中的数据结构就能够彻底按照展现逻辑来优化,好比:我能够有一张订单展现表,表中包含了买家信息和卖家信息。在展现时,直接查询这张表就能够了,不须要和用户表进行关联查询,提升了数据读性能。
而对于数据持久化来讲,就不须要考虑数据展现了,只要提升持久化性能就能够了。例如不使用数据库,而使用顺序写入的文件方式。同时也不必定要存储数据自己,转而存储事件,就能够实现事件重演,这就是事件溯源。
在领域设计:Entity与VO一文中,提到了「状态」!
通常咱们处理状态都是直接去修改它,像下面这样:
那么请问,这个开关刚才经历了什么?!这是典型的ABA问题,即你只知道这个开关目前的状态,可是它曾经有没有开过或关过,你就无从得知了。
咱们对数据的处理也是这样,你只知道当前存在数据库中的数据是什么,而它曾经被修改过没有?被修改为过什么,你无从知晓。
由于咱们存的只是「即时状态」,即「快照」!
事件溯源存储的不是数据「快照」,而是「事件自己」!即它记录了全部对该数据的事件。
若是你了解Redis的持久化方案,你对事件溯源就必定不会感到陌生。Redis有两种持久化方式RDB方式和AOF方式:
咱们通常的持久化方式实际对应的就是Redis的RDB方式,而事件溯源就是AOF方式。
回到上图,在CQRS中,WriteDB能够经过类AOF的方式来存储命令,也就是事件溯源。当须要对ReadDB中的数据进行恢复操做时,能够经过命令重演的方式来恢复。
不过你应该发现问题了,命令重演的方式性能上有问题。因此咱们能够参考Redis,使用快照+事件溯源的方式来存储。即WriteDB中存储事件,额外再定时对数据进行快照备份。恢复数据时先经过快照备份恢复,再从指定位置进行命令重演,来提升性能。
读写分离后,致使的一个问题就是读写一致性。在原来的分层架构中,数据写入后再读取,是能够当即读取到写入的数据的(事务保障)。
可是读写分离后,读到的数据不必定是写入的最新数据。通常状况下,这个问题并不大。由于实际上你读的基本上都是历史数据!为何这么说呢?由于你无法保证数据在展示到你面前的过程当中,没有新的写入。除非展现是基于推送机制的。
可是对于特殊状况下,可能不能容忍这样的状况。有几种解决方案: