一、分离查询命令 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. QEMU启动时插入tap虚拟网卡

    1.利用brctl命令创建虚拟网桥br0 brctl addbr br0 ifconfig br0 up //上述两条命令分开执行会导致网络断开 2.将虚拟网桥br0与物理网卡eth0绑定 brctl ...

  2. Codeforces Round #267 Div.2 D Fedor and Essay -- 强连通 DFS

    题意:给一篇文章,再给一些单词替换关系a b,表示单词a可被b替换,可多次替换,问最后把这篇文章替换后(或不替换)能达到的最小的'r'的个数是多少,如果'r'的个数相等,那么尽量是文章最短. 解法:易 ...

  3. 开坑,Unix环境高级编程,转行之路又得缓缓了

    不要问我基础,我用了近6年的Linux系统,最早的版本可以追溯到Ubuntu 8.04,常用的命令 VIM基本上是没压力,遇到问题google 配置环境变量 网络环境也不在话下, C语法基本熟练,过去 ...

  4. js中的return,return true,return false小结

    return  函数执行到这句时会终结,并返回调用函数,而且把表达式的值作为函数的结果返回 return false 可以防止默认的事件行为.例如,默认情况下点击一个<a>元素,页面会跳转 ...

  5. css常用中文字体的英文名称写法

    我们知道网页中使用中文字体最好用其对应的英文名称,这样可以避免出现编码问题导致样式中的中文名称出现乱码,从而不识别.下面是网页中常用的中文字体所对应的英文名称.留着,不用翻资料了 中文 英文 宋体 S ...

  6. Silverlight 利用DataGrid行加载事件动态控制行列显示

    datagrid的绑定很好用,但有时候我们往往需要根据model内容来动态控制行或者列,该怎么办呢? 这时候,我们就需要用到行加载事件:在加载每一行数据的时候,根据数据的内容来控制相应的表格显示. 比 ...

  7. 神奇的GO语言:空接口(interface)

    对于go语言来说,设计最精妙的应该是interface了,直白点说interface是一组method的组合.至于更加详细的描述,本文不做介绍,今天谈谈空接口. 空interface(interfac ...

  8. VMware 不可恢复错误(svga)”解决方法

    虚拟机VMware 文件在迁移到另一台计算机时出现"VMware Workstation 不可恢复错误(svga)"  将另一台机器的 VMware 文件拷贝至本机,打开虚拟机出现 ...

  9. C语言 日常小结

    1.当数组当作函数参数的时候会退化为指针 #include<stdio.h> #include<stdlib.h> void sort(int a[]){ int num = ...

  10. GPS坐标换算为百度坐标

    最近在做一个关于手机定位的小应用,需求是这样的,用户通过手机(Wp8)进行二维码扫描操作并且记录用户的当前位置,在PC上可以查看用户所在地图的位置,做法就是在用户扫描条码时,通过手机GPS获取当前在地 ...