Domanda

Let's say we have the following business rules:

  1. When an order is cancelled, all unshipped shipments should be cancelled.
  2. Individual shipments can be cancelled for other reasons.

If we're doing this stuff synchronously, I have questions about transaction scope:

  1. I've heard it said that Aggregate == "transactional boundary", but wouldn't it be more beneficial in this case to request-scope DbContext so that all synchronous handlers execute in the same transaction? It would give more consistency in case something failed in the latter 2 handlers.
  2. If #1 is not a good idea, why? Do we just say "each handler in its own transaction and if something goes wrong in the middle of it, you're on your own"?
  3. If #1 is a good idea, how do I do this with NServiceBus' Bus.InMemory? The documentation is not very clear on this matter.

Code follows:

public class CancelOrder : ICommand 
{
    public Guid Id { get; set; }
}

public class OrderCancelled : IEvent
{
    public Guid Id { get; set; }
}

public class CancelShipment : ICommand
{
    public Guid Id { get; set; }
}

public class CancelOrderHandler : IHandleMessages<CancelOrder>
{
    private DbContext _dbContext;
    private IBus _bus;

    public void Handle(CancelOrder message) 
    {
        var order = _dbContext.Orders.Single(x => x.Id == message.Id);

        // do some validation here

        order.Status = OrderStatus.Cancelled;

        // unsure about whether to call _dbContext.SaveChanges() or not

        _bus.Publish(new OrderCancelled {
            Id = message.Id
        });
    }
}

public class OrderCancelledHandler : IHandleMessages<OrderCancelled>
{
    private DbContext _dbContext;
    private IBus _bus;

    public void Handle(CancelOrder message) 
    {
        var commands = _dbContext.Shipments
            .Where(x => x.OrderId == message.Id && x.Status != ShipmentStatus.Shipped)
            .Select(x => new CancelShipment {
                Id = x.Id
            });

        _bus.Send(commands);
    }
}

public CancelShipmentHandler : IHandleMessages<CancelShipment>
{
    private DbContext _dbContext;

    public void Handle(CancelShipmentHandler message) 
    {
        // Similar to CancelOrder, only with shipments.
    }
}
È stato utile?

Soluzione

The answer here depends on the business requirements - for example, if we are unable to cancel one of the shipments, does that mean that we should roll back the cancellation of the order?

My sense is that the answer is probably no. As such, the message-driven model you've used seems more appropriate than doing everything synchronously in a single handler.

One minor issue I have with your code is that you're publishing commands in your OrderCancelledHandler. You should probably Bus.Send them instead.

Also, by doing a single Bus.Publish/Bus.Send, you are indicating that the processing of the complete set of commands should be one transaction. I don't think you want that. Instead, I'd suggest looping over the commands and doing a Bus.Send for each one separately.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top