Dapr service discovery & invocation is a feature that allows services to discover and call other services using HTTP or gRPC protocols, providing a simple and consistent way to invoke them.
Communication is also
- secure (with mutual mTLS authentication)
- resilient (with configurable retry\circuit breaker\timeout policies)
- traced and metered (using common protocols)
- controlled (we can use ACL to restrict accesses\permissions to some APIs)
- balanced (using round-robin)
The pub/sub is a messaging pattern where a message is published by a sender to a topic, and all the subscribers who have subscribed to that topic will receive a copy of the message. This allows loosely coupled communication between services, as the sender does not need to know who the receivers are or how many there are, and the subscribers do not need to know who the publisher is.
The pub/sub API in DAPR provides a set of APIs that can be used to implement this pattern and allows developers to use different message brokers like Apache Kafka, RabbitMQ, Azure Service Bus, and many others. The API is platform-agnostic and offers an at least-once message delivery guarantee.
Let's see how we can use both functionalities in the orders microservices.
As always, we have a standard API controller where we inject the DaprClient.
The relevant code is in the SubmitOrder.
Essentially, what we want to do is:
- retrieve customer info (calling the customer's microservice)
- apply the current valid discount (calling the discount microservice)
- retrieve products info
- create and save a new order
- publish a message OrderSubmittedIntegrationEvent
[HttpPost("Submit")
[ProducesResponseType(typeof(Order), (int)HttpStatusCode.Created)]
public async Task<ActionResult> SubmitOrder(SubmitOrderRequest request)
{
var customerInfo = await daprClient
.InvokeMethodAsync<CustomerInfo>(
HttpMethod.Get,
"microdelivery-customers-api",
$"customers/{request.CustomerId}");
if (customerInfo is null)
{
throw new Exception($"Customer {request.CustomerId} not found");
}
var discount = await daprClient
.InvokeMethodAsync<int>(
HttpMethod.Get,
"microdelivery-discount-api",
"discount");
var order = new Order
{
CustomerId = customerInfo.Id,
CustomerFirstName = customerInfo.FirstName,
CustomerLastName = customerInfo.LastName,
CustomerEmail = customerInfo.Email,
TotalDiscount = discount
};
var orderLineItems = new List<OrderLineItem>();
foreach (var requestOrderLineItem in request.OrderLineItems)
{
var productInfo = await daprClient
.InvokeMethodAsync<ProductInfo>(
HttpMethod.Get,
"microdelivery-products-api",
$"products/{requestOrderLineItem.ProductId}");
if (productInfo is null)
{
throw new Exception($"Product {requestOrderLineItem.ProductId} not found");
}
var discountedPrice = discount == 0 ? productInfo.Price : productInfo.Price - (productInfo.Price * ((double)discount / 100));
var orderLineItem = new OrderLineItem
{
ProductId = productInfo.Id,
ProductName = productInfo.Name,
Price = productInfo.Price,
DiscountedPrice = discountedPrice,
Quantity = requestOrderLineItem.Quantity
};
orderLineItems.Add(orderLineItem);
}
order.OrderLineItems = orderLineItems;
await orderRepository.CreateOrderAsync(order);
var orderSubmittedIntegrationEvent = new OrderSubmittedIntegrationEvent
{
OrderId = order.Id,
CustomerId = order.CustomerId,
CustomerFirstName = order.CustomerFirstName,
CustomerLastName = order.CustomerLastName,
CustomerEmail = order.CustomerEmail,
TotalDiscount = order.TotalDiscount,
OrderLineItems = order.OrderLineItems.Select(oli =>
new OrderSubmittedIntegrationEventLineItem
{
ProductId = oli.ProductId,
ProductName = oli.ProductName,
Quantity = oli.Quantity,
Price = oli.Price,
DiscountedPrice = oli.DiscountedPrice
})
};
await daprClient.PublishEventAsync(
"rabbitmqpubsub",
"OrderSubmittedEventTopic",
orderSubmittedIntegrationEvent);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
For the service invocation, we use the method InvokeMethodAsync<T> of the DaprClient.
We have to specify the HTTP Verb to use, the application id of the other service (we set it on the sidecar configuration in the docker-compose file), and the method name. If the call is successful, we obtain the response of type T. That's simple.
To publish a message, we use the method PublishEventAsync<T> of the DaprClient.
We have to specify the name of the pubsub component, the topic where we want to publish the message, and the message payload.
In our example, we're going to use RabbitMq, so here is the component specification:
apiVersion: dapr.io/v1alpha
kind: Component
metadata: name: rabbitmqpubsub
spec: type: pubsub.rabbitmq version: v1 metadata: - name: connectionString value: "amqp://rabbit:5672" - name: hostname value: rabbit - name: username value: guest - name: password value: guest
In the next article, we'll see how other services can subscribe to the published event in order to perform their logic.