【问题标题】:Publish-Subscriber using SimpleInjector IoC使用 Simple Injector IoC 发布-订阅
【发布时间】:2017-05-21 06:01:43
【问题描述】:

我一直在考虑在我的网络应用中实现事件模型。

我需要实现的方法是:

  1. 从数据库中获取数据;
  2. 将其传递给 Grab 方法并收集数据;
  3. 将结果转换为适合我的数据并构建电子邮件、日志消息列表;
  4. 如果需要,通过电子邮件服务通知用户; (之后我们需要更改通知用户的 DB 中的 bool 标志)
  5. 将所有抓取日志写入某个存储;
  6. 将抓取的数据添加到 DB。

根据this 的回答,我决定使用上述示例。但是我已经积累了如何连接事件和传递我需要处理的数据。我有一个班级想从互联网上获取一些信息:

public interface IScheduleService
{
    void ParseWebData(int userId);
}

public class ScheduleService : IScheduleService
{
    IQueryHandler<ProductGrabbedInfoByUserQuery, IEnumerable<ProductGrabbedInfo>> GrabberQuery;
    IEventPublisher publisher;

    List<string> logMessages = new List<string>();
    List<string> emailMessages = new List<string>();


    public ScheduleService(IQueryHandler<ProductGrabbedInfoByUserQuery, IEnumerable<ProductGrabbedInfo>> GrabberQuery,
                           IEventPublisher publisher)
    {
        this.GrabberQuery = GrabberQuery;
        this.publisher = publisher;
    }

    public void ParseWebData(int userId)
    {
        // получаем данные из БД
        var qry = new ProductGrabbedInfoByUserQuery { UserId = 1 }; // пока что один юзер
        var data = GrabberQuery.Handle(qry).ToList();
        if (data.Any())
        {
            // собираем информацию
            GrabberEngine ge = new GrabberEngine(BrowserType.Mozilla);
            ge.Grab(data);
            // преобразовываем в наш вид с собиранием логирования и ошибок
            var converted = ConvertFromGrabbedInfo(data);
            // HERE I THINK WE SHOULD CALL publisher.Publish(new GrabbingCompletedEvent{...}) to execute 3 points below:
            // 1. Send email message using List<string> emailMessages info
            // 2. Log messages using List<string> logMessages info
            // 3. add collected data to DB
        }
    }
}

如果我们需要让事件数据最小化,那么我们需要存储一些小的值:

public class GrabbingCompletedEvent : IDomainEvent
{
    public readonly string JobId;
    public readonly int UserId;

    public GrabbingCompletedEvent(string JobId, int UserId)
    {
        this.JobId = JobId;
        this.UserId = UserId;
    }
}

好的,那么我认为我们需要创建事件处理程序:

public class NotifyEmailGrabbingCompletedEventHandler : IEventHandler<GrabbingCompletedEvent>
{
    private IEmailService emailService;
    private readonly ILogger logger;
    private IEnumerable<string> messages;
    private IQueryHandler<GetByPrimaryKeyQuery<User>, User> UserQuery;

    public NotifyEmailGrabbingCompletedEventHandler(IEmailService emailService, 
        ILogger logger, 
        IEnumerable<string> messages, 
        IQueryHandler<GetByPrimaryKeyQuery<User>, User> UserQuery)
    {
        this.emailService = emailService;
        this.logger = logger;
        this.messages = messages;
        this.UserQuery = UserQuery;
    }

    public void Handle(GrabbingCompletedEvent e)
    {
        var user = GetUserInfo(e.UserId);
        emailService.Send(user.Email, string.Join(Environment.NewLine, messages));
        emailService.SetDelivered(true);
    }

    private User GetUserInfo(int userId)
    {
        var cmd = new GetByPrimaryKeyQuery<User> { PK = userId };
        return UserQuery.Handle(cmd);
    }
}

public class LogGrabbingCompletedEventHandler : IEventHandler<GrabbingCompletedEvent>
{
    private readonly ILogger logger;
    private IEnumerable<string> messages;

    public LogGrabbingCompletedEventHandler(ILogger logger, IEnumerable<string> messages)
    {
        this.logger = logger;
        this.messages = messages;
    }

    public void Handle(GrabbingCompletedEvent e)
    {
        foreach (var message in messages)
        {
            this.logger.Log(message);
        }
    }
}

我的问题是如何将记录器实例、消息列表和查询处理程序传递给当前处理程序?我在事件处理程序构造函数中传递这些参数是否正确?我想到的第一个变体是将这些数据作为属性添加到事件类中,但这似乎是一个错误的决定。能否请您解释一下哪种设计方法更适合这种情况。

【问题讨论】:

    标签: c# design-patterns simple-injector


    【解决方案1】:

    如果您使用像 SimpleInjector 这样的 IoC 容器,则事件处理程序类应该由容器解析,而它的所有依赖项都应该从容器中解析。这使事件处理程序与引发事件的类分离。事件类只需要包含描述事件的数据,不需要任何其他依赖项。

    所以是的,事件处理程序的构造函数将是放置其依赖项的正确位置,就像引发事件的类一样。

    我使用注入到类中的IEventBus 接口 - 与您的IEventPublisher 几乎相同。该实现解析了在容器中注册的IEventHandler&lt;T&gt; 的实现。这样一来,一切都可以从容器中解决。

    Here's an implementation I use。事件总线本身与容器无关。如果您使用 DI,则需要 IHandlerProvider 实现来使用容器解析处理程序。我的实现使用温莎。但设计是您可以使用不同的容器来解析处理程序。您只需要编写 IHandlerProvider 的不同实现。

    【讨论】:

    • 但是如何将我的List&lt;string&gt; logMessagesList&lt;string&gt; emailMessages 传递给事件处理程序。然后我需要将它们作为属性保存在 GrabbingCompletedEvent 中吗?事件数据似乎不是开销和冗余吗?否则我不知道如何将数据传递给事件处理程序......
    • 如果它是事件处理程序应该响应的数据,那么是的,你应该传递它。但是您可能想要添加 .AsReadOnly() 以便传递只读集合。否则,其中一个事件处理程序可能会修改集合。
    • 好的,谢谢,还没考虑。还有一个问题,斯科特。我是否需要创建第三个事件处理程序来将抓取的数据插入数据库,或者我的调度服务可以对此负责?在那个变体中,我只是将发布者方法包装在异步任务中,然后插入。
    • 我并没有真正详细地遵循它的逻辑。通常,我将域事件用于类所做的“副作用”。如果您的类的主要行为是确保将数据写入某处,那么您可能希望它依赖于执行该操作的另一个类。如果这是副作用,那么您可以只引发一个事件并让另一个处理程序来执行此操作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多