• Typed Message模式与Event Sourcing


    引言

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

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

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

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

    Typed Message模式

    意图

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

    动机

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

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

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

    结构图

    TypedMessage

    参与者

    • 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承载的信息,并借此完成自己的职责。

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

    EventSourcing1

    代码实现

    借用《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);
                }
            }
        }
        .... ....
    }

    结语

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

    • 用代码量来衡量开发人员的效率,实现是一项很糟糕的标准。一个良好的设计恰恰相反——它简洁而优雅。
    • 期望开发人员抽出时间来专心思考是不切实际的,但你能够做的是将积累的经验逐渐记录下来。
    • 尽可能多看一些其他的系统。分析一个系统时,试着去辨认你已经知道的模式。
  • 相关阅读:
    Elasticsearch崩溃解决办法
    阿里云服务器ubuntu14.04安装Redis
    error: command ‘x86_64-linux-gnu-gcc’ failed with exit status 1
    本机访问阿里云服务器上的Elasticsearch
    阿里云服务器配置ElasticSearch
    ElasticSearch-RTF的安装
    【已解决】neo4j-import使用过程中遇到的问题(there's a field starting with a quote and whereas it ends that quote there seems to be characters in that field /Executor has been shut down in panic)
    OutOfMemoryError和StackOverflowError
    线程的终止
    scala基础
  • 原文地址:https://www.cnblogs.com/Abbey/p/4888301.html
Copyright © 2020-2023  润新知