In the first part we ported the retail demo using Msmq to RabbitMq, with both the Direct and Conventional Routing Topologies. All source code is on Github.
Now we are going to add a CancelOrder command and OrderCancelled event, as follows:
Notice that the Billing endpoint does not send a BillCancelled event. This is because shipping doesn't really need wait for the bill to be cancelled. The shipment should be cancelled or recalled as soon as possible.
Let's make the code changes.
First we create the CancelOrder command and OrderCancelled event in the Messages project.
public class CancelOrder : ICommand
{
public string OrderId { get; set; }
}
public class OrderCancelled : IEvent
{
public string OrderId { get; set; }
}
In Rabbit.ClientUI Program.cs add the following line in the AsyncMain:
routing.RouteToEndpoint(typeof(CancelOrder), "Rabbit.Sales");
In the RunLoop method add a new case for ConsoleKey.C
case ConsoleKey.C:
// Instantiate the command
var cancelCommand = new CancelOrder
{
OrderId = Guid.NewGuid().ToString()
};
// Send the command to the local endpoint
log.Info($"Sending CancelOrder command, OrderId = {cancelCommand.OrderId}");
await endpointInstance.Send(cancelCommand).ConfigureAwait(false);
break;
Now go to the Rabbit.Sales project. The Program.cs needs no change at all. Just add a message handler for the new CancelOrder command.
public class CancelOrderHandler : IHandleMessages<CancelOrder>
{
static ILog logger = LogManager.GetLogger<CancelOrderHandler>();
public Task Handle(CancelOrder message, IMessageHandlerContext context)
{
logger.Info($"Received CancelOrder, OrderId = {message.OrderId}");
// This is normally where some business logic would occur
var orderCancelled = new OrderCancelled
{
OrderId = message.OrderId
};
return context.Publish(orderCancelled);
}
}
Now go to the Rabbit.Billing project. Again no change to the Program.cs is required. Just add a message handler for the OrderCancelled event.
public class OrderCancelledHandler : IHandleMessages<OrderCancelled>
{
static ILog logger = LogManager.GetLogger<OrderCancelledHandler>();
public Task Handle(OrderCancelled message, IMessageHandlerContext context)
{
logger.Info($"Received OrderCancelled, OrderId = {message.OrderId} - reembursing credit card");
return Task.CompletedTask;
}
}
Finally go to the Shipping project and add the OrderCancelledHandler there.
public class OrderCancelledHandler : IHandleMessages<OrderCancelled>
{
static ILog logger = LogManager.GetLogger<OrderCancelledHandler>();
public Task Handle(OrderCancelled message, IMessageHandlerContext context)
{
logger.Info($"Received OrderCancelled, OrderId = {message.OrderId} - shipment cancelled");
return Task.CompletedTask;
}
}
That is all the code changes done. So what new exchanges and queues will NServiceBus create? It depends on the routing topology we are using.
Conventional Routing Topology
Just one new exchange Messages.Events:OrderCancelled that binds to the same exchanges as Messages.Events:OrderPlaced. The Rabbit.Sales queue now receives both the PlaceOrder and the CancelOrder commands. Both are processed by the same Rabbit.Sales endpoint, but by different message handlers.
The Rabbit.Billing and Rabbit.Shipping exchanges and queues now receive both the OrderPlaced and OrderCancelled events. Each endpoint processes both event types, but uses a different message handler for each.
So we see how one endpoint can receive multiple message types over the same queue. Internally NServiceBus wires up the message handlers inside the application.
Direct Routing Topology
So no new exchanges or queue, just extra bindings for the new message routing keys.
In the next part we'll look at customising the Direct and Conventional routing topologies a little.