A DotNet Raider

My adventures in the .NET world!
posts - 49, comments - 12, trackbacks - 0

My Links

News

Website View Martino Bordin's profile on LinkedIn

Archives

Post Categories

mercoledì 6 dicembre 2023

Dapr - Pub/sub & Output Binding

In the last article, once created an order we published a message, called OrderSubmittedIntegrationEvent with all the relevant information.

How can we subscribe to this event in other services?

In Dapr there are two ways:

  • declaratively, where subscriptions are defined in an external file
  • programmatically, where subscriptions are defined in our code

We're going to use the second method in the Notifications and Shipping microservices along with the Output binding building block

What is an Output binding? It's a way to invoke external resources just by passing a payload and additional metadata.

Let's see them in action!

No alt text provided for this image

Notifications microservice

The notification microservice will have just one method that is going to use the SMTP output binding to send an email to inform the customer that the order has been confirmed and what discount has been applied.

Here is the stripped version of the code, check GitHub for the complete one.

[HttpPost
[Topic("rabbitmqpubsub", "OrderSubmittedEventTopic")]
public async Task<ActionResult> OnOrderSubmittedEventAsync(
    OrderSubmittedIntegrationEvent orderSubmittedIntegrationEvent)
{
	var stringBuilder = new StringBuilder();
	stringBuilder.AppendLine($"Hello {orderSubmittedIntegrationEvent.CustomerFirstName} {orderSubmittedIntegrationEvent.CustomerLastName}<br>");
	stringBuilder.AppendLine($"Your order <strong>#{orderSubmittedIntegrationEvent.OrderId.ToString()[..6]}</strong> has been shipped.<br><br>");
	stringBuilder.AppendLine($"Your CRAZY DISCOUNT is <strong>#{orderSubmittedIntegrationEvent.TotalDiscount}%</strong>!<br><br>");

	var message = stringBuilder.ToString();
	var metadata = new Dictionary<string, string>
	{
		{ "emailTo", orderSubmittedIntegrationEvent.CustomerEmail },
		{ "subject", $"Order Shipped!" },
		{ "priority", "1" }
	};
	await this.daprClient
                        .InvokeBindingAsync(
                           "smtpbinding", 
                           "create", 
                           message, 
                           metadata);

	return Ok();
}]

The first thing you can notice is that we're using the Topic attribute to subscribe to the topic OrderSubmittedEventTopic on the rabbitmqpubsub component.

Dapr is going to invoke this method once a new message is published to that topic, and we receive the payload as a parameter (OrderSubmittedIntegrationEvent). We then use the payload to create an email and send it using the InvokeBindingAsync, passing as a parameter the name of the binding component (smtpbinding), the body of the email, and some metadata (subject, mail address, priority).

Of course, we have to configure the binding in the YAML specification:

apiVersion: dapr.io/v1alpha
kind: Component
metadata:   name: smtpbinding
spec:   type: bindings.smtp   version: v1   metadata:   - name: host     value: "smtp4dev"   - name: port     value: "25"   - name: emailFrom     value: "no-reply@microdelivery.com"

For testing purposes, I'm using an email server called smtp4dev hosted in docker to send and check emails.

No alt text provided for this image

The smtp4dev User Interface

Now let's see the shippings service.

No alt text provided for this image

Shipping microservice

All the logic is in the method OnOrderSubmittedEvent, where we're subscribing to the topic OrderSubmittedEventTopic, and we're performing an HTTP request using the HTTP Output Binding (to simulate a call to an external supplier). Once done, we publish the OrderShippedIntegrationEvent that will be handled by the Orders microservice to mark the order as shipped.

[HttpPost
[Topic("rabbitmqpubsub", "OrderSubmittedEventTopic")]
public async Task<ActionResult> OnOrderSubmittedEvent(
   OrderSubmittedIntegrationEvent orderSubmittedIntegrationEvent)
{
	await this.daprClient.InvokeBindingAsync(
                                    "httpbinding", 
                                    "post", 
                                     orderSubmittedIntegrationEvent);


	var orderShippedIntegrationEvent = new OrderShippedIntegrationEvent() 
                      { 
                        OrderId = orderSubmittedIntegrationEvent.OrderId, 
                        ShippedAtUtc = DateTime.UtcNow 
                      };

	await daprClient.PublishEventAsync(
                                        "rabbitmqpubsub", 
                                        "OrderShippedEventTopic", 
                                        orderShippedIntegrationEvent);

	return Ok();
}]

As in the notifications microservice, we use the InvokeBindingAsync, this time to call the HTTP endpoint, passing the orderSubmittedIntegrationEvent as payload.

The binding is configured with the following YAML specification:

apiVersion: dapr.io/v1alpha
kind: Component
metadata:
  name: httpbinding
spec:
  type: bindings.http
  version: v1
  metadata:
  - name: url
    value: http://echorestbot.azurewebsites.net/microdelivery
  - name: securityToken
    secretKeyRef:
      name: httpbindingtoken
      key: httpbindingtoken
  - name: securityTokenHeader
    value: "Authorization"
auth:
  secretStore: localsecretstore

Here you see that we want to perform a POST request to the URL http://echorestbot.azurewebsites.net/microdelivery (it's a simple API I built that gives you back the request details). We are also passing a token in the "Authorization" header; the value of the token is read by using the secret management build block (more info in the next article).

Here is an example of the request we made to the EchoRest endpoint, where you can see the payload containing the order's detail:

{   "RequestIdentifier": "14085696-218e-42f2-8966-7a0b37b506ea",   "Date": "2023-05-01T20:02:07.2657896Z",   "Url": "http://echorestbot.azurewebsites.net/microdelivery",   "Body": "{\"orderId\":\"307998cc-53c9-46cf-9dca-06df25419496\",\"customerId\":1,\"customerFirstName\":\"Joe\",\"customerLastName\":\"Doe\",\"customerEmail\":\"joe.doe@email.com\",\"orderLineItems\":[{\"productId\":1,\"productName\":\"Tomato salad\",\"quantity\":3,\"price\":8,\"discountedPrice\":8},{\"productId\":2,\"productName\":\"Margherita\",\"quantity\":3,\"price\":6,\"discountedPrice\":6}],\"totalDiscount\":0,\"eventId\":\"823132ef-197a-4fc3-8da4-175966bae8dc\"}",   "Headers": [     {       "Key": "Accept",       "Value": "application/json; charset=utf-8"     },     {       "Key": "Host",       "Value": "echorestbot.azurewebsites.net"     },        {       "Key": "Authorization",       "Value": "my auth token"     },     {       "Key": "Content-Type",       "Value": "application/json; charset=utf-8"     },     {       "Key": "Content-Length",       "Value": "412"     }          ],   "Method": "POST",   "Path": {     "Value": "/microdelivery",     "HasValue": true   }
}

After calling the sample endpoint, we're going to publish an OrderShippedIntegrationEvent that will be handled by the Order's microservice to update the order, just subscribing to that event using the Topic attribute we already saw.

[HttpPost("Ship")
[Topic("rabbitmqpubsub", "OrderShippedEventTopic")]
public async Task<ActionResult> OnOrderShippedEvent(OrderShippedIntegrationEvent orderShippedIntegrationEvent)
{
	var order = await this.orderRepository

                     .GetOrderAsync(orderShippedIntegrationEvent.OrderId);
	if (order is null)
	{
		return NotFound();
	}

	order.ShippedAtUtc = orderShippedIntegrationEvent.ShippedAtUtc;
	await this.orderRepository.UpdateOrderAsync(order);

	return Ok();
}]

In the next article, we'll discuss about secret management with DAPR.

posted @ lunedì 1 gennaio 0001 00:00 | Feedback (0) | Filed Under [ Dapr Microservices ]

Powered by:
Powered By Subtext Powered By ASP.NET