Is there some way to keep domain events context unaware with NServiceBus 6 and the removal of IBus?

393 Views Asked by At

I'm wrestling with a situation where we currently use the IBus interface (NServiceBus v5) in domain event handlers to send commands to a backend service for processing. With the IBus, these commands could be sent regardless of what triggered the event, whether while receiving a Web API request or as part of an NServiceBus handler (common domain model). But, in NServiceBus v6, with the shift to context specific interfaces, IEndpointInstance or IMessageHandlerContext, it seems that my domain event handlers now need to become context aware. And further, it looks like the IMessageHandlerContext is only available via method injection, so I may have to sprinkle this parameter all throughout the call stack?

Is there some approach that I'm not seeing whereby I can keep my domain event handlers context unaware? Or have I followed some bad practice that's revealing itself through this code smell?

EDIT

Here's an attempt at boiling down the scenario to the most relevant pieces. There's an order in the domain model whose status may change. When the status of the order changes, we've been firing off a StatusChanged domain event through a publisher. A subscriber to this particular domain event writes out a record of the status change and also sends out an NServiceBus command to communicate this status out - the handler for this particular command will follow some further logic on whether to send out emails, SMS messages, etc., the details of which I don't think are relevant.

Order Domain Object

public class Order
{
    private OrderStatusCode _statusCode;

    public OrderStatusCode StatusCode
    {
        get { return _statusCode; }
        private set { _statusCode = value; }
    }

    public void ChangeStatus(OrderStatusCode status)
    {
        Status = status;

        Publish(new StatusChanged(CreateOrderSnapshot(), status));
    }

    protected void Publish<T>(T @event) where T : IDomainEvent
    {
        DomainEventPublisher.Instance.Publish(@event);
    }
}

Domain Event Publisher

public class DomainEventPublisher : IDomainEventPublisher
{
    private static IDomainEventPublisher _instance;
    public static IDomainEventPublisher Instance
    {
        get { return _instance ?? (_instance = new DomainEventPublisher()); }
    }

    public ISubscriptionService SubscriptionService { get; set; }

    public void Publish<T>(T @event) where T : IDomainEvent
    {
        if (SubscriptionService == null) return;

        var subscriptions = SubscriptionService.GetSubscriptions<T>();
        subscriptions.ToList().ForEach(x => PublishToConsumer(x, @event).GetAwaiter().GetResult());
    }

    private static async Task PublishToConsumer<T>(IEventSubscriber<T> x, T eventMessage) where T : IDomainEvent
    {
        await x.HandleEvent(eventMessage);
    }
}

Status Changed Domain Event Handler

public class StatusChangedHandler : IEventSubscriber<StatusChanged>
{
    private readonly IBus _bus;
    private readonly IOrdersRepository _ordersRepository;

    public StatusChangedHandler(IBus bus, IOrdersRepository ordersRepository)
    {
        _bus = bus;
        _ordersRepository = ordersRepository;
    }

    public async Task HandleEvent(StatusChanged @event)
    {
        var statusTrailEntry = new OrderStatusTrailEntry(@event.OrderSnapshot, @event.Status);

        var txOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
        using (
            var scope = new TransactionScope(TransactionScopeOption.Required, txOptions))
        {
            await _ordersRepository.SaveStatusTrail(statusTrailEntry);

            if (communicateStatus)
            {
                _bus.Send(new SendCommunicationCommand(@event.OrderSnapshot, @event.Status));
            }
            scope.Complete();
        }
    }
}

The things is, up until now none of the sample code above has needed to know whether the status changed as a result of a request coming in through a Web API request or as a result of a status being changed within the context of an NServiceBus message handler (within a windows service) - the IBus interface is not context specific. But with the differentiation between IEndpointInstance and IMessageHandlerContext in NServiceBus v6, I don't feel that I have the same flexibility.

If I understand correctly, I'm able to register the IEndpointInstance with my container and inject into the EventSubscriber, so I'd be covered in the case of a Web API call, but I'd also need to add an IMessageHandlerContext as a parameter to optionally be passed down through the call stack from ChangeStatus to the Publisher and finally to the Domain Event Subscriber if the status happens to be changed within the context of a message handler. Really doesn't feel right to be adding this parameter all throughout the call stack.

0

There are 0 best solutions below