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!
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.
The smtp4dev User Interface
Now let's see the shippings service.
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.