Eventos de domínio de incêndio após a conclusão da transação

5

Estou tentando implementar um sistema de eventos de domínio que só dispara seus eventos quando a unidade de trabalho associada é confirmada com êxito.

A principal razão pela qual eu quero fazer isso é porque eu tenho outros subsistemas que lêem o banco de dados que espera que essas mudanças estejam em vigor ao lidar com o evento.

Eu também não quero que os manipuladores de eventos façam seu trabalho (enviem emails, etc.) se a confirmação falhar.

O sistema é múltiplo de aplicativos ASP.NET (WebForms, MVC e WebAPI).

public class Order
{
    private Payment _payment;

    public int ID { get; private set; }

    public decimal Amount { get; private set; }

    public void PayUsing(IPaymentProcessor processor)
    {
        if (_payment != null)
        {
            throw new InvalidOperationException(
                "You can't pay for an order twice");
        }

        // the Process method may also raise domain events
        _payment = processor.Process(Amount);

        // Raise saves the event into a static Queue<T>
        DomainEvents.Raise(new OrderPaidEvent(this));
    }
}

public class OrderFulfillmentService
{
    private readonly IUnitOfWorkFactory _unitOfWorkFactory;
    private readonly IPaymentProcessor _paymentProcessor;

    public OrderFulfillmentService(
        IUnitOfWorkFactory unitOfWorkFactory, 
        IPaymentProcessor paymentProcessor)
    {
        _unitOfWorkFactory = unitOfWorkFactory;
        _paymentProcessor = paymentProcessor;
    }

    public void Fulfill(int orderId)
    {
        using (var unitOfWork = _unitOfWorkFactory.Create())
        {
            var order = unitOfWork.OrdersRepository.GetById(orderId);

            order.PayUsing(_paymentProcessor);

            unitOfWork.Commit();

            // I only want the events to be raised if the Commit succeeds               
        }
    }
}

Com a implementação atual, é problemático porque agora o aumento de domínio deve colocá-lo em uma fila, pois não deve disparar imediatamente. A fila é estática, o que causa problemas para sistemas multiusuários. Eu não quero disparar acidentalmente eventos para outros usuários, o que é um problema definido, já que todos os usuários acessam a mesma fila.

Esta é a lista de soluções que investiguei e por que elas não funcionam no meu cenário

  1. Tenha uma classe DomainRaiser não estática que seja passada para o método PayUsing .

    • Passar um DomainRaiser é muito difícil de fazer a partir dos objetos do domínio, às vezes, os eventos podem ser gerados alterando as propriedades e a passagem desse objeto é incômoda.
  2. Todas as entidades contêm uma Events enumeração que é lida pela chamada UnitOfWork.Commit , conforme discutido em link

    • O rastreamento de todas as entidades que possivelmente mudaram é muito difícil. Estou fazendo o download desse rastreamento de alterações para o ORM. Isso também sobrecarrega os objetos do domínio.
  3. Usando HttpContext.Current.Items para armazenar eventos específicos do usuário

    • Esta é atualmente a melhor sugestão, no entanto, não é possível testar a unidade e bloqueia meu domínio para usar o asp.net, que eu tenho planos no futuro de lançar um aplicativo para desktop.

A minha pergunta é: como faço para enfileirar e despachar esses eventos em um ambiente multiusuário de forma confiável, levando em consideração que eu só quero disparar eventos se a unidade de trabalho geral for bem-sucedida?

    
por Matthew 03.09.2014 / 22:54
fonte

1 resposta

2

Primeiro, eu asseguro que cada entidade tenha referência a DomainRaiser . Seria melhor se isso fosse definido quando a entidade fosse criada ou materializada no repositório. Cada contexto de usuário / solicitação teria sua própria instância, que seria então injetada em todas as entidades, que são trabalhadas pelo contexto. Eu não sei qual implementação do Repositório / UnitOfWork você está usando, então pode ser impossível fazer isso. Mas acho que pode ser automatizado se você puder dar às suas entidades uma interface que possa ser chamada durante a construção.

Isso também resolveria seu segundo problema de apenas executar os eventos se UnitOfWork for bem-sucedido. Se DomainRaiser e UnitOfWork tiverem instância única em um contexto, eles poderão interagir entre si, o que tornaria essa chamada de método simples.

A última coisa que me vem à mente é que você poderia separar alguns eventos em duas partes: prepare e execute . Prepare seria chamado no mesmo UnitOfWork quando o evento foi gerado. Isso permitiria ler dados relevantes para o evento, sem aguardar confirmação. A execução seria então chamada para fora com um sinalizador se UnitOfWork fosse confirmada com sucesso e usasse os dados preparados.

    
por 04.09.2014 / 08:47
fonte