Typed Message模式与Event Sourcing
引言
在《设计模式沉思录》(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的更多相关文章
- CQRS Event Sourcing介绍
什么是CQRS模式? CQRS是Command and Query Responsibility Segregation的缩写,直译就是命令与查询责任分离的意思. 命令会改变对象的状态,但不返回任何数 ...
- Event Sourcing Pattern 事件源模式
Use an append-only store to record the full series of events that describe actions taken on data in ...
- 事件溯源模式(Event Sourcing Pattern)
此文翻译自msdn,侵删. 原文地址:https://msdn.microsoft.com/en-us/library/dn589792.aspx 本文介绍了一种有利于物化(materialize)领 ...
- CQRS与Event Sourcing之浅见
引言 DDD是近年软件设计的热门.CQRS与Event Sourcing作为实施DDD的一种选择,也逐步进入人们的视野.围绕这两个主题,软件开发的大咖[Martin Fowler].[Greg You ...
- CQRS, Task Based UIs, Event Sourcing agh!
原文地址:CQRS, Task Based UIs, Event Sourcing agh! Many people have been getting confused over what CQRS ...
- Event Sourcing
Event Sourcing - ENode(二) 接上篇文章继续 http://www.cnblogs.com/dopeter/p/4899721.html 分布式系统 前篇谈到了我们为何要使用分布 ...
- Event Sourcing落地与意义
jsoncat:https://github.com/Snailclimb/jsoncat (仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架) 高内聚低耦 ...
- Event Sourcing - ENode(三)
接上一篇 http://www.cnblogs.com/dopeter/p/4903328.html 老板昨天在第二篇介绍中回复代码和文字无法一一对应.为了更好的让老板为大家解惑,把第二篇最后的猜测的 ...
- DDD创始人Eric Vans:要实现DDD原始意图,必须CQRS+Event Sourcing架构
http://www.infoq.com/interviews/Technology-Influences-DDD# 要实现DDD(domain drive design 领域驱动设计)原始意图,必 ...
随机推荐
- 【面试题】Google of Greater China Test for New Grads of 2014总结
2014年Google中国校园招聘采用在线技术笔试,在Code Jam平台上,14号9点到15号9点开放测试题,帮助大家熟悉环境.这个周末也有够忙的,当时就上去看了一下,把输入文件下了一下,今天才把题 ...
- POJ 1151 Atlantis(经典的线段树扫描线,求矩阵面积并)
求矩阵的面积并 采用的是区间更新 #include <iostream> #include <stdio.h> #include <string.h> #inclu ...
- HTML5 webSQL
https://www.ibm.com/developerworks/cn/web/1108_zhaifeng_websqldb/ <!DOCTYPE HTML> <html&g ...
- Andoid自动判断输入是电话,网址或者Email的方法----Linkify的应用!
本节要讲的是,当我们在一个EditText输入电话或者网址还是Email的时候,让Android自动判断,当我们输入的是电话,我们点击输入内容将调用打电话程序,当我们输入是网址点击将打开浏览器程序.而 ...
- IDA 使用技巧
我用的IDA Pro 6.5,把我自己使用ida的一些方法记录,免得自己遗忘 1 .导入符号表 可以像前一篇博客中写的那样,也可以使用File--->LoadFile--->PDB Fil ...
- cojs QAQ的图论题 题解报告
话说这个题目应该叫做 斯特林数的逆袭 QAQ 先说一说部分分的算法 1.n<=5 直接暴力搜索就可以了 2.k=0的时候不难发现任意一张图的价值都是n,问题转化为计算有多少种图,显然是2^C(n ...
- iphone 异常捕获处理
iphone 异常捕获处理 1 void UncaughtExceptionHandler(NSException *exception) { 2 NSArray *arr = [exception ...
- -ffunction-sections -Wl,--gc-sections
AVR/GCC设置不链接未调用的函数 http://blog.csdn.net/shevsten/article/details/7049688 在AVR Studio4/5的AVR/GCC默认设置下 ...
- JavaPersistenceWithHibernate第二版笔记-第五章-Mapping value types-005控制类型映射(Nationalized、@LOB、@org.hibernate.annotations.Type)
一.简介 1. 2. 3. 4. to override this default mapping. The JPA specification has a convenient shortcut a ...
- 扩展 delphi 线程 使之传递参数.(给匿名线程增加参数)
新delphi的线程TThread有了CreateAnonymousThread方法,如果再为它加一个可传递的参数不就更好了吗?代码如下: TAnonymousThreadX<T> = c ...