引言

在《设计模式沉思录》(Pattern Hatching: Design Patterns Applied,[美]JohnVlissides著)一书的第4章中,围绕事件Message传递的推-拉模型(Push-Pull),谈到了一个最初被称为Multicast,之后被定型为Typed Message的设计模式。

该模式的初衷是希望得到一个可扩展的、类型安全的事件传递机制,并能符合两点要求:

  • 通过推模型来将事件传递给消费者。
  • 特有的每个事件只需要最多新增一个类,而不需要对已有代码进行修改。

在阅读过程中,由于Event Sourcing中围绕领域事件Domain Event建立的机制,与这一模式非常近似,因此记录如下。

Typed Message模式

意图

将信息封装在一个对象中,从而使信息的传递能够以一种类型安全的方式进来。客户可以对对象进行扩展,以便向对象中增添信息,同时无需牺牲类型安全性。

动机

强调对事件的封装扩展,而不再强调通知的过程。

适用场景(需要以下三条全部符合)

  • 一些类的对象可能希望收到来自其他对象的消息。
  • 信息的结构和复杂度是任意的,而且可能会随着软件的逐步发展而发生变化。
  • 信息的传递应该是静态安全的。

结构图

参与者

  • Message:对Sender发给Receiver的信息进行封装。
  • Sender:维护对Receiver对象的引用,实现一个或多个涉及将消息发送给接收者的操作。
  • AbstractReceiver:定义用来接收Message对象的接口。
  • Receiver:实现一个或多个AbstractReceiver接口。

协作

  • Sender创建Message的实例,并将它传递给相应的Receiver。
  • Message是被动的,它不会发起它与Sender或者Receiver之间的通信。

效果

  • 信息的传递能以一种类型安全的方式进行,而且是可扩展的,无需进行向下转型或使用switch语句。
  • 当和Observer模式结合使用时,可以支持隐式调用。
  • 如果编程语言不支持多重(接口)继承,那么将难以运用本模式。
  • 可能会产生非常复杂的继承图。

关于Typed Message与Observer的争论

不得不提的是,由于Typed Message与Observer两种模式的应用场合、注册与通知的方式等存在诸多相似,因此即便是在GoF的四人组与作者之间,围绕Typed Message模式能否单独作为一种模式存在、它是否只是Observer的一种变体而引发的争论,也持续了相当长的时间,在该书第4章中详细描述了这一过程。这段争论当中,有一句话,我认为是点到了关键:

我认为它不是Observer的变体,原因在于Observer知道它的Subject,而Multicast中的处理程序并不需要知道它们的事件源。

正是由于这个原因,所以在Typed Message模式里,Receiver并不清楚Sender的具体情况。Receiver只关心Sender发出的Message,并且在Receive方法里完成实际的事件处理操作。

在Event Sourcing中的映射

相似点

细思之下,Event Sourcing中Event、Aggregate、EventHandler之间的关系,与Typed Message模式中的Message、Sender、Receiver何其相似:

  • Event从Aggregate发出,由EventHandler接收。
  • Event本身只是通信内容的载体。
  • 随着软件功能的发展,Event的家族会不断丰富。
  • Aggregate负责创建Event的实例,并将它传递给EventHandler。
  • EventHandler并不关心发出Event的那个Aggregate究竟什么样,只关心Event承载的信息,并借此完成自己的职责。

所以,对上图作简单修改,即可得到下图:

代码实现

借用《Exploring CQRS and Event Sourcing》中Conference案例的代码作为参考:

Event

Event的实现,是简单的、带版本Version的、若干Property组成的类。

public class OrderPlaced : VersionedEvent

{

    public Guid ConferenceId { get; set; }

    public IEnumerable<SeatQuantity> Seats { get; set; }

    public DateTime ReservationAutoExpiration { get; set; }

    public string AccessCode { get; set; }

}

Aggregate

在聚合Order里,在Order的构造子中触发一个OrderPlaced事件。

public class Order : EventSourced

{

    ... ...

    public Order(Guid id, Guid conferenceId, IEnumerable<OrderItem> items, IPricingService pricingService)

        : this(id)

    {

   ... ...

        this.Update(new OrderPlaced

        {

            ConferenceId = conferenceId,

            Seats = all,

            ReservationAutoExpiration = DateTime.UtcNow.Add(ReservationAutoExpiration),

            AccessCode = HandleGenerator.Generate(6)

        });        

        ... ...

    }    

    ... ...

}

IEventHandler

这里定义的接口IEventHandler,对应图中的抽象类EventHandler。(C#支持类的多接口实现,是Typed Message模式应用的关键之一)

public interface IEventHandler<T> : IEventHandler

        where T : IEvent

{

    void Handle(T @event);

}

EventHandler

在Conference案例中,主要有两种场景下实现的EventHandler。

一类是在Command端,从Event Queue中消费Event,或者Process Manager需要的,相对独立的EventHandler。

public class OrderEventHandler :

        IEventHandler<OrderPlaced>,

        IEventHandler<SeatUnassigned>

{

    ... ...

    public void Handle(OrderPlaced @event)

    {

        using (var context = this.contextFactory.Invoke())

        {

            context.Orders.Add(new Order(@event.ConferenceId, @event.SourceId, @event.AccessCode));

            context.SaveChanges();

        }

    }

    ... ...

另一类是在Query端,需要更新读模型时,嵌入视图生成器中的EventHandler。

public class PricedOrderViewModelGenerator :

        IEventHandler<OrderPlaced>,

        IEventHandler<SeatUpdated>

{

    .... ....

    public void Handle(OrderPlaced @event)

    {

        using (var context = this.contextFactory.Invoke())

        {

            var dto = new PricedOrder

            {

                OrderId = @event.SourceId,

                ReservationExpirationDate = @event.ReservationAutoExpiration,

                OrderVersion = @event.Version

            };

            context.Set<PricedOrder>().Add(dto);

            try

            {

                context.SaveChanges();

            }

            catch (DbUpdateException)

            {

                Trace.TraceWarning("Ignoring OrderPlaced message.", dto.OrderId, @event.Version);

            }

        }

    }

    .... ....

}

结语

同样以《设计模式沉思录》作为结束… …

  • 用代码量来衡量开发人员的效率,实现是一项很糟糕的标准。一个良好的设计恰恰相反——它简洁而优雅。
  • 期望开发人员抽出时间来专心思考是不切实际的,但你能够做的是将积累的经验逐渐记录下来。
  • 尽可能多看一些其他的系统。分析一个系统时,试着去辨认你已经知道的模式。

Typed Message模式与Event Sourcing的更多相关文章

  1. CQRS Event Sourcing介绍

    什么是CQRS模式? CQRS是Command and Query Responsibility Segregation的缩写,直译就是命令与查询责任分离的意思. 命令会改变对象的状态,但不返回任何数 ...

  2. Event Sourcing Pattern 事件源模式

    Use an append-only store to record the full series of events that describe actions taken on data in ...

  3. 事件溯源模式(Event Sourcing Pattern)

    此文翻译自msdn,侵删. 原文地址:https://msdn.microsoft.com/en-us/library/dn589792.aspx 本文介绍了一种有利于物化(materialize)领 ...

  4. CQRS与Event Sourcing之浅见

    引言 DDD是近年软件设计的热门.CQRS与Event Sourcing作为实施DDD的一种选择,也逐步进入人们的视野.围绕这两个主题,软件开发的大咖[Martin Fowler].[Greg You ...

  5. CQRS, Task Based UIs, Event Sourcing agh!

    原文地址:CQRS, Task Based UIs, Event Sourcing agh! Many people have been getting confused over what CQRS ...

  6. Event Sourcing

    Event Sourcing - ENode(二) 接上篇文章继续 http://www.cnblogs.com/dopeter/p/4899721.html 分布式系统 前篇谈到了我们为何要使用分布 ...

  7. Event Sourcing落地与意义

    jsoncat:https://github.com/Snailclimb/jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架) 高内聚低耦 ...

  8. Event Sourcing - ENode(三)

    接上一篇 http://www.cnblogs.com/dopeter/p/4903328.html 老板昨天在第二篇介绍中回复代码和文字无法一一对应.为了更好的让老板为大家解惑,把第二篇最后的猜测的 ...

  9. DDD创始人Eric Vans:要实现DDD原始意图,必须CQRS+Event Sourcing架构

    http://www.infoq.com/interviews/Technology-Influences-DDD# 要实现DDD(domain drive  design 领域驱动设计)原始意图,必 ...

随机推荐

  1. HDU 1698 Just a Hook (线段树区间更新)

    题目链接 题意 : 一个有n段长的金属棍,开始都涂上铜,分段涂成别的,金的值是3,银的值是2,铜的值是1,然后问你最后这n段总共的值是多少. 思路 : 线段树的区间更新.可以理解为线段树成段更新的模板 ...

  2. JDBC第三次学习

    这是我的JDBC第三次学习了,在学习的过程中,老是会忘掉一些知识,不记下笔记实在不行啊! 使用JDBC调用存储过程 (1)关于如何使用Navicat(11.1.13) for MySQL如何创建存储过 ...

  3. js截取指定字节长度的字符串

    默认的截取字符串都是根据字符长度或位置截取的,典型的两个方法是substr和substring. 这样导致的问题是截取同样长度的字符串时,多字节字符(汉字等)和单字节字符(半角英文字母.半角数字)占的 ...

  4. 【poj1006-biorhythms】中国剩余定理

    http://poj.org/problem?id=1006 题意:中国剩余定理的裸题. 题目可转化为求最小的x满足以下条件: x%23=a;x%28=b;x%33=c; 关于中国剩余定理可看我昨天的 ...

  5. servlet学习笔记四

    Servlet 主要内容: 1)servlet初始化参数与上下文参数 2)过滤器 3)监听器一.servlet初始化参数与上下文参数 1)servlet初始化参数 把某些变量放在web.xml配置,到 ...

  6. 快速构建自己的CentOS发行版

    一.制作LTOS具体过程 光盘结构介绍 * isolinux 目录存放光盘启动时的安装界面信息 * images 目录包括了必要的启动映像文件 * CentOS 目录存放安装软件包及信息 * .dis ...

  7. GDB调试方法精粹

    http://blog.chinaunix.net/uid-26922071-id-3756018.html 一.多线程调试 1. 多线程调试,最重要的几个命令: info threads       ...

  8. How to say "no"?

    How to say "no"?7招教你如何拒绝别人      Do you have a hard time saying no to others? Do you say “y ...

  9. MongoDB使用SSL

    1. MongoDB对SSL的支持情况 MongoDB社区版本不支持SSL,企业版提供对SSL的支持.MongoDB源代码中包含SSL的实现,可以自己编译带SSL的MongoDB. MongoDB支持 ...

  10. VCL设计方法概论(自己总结了9条),以及10个值得研究的控件 good

    VCL设计方法概论 1. 把Delphi对象改造成一个Windows窗口,主要是要设置Handle和回调函数.在创建一个Windows窗口后,将其句柄赋值给Delphi对象的属性,这个并不难,相当于从 ...