1. 程式人生 > >🇺🇸 Commands + Domain Events + Real time notification — Hands on

🇺🇸 Commands + Domain Events + Real time notification — Hands on

🇺🇸 Commands + Domain Events + Real time notification — Hands on

Following my series about distributed systems, in which I start explaining about principles of service design, later on telling why they should be resilient and the need of domain events, after some feedbacks I want to share with you guys a little bit of hands on about what I’ve been talking lately.

Note.: At the end of the post, there is a link with the final solution.

To this hands on, I’ll show a system composed by:

  • An API that accepts orders containing customer name and list of products.
  • A frontend used only for sending orders to the backend and receiving notifications.
  • A backend project, responsible to process an order and publish domain events.
  • MediatR was used as an abstraction layer for commands and domain events.
  • SignalR used for sending notifications to the frontend.

See below an image with the concepts within this example:

Starting from the API, lets create an ASP.NET Core 2.1 project and add the NuGet package: MediatR.Extensions.Microsoft.DependencyInjection.

It will contain everything that we need to configure MediatR as our message broker.

Note that MediatR is a library that offers abstractions for sending commands and publishing events, but everything runs in memory. In a real scenario, you want to adopt a reliable system that transports these messages, as Azure Service Bys or RabbitMQ. They guarantee that you don’t loose these messages, even if one of your servers is down.

Create a controller called OrderController and add an endpoint as follows below:

[Route("api/[controller]")]public class OrdersController : Controller{   private readonly IMediator _mediator;
   public OrdersController(IMediator mediator)   {      _mediator = mediator;   }
   [HttpPost]   public Task PostAsync([FromBody]PlaceOrderCommand order) =>      _mediator.Send(order);
}

Note that this is a simple controller and there is nothing more than a mediator class being injected and an endpoint sending a command.

A command can never be sent in an invalid state, however, for the matter of the simplicity of this example, I’ll not implement validations. In case you want to look deep into it, see this MediatR wiki for behaviors. With them it is possible to create an execution pipeline that runs before the command is sent and the validation can be called there.

Also the method PostAsync receives as an argument a DTO called PlaceOrderCommand, representing the action of this endpoint. See its definition:

public class PlaceOrderCommand : IRequest{   public string CustomerName { get; set; }   public OrderLine[] OrderLines { get; set; }
   public class OrderLine   {      public string ProductName { get; set; }      public int Quantity { get; set; }   }}

A DTO with customer name and products.

In order to send the command through MediatR, this class needs to implement the interface IRequest.

Until now we have an API that accepts HTTP requests and redirect them as commands to our message broker. Now we need to implement the worker responsible for processing these commands.

Now create a class called PlaceOrderRequestHandler and make it implements the interface IRequestHandler<PlaceOrderCommand>.

public class PlaceOrderRequestHandler :   IRequestHandler<PlaceOrderCommand>{   private readonly IMediator _mediator;   public PlaceOrderRequestHandler(IMediator mediator)   {      _mediator = mediator;   }
   public async Task<Unit> Handle(PlaceOrderCommand request,      CancellationToken cancellationToken)   {      // Manipulate your domain object      // Persist it      // Publish events
      // Wait for the matter of the example      await Task.Delay(2000, cancellationToken);
      await _mediator.Publish(new OrderPlacedEvent { OrderId =         Guid.NewGuid() }, cancellationToken);
      return Unit.Value;   }}

Note that the interface IRequestHandler asks for a generic meaning the type of message that this worker will handle, in our example, handle messages of type PlaceOrderCommand.

What is interesting here is that MediatR takes care of discover this class, instantiate and inject all services that it depends on. This way, our API doesn’t know any detail on who or how the message will be processed.

The Handle method will be executed as soon as MediatR identify that a command was sent. This part, usually is responsible for get the domain object, modify and publish events. Within this method there is a long process going on and at the end, the Order Placed Event is published with the order id.

Placing order is ready, but before running, we need to configure MediatR in our API. To configure it, change the method ConfigureServices within the class Startup.cs and add a line at the end of it:

services.AddMediatR();

Now hit Debug (F5) and see the result using Postman:

Back to the first image on this post, we need to implement the other way around, notifying the client that an order was created.

Lets create now:

  • A websocket endpoint, so that clients can be able to connect and receive notifications from the server.
  • A worker that will listen to OrderPlacedEvent and send messages to the clients.

To do that, I’m going to use ASP.NET COre SignalR. The configuration is simple: Still within the Startup.cs class, add a line at the end of the method ConfigureServices:

services.AddSignalR();

And at the end of the method Configure add the following:

app.UseSignalR(routes =>{      routes.MapHub<OrderingEventsClientHub>("/ordering-events");});

This last, configures the middleware that map the available routes to our websocket. In this example, we are using the route “/ordering-events” to the Hub “OrderingEventsClientHub”

The Hub is an abstraction of a communication channel between clients and backend. Create the class as follows:

public class OrderingEventsClientHub : Hub {}

Note here that it is empty, because it is representing a websocket without any methods. Its purpose is to open the window for communication between systems backend and client.

Now that we have the Hub created, we need to create the responsible for listening our order domain event and send them to the client.

public class OrderingEventsClientDispatcher :   INotificationHandler<OrderPlacedEvent>{   private readonly IHubContext<OrderingEventsClientHub>      _hubContext;
   public OrderingEventsClientDispatcher(      IHubContext<OrderingEventsClientHub> hubContext)   {      _hubContext = hubContext;   }
   public Task Handle(OrderPlacedEvent @event,       CancellationToken cancellationToken)   {      return _hubContext.Clients.All.SendAsync("orderPlaced",         @event, cancellationToken);   }
}

This class implements the interface INotificationHandler, offered by MediatR. When MediatR API recognizes that there is a new event of type OrderPlacedEvent, this class will be used and the method Handle will be executed, similar to the IRequestHandler.

On the constructor, the service IHubContext<T> is being injected. It is provided by SignalR and offers methods of communication.

Within the method Handle, I’m using the context to forward the event to all of our clients. Now clients will be able to listen to these events through the identifier “orderPlaced” on the first argument.

Note: For the matter of this example, I’m sending the event to all clients. In a real scenario, the event would be sent only for those who are interested on it, for instance, only the user that placed the order. SignalR provides a full API that deals with such cases, by using ClaimsPrincipal available on ASP.NET.

Finishing this example, we need to create a client that is able to handle these events. Replace the file “Index.cshtml” with the following:

@[email protected] In[email protected]{   ViewData["Title"] = "Home page";}
<div class="row">   <div class="col-md-12">      <button id="placeOrder" class="btn btn-info">Place Order</button>   </div></div>
<div class="row">   <div class="col-md-12">      <ul id="event-list"></ul>   </div></div>
<script src="~/lib/signalr/dist/browser/signalr.js"></script><script src="~/js/ordering.js"></script>

This page contains a button, that will be used to place orders and a list used to show the events coming from the backend.

There are also 2 javascript files:

  • “~/lib/signalr/dist/browser/signalr.js” — SignalR Client for Javascript. Can be downloaded through NPM.
  • “~/js/ordering.js” — Javascript with the code that connects to the backend.

In order to see the contents of ordering.js click here.

With all of these components, it is possible to run the project and see the result:

Access the repository and see the final solution.