【问题标题】:Implementing Domain Event Handler pattern in C# with Simple Injector使用简单注入器在 C# 中实现域事件处理程序模式
【发布时间】:2015-06-03 16:10:22
【问题描述】:

我正在尝试使用 Simple Injector 在 C# 中实现域事件模式。

我已将我的代码简化为一个可以作为控制台应用程序运行的文件,并排除了 Simple Injector 代码以保持问题清晰。

我遇到的问题是每个事件可能有多个事件处理程序,并且可能引发多个事件,但我想限制我的 Dispatcher 只处理实现 IEvent 接口的事件,所以我把这个限制放在我的调度方式。

这导致了如何从 Simple Injector 获取实例的问题,因为每次调用 Dispatch 方法时,TEvent 的类型为 IEvent(正如我所料),但我需要获取传入的事件,因此我可以从 Simple Injector 获取相关的处理程序。

希望我的代码能更好地解释这一点:

interface IEvent 
{
}

interface IEventHandler<T> where T : IEvent
{
    void Handle(T @event);
}

class StandardEvent : IEvent
{
}

class AnotherEvent : IEvent
{
}

class StandardEventHandler : IEventHandler<StandardEvent>
{
    public void Handle(StandardEvent @event)
    {
        Console.WriteLine("StandardEvent handled");
    }
}

class AnotherEventHandler : IEventHandler<AnotherEvent>
{
    public void Handle(AnotherEvent @event)
    {
        Console.WriteLine("AnotherEvent handled");
    }
}

这是我的调度员:

static class Dispatcher
{
    // I need to get the type of @event here so I can get the registered instance from the
    // IoC container (SimpleInjector), however TEvent is of type IEvent (as expected). 
    // What I need to do here is Get the registered instance from Simple Injector for each
    // Event Type i.e. Container.GetAllInstances<IEventHandler<StandardEvent>>()
    // and Container.GetAllInstances<IEventHandler<AnotherEvent>>()
    public static void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent
    {
    }
}

class PlainOldObject
{
    public ICollection<IEvent> Events = new List<IEvent>
    {
        new StandardEvent(),
        new AnotherEvent()
    };
}

class StandAlone
{
    static void Main(string[] args)
    {
        var poco = new PlainOldObject();
        foreach (var @event in poco.Events)
        {
            Dispatcher.Dispatch(@event);
        }
    }
}

我已经在 Dispatch 方法中评论了我的问题。有人知道我应该如何解决这个问题吗?

问候, 加里

【问题讨论】:

    标签: c# generics inversion-of-control simple-injector


    【解决方案1】:

    要使用 SimpleInjector 并动态注入域事件,您可以执行以下操作:

    在 SI 注册中

    _container.Register(typeof(IDomainEventHandler<>), new[] { typeof(IDomainEventHandler<>).Assembly});
    

    然后创建一个事件

     public class PolicyAddressChangedEvent : IDomainEvent
        {
            public Address NewAddress { get;  }
            public Address OriginalAddress { get;  }
    
            public PolicyAddressChangedEvent(Address oldBillingAddress, Address newbillingAddress)
            {
                OriginalAddress = oldBillingAddress;
                NewAddress = newbillingAddress;
            }
        }
    

    然后为事件创建一个处理程序

    public class PolicyAddressChangeHandler : IDomainEventHandler<PolicyAddressChangedEvent>
        {
            private readonly ILoggingService _loggingService;
    
            public PolicyAddressChangeHandler(ILoggingService loggingService)
            {
                _loggingService = loggingService;
            }
    
            public void Handle(PolicyAddressChangedEvent domainEvent)
            {
                _loggingService.Info("New policy address recorded", new Dictionary<string, object> { { "new address", domainEvent.NewAddress } }, "FrameworkSample");
                //this could be event hub, queues, or signalR messages, updating a data warehouse, sending emails, or even updating other domain contexts
            }
        }
    

    现在,当您使用简单的注入器创建 IDomainEventDistpatcher 时,要注入正确的注入器,您可以使用工厂注入器。这是获取所有类型并能够动态查找它们的关键。通过这样做,我们将 Func 注入到 DomainEventDispatcher 中。

     _container.RegisterSingleton<IDomainEventDispatcher>(() =>
                                                            {
                                                                return new DomainEventDispatcher(type => _container.GetInstance(type));
                                                            });
    

    现在我们在 DomainEventDispatcher 中

    public class DomainEventDispatcher : IDomainEventDispatcher
        {
            private readonly Func<Type, object> _handlerLookup;
    
            public DomainEventDispatcher(Func<Type, object> handlerLookup)
            {
                _handlerLookup = handlerLookup;
            }
    
            public void Dispatch(IDomainEvent domainEvent)
            {
                Type handlerType = typeof(IDomainEventHandler<>).MakeGenericType(domainEvent.GetType());
                var handler = GetHandler(handlerType);
                if (handler != null)
                {
                    handler.Handle((dynamic)domainEvent);
                }
            }
    
            private dynamic GetHandler(Type filterType)
            {
                try
                {
                    object handler = _handlerLookup.Invoke(filterType);
                    return handler;
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }
    

    这现在接受 IDomainEvent 并创建正确的类型并根据提供的 Func 进行查找。

    这更好,因为现在我们不强制依赖类来了解我们正在使用的 DI 实现。与上面 Steven 的 anwser 非常相似(有一些小 tweeks),只是认为也可以提供一个完整的示例。

    【讨论】:

      【解决方案2】:

      您需要的解决方案有点取决于Dispatcher 的使用者如何调用事件。如果使用者在编译时总是知道事件的确切类型,您可以使用上面显示的通用Dispatch&lt;TEvent&gt;(TEvent) 方法。在这种情况下,Dispatcher 的实现将非常简单。

      另一方面,如果消费者可能并不总是知道确切的类型,而只是使用IEvent 接口,Dispatch&lt;TEvent&gt;(TEvent) 中的泛型类型参数将变得无用,您最好定义一个Dispatch(IEvent)方法。这使得实现更加复杂,因为您需要使用反射来解决这个问题。

      还请注意,最好引入IEventDispatcher 抽象。不要从代码中调用静态类。即使是 Udi Dahan(很久以前最初是 described such static class)现在也认为这是一种反模式。相反,将IEventDispatcher 抽象注入到需要事件分派的类中。

      如果所有消费者都使用在编译时已知的事件类型,您的实现将如下所示:

      public interface IEventDispatcher
      {
          void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent;
      }
      
      private sealed class Dispatcher : IEventDispatcher
      {
          private readonly Container container;
          public Dispatcher(Container container) {
              this.container = container;
          }
      
          public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent {
              if (@event == null) throw new ArgumentNullException("event");
      
              var handlers = this.container.GetAllInstances<IEventHandler<TEvent>>();
      
              foreach (var handler in handlers) {
                  handler.Handle(@event);
              }
          }
      }
      

      另一方面,如果事件类型未知,则可以使用以下代码:

      public interface IEventDispatcher
      {
          void Dispatch(IEvent @event);
      }
      
      private sealed class Dispatcher : IEventDispatcher
      {
          private readonly Container container;
          public Dispatcher(Container container) {
              this.container = container;
          }
      
          public void Dispatch(IEvent @event) {
              if (@event == null) throw new ArgumentNullException("event");
      
              Type handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());
      
              var handlers = this.container.GetAllInstances(handlerType);
      
              foreach (dynamic handler in handlers) {
                  handler.Handle((dynamic)@event);
              }
          }
      }
      

      请注意,与使用 .NET 反射 API 相比,使用 dynamic 关键字有一些不明显的优势。例如,当使用动态调用处理程序的Handle 方法时,从句柄抛出的任何异常都会直接冒泡。另一方面,当使用MethodInfo.Invoke 时,异常将被包装在一个新异常中。这使得捕获和调试变得更加困难。

      您的事件处理程序可以按如下方式注册:

      container.RegisterCollection(typeof(IEventHandler<>), listOfAssembliesToSearch);
      

      【讨论】:

      • 嗨史蒂文,感谢您的回复。事件类型是未知的,所以第二个选项是我需要的。我在获取处理程序时遇到问题,GetAllInstances 不返回任何内容。我尝试使用 Register 和 RegisterManyForOpenGeneric 方法注册我的事件,但仍然无法将处理程序从容器中取出。我可以调试代码并查看容器是否具有我所期望的注册。我认为这可能是我做错了。
      • 那是因为您没有使用RegisterAll 注册您的处理程序。见the documentation。基本上,您需要做的是:container.RegisterManyForOpenGeneric(typeof(IEventHandler&lt;&gt;), container.RegisterAll, [ your assemblies ]);。 Simple Injector 区分registrations of collections and one-to-one mappings
      • 非常感谢史蒂文,我已经忘记了。感谢您的帮助,非常感谢您解决了我的问题。
      • 非泛型调度有效。但是通用的 Dispatch() 不起作用。调用 GetAllInstances>() 时,不返回处理程序。
      • 如果容器是实例引用,你怎么可能在静态方法上引用容器?
      【解决方案3】:

      这是一个使用缓存委托的更快版本。第一次通话后没有动态,也没有反射。使用默认 Microsoft DI 中的 IServiceProvider,但可以轻松更改。也可以使用表达式树,但它们会占用更多内存:

      public class EventDispatcherService : IEventDispatcher
      {
          private static readonly ConcurrentDictionary<Type, IEnumerable<Func<object, Task>>> HandlersCache
              = new ConcurrentDictionary<Type, IEnumerable<Func<object, Task>>>();
      
          private static readonly Type HandlerType = typeof(IEventHandler<>);
      
          private static readonly MethodInfo MakeDelegateMethod = typeof(EventDispatcherService)
              .GetMethod(nameof(MakeDelegate), BindingFlags.Static | BindingFlags.NonPublic);
      
          private static readonly Type OpenGenericFuncType = typeof(Func<,>);
      
          private static readonly Type TaskType = typeof(Task);
      
          private readonly IServiceProvider serviceProvider;
      
          public EventDispatcherService(IServiceProvider serviceProvider)
              => this.serviceProvider = serviceProvider;
      
          public async Task Dispatch(IDomainEvent domainEvent)
          {
              var eventHandlers = HandlersCache.GetOrAdd(domainEvent.GetType(), eventType =>
              {
                  var eventHandlerType = HandlerType.MakeGenericType(eventType);
      
                  var makeDelegate = MakeDelegateMethod.MakeGenericMethod(eventType);
      
                  var funcType = OpenGenericFuncType.MakeGenericType(eventType, TaskType);
      
                  return this.serviceProvider
                      .GetServices(eventHandlerType)
                      .Select(handler => handler
                          .GetType()
                          .GetMethod("Handle")
                          .CreateDelegate(funcType, handler))
                      .Select(handlerDelegateConcrete => (Func<object, Task>)makeDelegate
                          .Invoke(null, new object[] { handlerDelegateConcrete }))
                      .ToList();
              });
      
              foreach (var eventHandler in eventHandlers)
              {
                  await eventHandler(domainEvent);
              }
          }
      
          private static Func<object, Task> MakeDelegate<T>(Func<T, Task> action)
              => value => action((T)value);
      }
      

      这是事件处理程序接口:

      public interface IEventHandler<in TEvent>
          where TEvent : IDomainEvent
      {
          Task Handle(TEvent domainEvent);
      }
      

      【讨论】:

        猜你喜欢
        • 2015-11-03
        • 1970-01-01
        • 2020-02-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多