一、分离查询命令 Separating commands from queries

    早期的面向DDD设计方法的难点是如何设计一个类,这个类要包含域的方方面面。通常来说,任务软件系统方法调用可以分为两类:查询和命令。在这里,查询是指一个系统的和个操作,它不会改变系统的任务值,仅返回一些结果。命令的职责是个性系统数据。 如果两组方法都使用相同的域模型,逻辑上可能存在查询和命令分离不明显问题,所以引入新的设计模式。

  1. 从域模型到 CQRS

    从某种程序度上,CQRS是复杂的域模型设计的一种横向设计思维。如果域模型客观上就是复杂的,我们使用CQRS是否还需要? 从目前的设计理念看来,CQRS使用的是两个不同的域模型,而不是一个。分离是通过将查询和命令分离到两个层上,它们各有自己的架构体系和服务集合。

  • 查询和命令域层结构

    命令和查询的简单对系统的设计影响可能让人惊讶。系统的组织作为两个并行分支,如图上图 所示的体系结构,有一个正式的域模型是系统的严格要求。

    在提供查询服务的时候我们可能不需要域模型,查询仅仅是用记接口用记查询数据的方法。查询的结果中有可能有命令,所以域模型中设计的各种关系和约束都是没必要的。域模型的查询区域专门定制简单的数据传输类DTO做为数据载体。这种情况下,域服务可能成为使用弱类来实现业务有的某些功能。

  •     CQRS不是顶层设计

    不同 于DDD,CQRS 不是企业级系统设计的综合方法。CQRS是为你在大型系统中为上下文边界设计的一个指导模式。DDD设计基本统一语言,贯穿于整个系统设计。

  • CQRS的优点

        -简单化设计 Simplifcation of the design

     在域模型交互设计中,通常要对面系统的复杂点是改变系统状态的操作。命令需要验证当前状态并决定是否执行,命令同步要保持系统数据的一致性。在读写共享相同的数据实体时很难确保一个操作不会做出意料之外的读写操作。早期我们就认识到模型的命令复杂度以笛卡尔积式增长的。称N为查询和命令的复杂度。在单个域模型中,在查询的规定和约束影响命令和反之亦然,如同笛卡尔积,复杂度的成长为N*N。如果将查询和命令分离,模型的复杂度则为N+N。

        -增强可扩展性的潜力 Potential for enhanced scalability 

    可扩展性有很多方面的因素,针对性解决方法是保持每一个系统的唯一性。通常,可扩展性指系统的可维护性以及在用户增长数量级下的性能。架构 的可扩展性取决于大多数方法的操作类型,如果读取是主要的操作类型,可以引入缓存彻底解决数据库的读取压力。如果是写操作将系统拖慢,应考虑使用异步写替代同步,或使用队列。读写分离后,对系统的可扩展性可以更容易、更有针对性的处理。

  • 在业务逻辑层使用CQRS

        CQRS实际上没有什么负面影响,如何使用CQRS取决你如何理解CQRS,目前来说它只用于一种模式,在两个不同的层,一个是查询服务层一个是命令服务层。系统中所有的部分就会由CQRS带来益处,并且不需要太多的学习成本。

  • 查询堆栈 The query stack

        只读域模型 The read domain model

    一个只用于读的域模型要比读写兼备的域模型简单的多。有下面一个问题,一个Order类中有一个产品类项列表属性。该属性本质上包含可枚举的数据,但不知道Items应该是哪种类型,第一种方法是使用泛型IList<T>,这种方法可以实现:

public IList<OrderItem> Items { get; private set; }

使用ReadOnly属性是更好的先择。Read-only不允许更改集合的结构,此外,如果作为包装器,用于常规的列表创建只读集合,则对基础列表的更改不影响只读包装

public class Order

{

    private readonly IList<OrderItem> _items;

    public Order()

    {

        _items = new List<MOrderItem>();

    }

    public ReadOnlyCollection<OrderItem> Items

    {

        get

        {

            return new ReadOnlyCollection<OrderItem>(_items);

        }

    }

    public void Add(int id, int quantity)

    {

        _items.Add(new OrderItem(id, quantity));

    }

}

public class OrderItem

{

    public OrderItem(int id, int quantity)

    {

        Quantity = quantity;

        ProductId = id;

    }

    public int Quantity { get; /*private*/ set; }

    public int ProductId { get; /*private*/ set; }

}

 

            设计只读模型

    查询堆栈可能仍然需要域服务从存储中提取数据,并为它服务达应用程序和表示层。在这种情况下,域名服务和专门的存储库,应将重定向允许只读取的操作在存储上。在这种情况下,域名服务和专门的存储库应将重定向允许只读取的操作在存储上。

……………………………………

  •  命令堆栈The command stack

    的CQRS场景下,Command是的唯一作用就是改变系统的数据。通常应用层接受来处表现层的数据并执行。命令是针对后端,如注册一个新用户、 处理购物车的内容或更新的客户配置文件等数据落地操作。CQRS 的角度来看,命令就是数据持久化的单向操作。

    任务有两种方式被触发,一种是用户在UI上明确的开始一项任务,别一种是由系统的一些服务自动触发的任务。命令的主要任务是更新系统数据,但有时候调用都需要返回一些数据来确认调用是否成功。

  • 命令与事件

    有两种类型的消息:命令和事件。两种类型中命令是一种数据包,命令是系统执行请求的必要数据。它们有相同点也有不同点

    命令由调用者直接发出

    命令可以被系统驳回

    命令可能会执行失败

    基于网络的命令会依赖系统的当前状态

    事件不能由系统驳回或取消

    事件可以有多外处理者

    The processing of an event can, in turn, generate other events for other handlers to process.

    An event can have subscribers located outside the bounded context from which it originated

    
 

    事件类写法,如下面的代码,命令和事件都继承自Message类。

public class CheckoutCommand : Message

{

    public string CartId { get; private set; }

    public string CustomerId { get; private set; }

    public CheckoutCommand(string cartId, string customerId)

    {

        CartId = cartId;

        CustomerId = customerId;

    }

}

Conversely, here's the layout of an event class.

public class DomainEvent : Message

{

// Common properties

...

}

public class OrderCreatedEvent : DomainEvent

{

    public string OrderId { get; private set; }

    public string TrackingId { get; private set; }

    public string TransactionId { get; private set; }

    public OrderCreatedEvent(string orderId, string trackingId, string transactionId)

    {

        OrderId = orderId;

        TrackingId = trackingId;

        TransactionId = transactionId;

    }

}

 

命令与事件处理

     命令由一个被称为Command bus的处理者来管理。事件由Event bus组件来管理。有此时候命令和事件由同一个bus来处理。下图是基于一个事件的CQRS解决方案。所有的任务都是由用户接口发起,在Asp.net MVC中Controller中的Action接收请求并向应用层发起命令。

 

 

Bus 组件

   Command Bus持有一系统已知业务处理器,这些处理器可以有命令来触发。事件的处理同时会在域中产生许多事件。生成的事件被发布到同一个命令bus或Event bus。Comand Bus是一个接收消息并且找出执行方法的单独的类,Bus类不会自己执行实际要处理的任务,它会选择一个已注册的处理者来处理事件或命令。

public interface IHandles

{

void Handle(T message);

}

接口同时处理命令和事件

public class Bus

{

    private static readonly Dictionary<Type, Type> SagaStarters =

    new Dictionary<Type, Type>();

    private static readonly Dictionary<string, object> SagaInstances =

    new Dictionary<string, object>();

    public static void RegisterSaga<TStartMessage, TSaga>()

    {

        SagaStarters.Add(typeof(TStartMessage), typeof(TSaga));

    }

    public static void Send<T>(T message) where T : Message

    {

        // Publish the event

        if (message is IDomainEvent)

        {

            // Invoke all registered sagas and give each

            // a chance to handle the event.

            foreach (var saga in SagaInstances)

            {

                var handler = (IHandles<T>)saga;

                if (handler != null)

                    handler.Handle(message);

            }

        }

        // Check if the message can start one of the registered sagas

        if (SagaStarters.ContainsKey(typeof(T)))

        {

            // Start the saga creating a new instance of the type

            var typeOfSaga = SagaStarters[typeof(T)];

            var instance = (IHandles<T>)Activator.CreateInstance(typeOfSaga);

            instance.Handle(message);

            // At this point the saga has been given an ID;

            // let's persist the instance to a (memory) dictionary for later use.

            var saga = (SagaBase)instance;

            SagaInstances.Add(saga.Data.Id, instance);

            return;

        }

        // The message doesn't start any saga.

        // Check if the message can be delivered to an existing saga instead

        if (SagaInstances.ContainsKey(message.Id))

        {

            var saga = (IHandles<T>)SagaInstances[message.Id];

            saga.Handle(message);

            // Saves saga back or remove if completed

            if (saga.IsComplete())

                SagaInstances.Remove(message.Id);

            else

                SagaInstances[message.Id] = saga;

        }

    }

}

  Bus的功能就是做命令映射和事件分发。

 

Saga组件

一般情况下,一个Saga组件看起来像逻辑上相关的方法和事件处理程序的集合。每个Saga是一个组件,它声明了以下信息:

  •  命令或启动与Saga组件关联进程的事件
  • 命令Saga组件可以处理和aga组件感兴趣的事件

 

 

 

 

        
 

 

 

 

 

.net架构设计读书笔记--第三章 第10节 命令职责分离(CQRS)简介(Introducing CQRS)的更多相关文章

  1. .net架构设计读书笔记--第三章 第8节 域模型简介(Introducing Domain Model)

    一.数据--行为转变     很长的时间,典型的分析方法或多或少是以下两种,第一,收集需求并做一些分析,找出有关实体 (例如,客户. 订单. 产品) 和进程来实现. 第二,手持这种理解你尝试推断一个物 ...

  2. .net架构设计读书笔记--第三章 第9节 域模型实现(ImplementingDomain Model)

        我们长时间争论什么方案是实现域业务领域层架构的最佳方法.最后,我们用一个在线商店案例来说明,其中忽略了许多之前遇到的一些场景.在线商店对很多人来说更容易理解. 一.在线商店项目简介 1. 用例 ...

  3. 《Linux内核设计与分析》第六周读书笔记——第三章

    <Linux内核设计与实现>第六周读书笔记——第三章 20135301张忻估算学习时间:共2.5小时读书:2.0代码:0作业:0博客:0.5实际学习时间:共3.0小时读书:2.0代码:0作 ...

  4. .net架构设计读书笔记--第一章 基础

    第一章 基础 第一节 软件架构与软件架构师  简单的说软件架构即是为客户构建一个软件系统.架构师随便软件架构应运而生,架构师是一个角色. 2000年9月ANSI和IEEE发布了<密集性软件架构建 ...

  5. 《linux内核设计与实现》读书笔记第三章

    第3章 进程管理 3.1 进程 1.进程 进程就是处于执行期的程序. 进程包括: 可执行程序代码 打开的文件 挂起的信号 内核内部数据 处理器状态 一个或多个具有内存映射的内存地址空间 一个或多个执行 ...

  6. .net架构设计读书笔记--第二章 第7节 神化般的业务层

    一.编排业务逻辑的模式1. 事务脚本模式TS(The Transaction Script pattern ) TS模式概述     TS 鼓励你跳过任何的面向对象的设计,你直接到所需的用户操作的业务 ...

  7. 《Linux内核设计与实现》读书笔记 第三章 进程管理

    第三章进程管理 进程是Unix操作系统抽象概念中最基本的一种.我们拥有操作系统就是为了运行用户程序,因此,进程管理就是所有操作系统的心脏所在. 3.1进程 概念: 进程:处于执行期的程序.但不仅局限于 ...

  8. 《CSS3实战》读书笔记 第三章:选择器:样式实现的标记

    第三章:选择器:样式实现的标记 选择器的魔力在于,让你完全实现对网页样式的掌控.不同的选择器可以用在不同的情况下使用.总之把握的原则是:规范的编码,根据合理地使用选择器,比去背选择器的定义有价值的多. ...

  9. .net架构设计读书笔记--第二章 设计体系结构

    第五节 探索领域架构 一.领域驱动设计的价值与意义 最初在java中使用,.net要晚些才引入.领域驱动设计出现之初的争议.一个向导,少走弯路   1. 我们真的需要DDD吗? DDD并不适用于每个软 ...

随机推荐

  1. 伪多项式时间算法Pseudo-polynomial Algorithms-----geeksforGeek 翻译

    原创翻译加学习笔记,方便国人学习算法知识! 原文链接http://www.geeksforgeeks.org/pseudo-polynomial-in-algorithms/ 什么是伪多项式? 当一个 ...

  2. 转: java学习路线图

    http://www.java1234.com/javaxuexiluxiantu.html

  3. STL之stack栈

    栈(statck)这种数据结构在计算机中是相当出名的.栈中的数据是先进后出的(First In Last Out, FILO).栈只有一个出口,允许新增元素(只能在栈顶上增加).移出元素(只能移出栈顶 ...

  4. FusionCharts V3图表导出图片和PDF属性说明(转)

    百闻不如一见,狠狠点击,快快下载:(演示文档有错误,不提供下载了.待新的演示文档出来.) 许多朋友说上面的DEMO用不了.fusioncharts官方的演示非常不错,就是来不及整理,各位大侠们可以研究 ...

  5. C# 无边框窗体之窗体移动

    点击窗体任意位置移动窗体: 需要添加命名空间: using System.Runtime.InteropServices; private const int WM_NCLBUTTONDOWN = 0 ...

  6. DWZ(JUI)的lookupGroup增加回调函数

    DWZ 是一个很好的富客户端框架 lookupGroup也是一个必用到的东东,但没有回调函数,后期处理相当的不便. 修改其dwz.database.js 增加几行,就能实行一个很好的回调. 使用就方便 ...

  7. 2016温碧霞爱情《爱在深秋》HD720P.国语中字

    导演: 林家威编剧: 林家威 / 李非 / 黄国兆主演: 温碧霞 / 谭耀文 / 赵炳锐 / 方皓玟 / 王建成类型: 爱情制片国家/地区: 香港语言: 汉语普通话上映日期: 2016-01-22(中 ...

  8. Arduino小车学习与研究博客

    Arduino小车学习与研究博客 信安系统设计基础实践模块 Arduino小车学习与研究 ================== 陈都(20135328) 余佳源(20135321) 莫凡(201352 ...

  9. 20135328信息安全系统设计基础第一周学习总结(Linux应用)

    学习计时:共xxx小时 读书: 代码: 作业: 博客: 一.学习目标 1. 能够独立安装Linux操作系统   2. 能够熟练使用Linux系统的基本命令   3. 熟练使用Linux中用户管理命令/ ...

  10. 【面试必备】Swift 面试题及其答案

    初级 问题1- Swift 1.0 or later 什么是optional类型,它是用来解决什么问题的? 答案:optional类型被用来表示任何类型的变量都可以表示缺少值.在Objective-C ...