9

Real-Time Web by leveraging Event Driven Architecture

 3 years ago
source link: https://codeopinion.com/real-time-web-by-leveraging-event-driven-architecture/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Event Driven Architecture isn’t just for communicating between services or systems. It’s a characteristic of your architecture. You can develop a monolith and still use event driven architecture. Here’s an example of how using events can drive real-time web and make your clients more interactive.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.

Simple Event Processing

The basics of Event Driven Architecture are Events, Producers, and Consumers.

To start with a Producer will create an Event. An event is simply a message that identifies that something has occurred within the system.

Then the producer publishes that Event (message) to a Topic on a Message Broker.

Finally, Consumers subscribe to a Topic. This means that they will receive the Events (messages) that are Published to the Topic from the Producer.

The Producer has no idea who the Consumers are. It has no concept that there even are any Consumers. There may be zero Consumers for an Event or many Consumers. The Producer simply publishes Events to the Message Broker about what has occurred within the system. Consumers consume these events usually by performing some type of task. This is the publish/subscribe pattern of messaging.

For the example of this post, we can leverage this pattern to push events to a browser for real-time web.

Real-Time Web

In this example, I have an ASP.NET Core HTTP API that’s Product and Consumer. Meaning it will both Publish events and consume them as well. Once it consumes an event, it will use SignalR to communicate with Blazor Web Assembly apps. The reason our ASP.NET Core is a Product and Consumer is it allows us to decouple the concerns of using SignalR to communicate with Blazor WebAssembly.

I’m using and modifying the Blazor Pizza Sample App to illustrate this. First, our ASP.NET Core app will create a message when a Pizza Order is going through its state transitions. The transitions are Order is Placed, Preparing, Out for Delivery, and Delivered.

Then it publishes this event to our Message Broker.

Finally, since our ASP.NET Core is also the Consumer, it receives this Event and then uses SignalR to push a message to our connected Blazor WebAssembly Client Apps.

Source Code

Developer-level members of my CodeOpinion YouTube channel get access to the full source for any working demo application that I post on my blog or YouTube. Check out the membership for more info.

I’ve defined 3 events for the state transitions:

public record OrderBeingPreparedEvent(int OrderId); public record OrderPickedUpForDeliveryEvent(int OrderId); public record OrderDeliveredEvent(int OrderId);

Our ASP.NET Core App will Publish these events. I’m using CAP in these examples along with Kafka as a message broker.

Each Controller Action for changing the state of the order will publish a associated event.

[HttpPost("{orderId}/prepare")] public async Task<ActionResult<OrderWithStatus>> Preparing(int orderId, [FromServices] ICapPublisher capPublisher) { var order = await _db.Orders .Where(o => o.OrderId == orderId) .SingleOrDefaultAsync();

if (order == null) { return NotFound(); }

order.OrderStatus = OrderStatus.Preparing; await _db.SaveChangesAsync();

await capPublisher.PublishAsync(nameof(OrderBeingPreparedEvent), new OrderBeingPreparedEvent(orderId));

return NoContent(); }

[HttpPost("{orderId}/outForDelivery")] public async Task<ActionResult<OrderWithStatus>> OutForDelivery(int orderId, [FromServices] ICapPublisher capPublisher) { var order = await _db.Orders .Where(o => o.OrderId == orderId) .SingleOrDefaultAsync();

if (order == null) { return NotFound(); }

order.OrderStatus = OrderStatus.OutForDelivery; await _db.SaveChangesAsync();

await capPublisher.PublishAsync(nameof(OrderPickedUpForDeliveryEvent), new OrderPickedUpForDeliveryEvent(orderId));

return NoContent(); }

[HttpPost("{orderId}/deliver")] public async Task<ActionResult<OrderWithStatus>> Deliver(int orderId, [FromServices] ICapPublisher capPublisher) { var order = await _db.Orders .Where(o => o.OrderId == orderId) .SingleOrDefaultAsync();

if (order == null) { return NotFound(); }

order.OrderStatus = OrderStatus.Delivered; await _db.SaveChangesAsync();

await capPublisher.PublishAsync(nameof(OrderDeliveredEvent), new OrderDeliveredEvent(orderId));

return NoContent(); }

Since our ASP.NET Core app is also the consumer, we are creating a CAP Subscriber that will subscribe and consume all these events, and use SignalR to push down a message to our Blazor WebAssembly Client Apps.

public class CustomerOrderEventNotificationHandler : ICapSubscribe { private readonly IHubContext<CustomerOrderHub> _hubContext;

public CustomerOrderEventNotificationHandler(IHubContext<CustomerOrderHub> hubContext) { _hubContext = hubContext; }

[CapSubscribe(nameof(OrderBeingPreparedEvent), Group = nameof(CustomerOrderEventNotificationHandler) + ":" + nameof(OrderBeingPreparedEvent))] public async Task Handle(OrderBeingPreparedEvent orderBeingPreparedEvent) { await _hubContext.Clients.Group(orderBeingPreparedEvent.OrderId.ToString()).SendAsync("OrderBeingPrepared"); }

[CapSubscribe(nameof(OrderPickedUpForDeliveryEvent), Group = nameof(CustomerOrderEventNotificationHandler) + ":" + nameof(OrderPickedUpForDeliveryEvent))] public async Task Handle(OrderPickedUpForDeliveryEvent orderPickedUpForDeliveryEvent) { await _hubContext.Clients.Group(orderPickedUpForDeliveryEvent.OrderId.ToString()).SendAsync("OrderPickedUpForDelivery"); }

[CapSubscribe(nameof(OrderDeliveredEvent), Group = nameof(CustomerOrderEventNotificationHandler) + ":" + nameof(OrderDeliveredEvent))] public async Task Handle(OrderDeliveredEvent orderDeliveredEvent) { await _hubContext.Clients.Group(orderDeliveredEvent.OrderId.ToString()).SendAsync("OrderDelivered"); } }

Then from our Blazor WebAssembly App, we can subscribe to the SignalR messages and once received, update our Order Details Page.

protected override async Task OnInitializedAsync() { var tokenResult = await _tokenProvider.RequestAccessToken(); tokenResult.TryGetToken(out var token);

_hubConnection = new HubConnectionBuilder() .WithUrl(_navigationManager.ToAbsoluteUri("https://localhost:6001/customer/orderhub"), options => { options.AccessTokenProvider = () => Task.FromResult(token.Value); }) .Build();

_hubConnection.On("OrderBeingPrepared", async () => { await GetOrderDetails(); StateHasChanged(); });

_hubConnection.On("OrderPickedUpForDelivery", async () => { await GetOrderDetails(); StateHasChanged(); });

_hubConnection.On("OrderDelivered", async () => { await GetOrderDetails(); StateHasChanged(); });

await _hubConnection.StartAsync(); await _hubConnection.InvokeAsync("WatchOrder", OrderId); }

Here’s a visual illustration of this working.

signalr.gif

Real-Time Web & Event Driven Architecture

Using events to communicate between microservices or systems isn’t the only reason to leverage event driven architecture. You can also use it within a single application and be both the Producer and Consumer. This allows you to decouple the concerns and be reactive about events occurring within your system. In this example, I’m using events to drive real-time web, but hopefully, you can now see other use-cases.

Related Posts

Follow @CodeOpinion on Twitter

Leave this field empty if you're human:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK