Simplifying .NET Microservices with Dapr and Azure Container Apps
While building and maintaining applications using Microservices Architecture, developers often encounter issues around event-driven requirements. In addition to the application code, it’s required to manage communication between microservices correctly, whether HTTP/gRPC or pub/sub. Implementing this extra logic yourself will complicate your code, slow down the development and may cause unpredicted issues. Initially, I believed the ideal implementation would involve extracting the communication logic into a shared library to be reused across all services. However, after trying Dapr, I no longer favour this approach.
Through close collaboration with Samvirk, we developed a sophisticated architecture using a service-oriented approach and Distributed Application Runtime(Dapr). This streamlined microservices connectivity via APIs delivers a resilient and secure system. Dapr, drawing on Microsoft’s extensive experience in cloud-native development, enabled us to concentrate on the application logic without worrying about the complexities of managing a distributed system.
Dapr (Distributed Application Runtime) offers APIs that simplify microservices connectivity. It enables you to create resilient and secure architecture regardless of your chosen communication pattern. Based on Microsoft’s experience developing hyper-scale cloud-native applications, Dapr allows developers to focus on application logic and not hold distributed system challenges.
The Dapr runtime operates in sidecar processes, offloading most of the application’s complexity to a separate environment and simplifying development and operations. It was designed as a set of pluggable building blocks that developers can adopt selectively:
- Service Invocation
- State Management
- Publish & subscribe messaging
- Input/Output Bindings
- Actors
- Observability
- Secrets Management
Dapr’s official documentation provides detailed descriptions of all these building blocks.
Azure Support
Since at Reenbit we actively use Azure Cloud, it was important that Dapr apps could be hosted on Azure. There are a few hosting options:
- Running in self-hosted mode on a local dev machine or production VMs
- Running on any supported versions of Kubernetes
- Running on the Azure Container Apps platform
If you don’t want to maintain the VMs, orchestrators or other cloud infrastructure, Azure Container Apps is a great solution. It’s a new player in the Azure containers ecosystem – it was announced in 2021 at Ignite conference and went GA in May 2022. Although maintaining Azure Container Apps is much easier than Kubernetes, every container app runs on Azure Kubernetes Service with KEDA behind the scenes.
Let’s try implementing a sample distributed application using .NET & Dapr and deploying it to Azure Container Apps.
Sample Application
The application is an e-commerce system where a salesperson registers products to be sold, and customers can create and place orders for these products.
The requirements are the following:
- Sales Person Registers Products: A salesperson can register new products into the system and view the available products.
- Customer Makes Order: Customers can select multiple products to purchase within a single order. This step involves the customer choosing the products they wish to buy. Once the customer has ordered, the salesperson places this order into the system.
- Update Product Availability: After an order is placed, the system should update the products’ availability to reflect the new inventory levels.
We plan to build a microservices application using .NET 8.0 and Dapr, consisting of two services: the Inventory Service and the Ordering Service. Once developed, we will deploy it to Azure Container Apps.
Inventory Service
The Inventory Service handles product information and provides two endpoints:
HTTP GET /inventory/products
returns a list of available products
HTTP POST /inventory/products
adds a new product with the following data structure:
{
"title": "<string>",
"description": "<string>",
"price": 19.99,
"remainingCount": 100
}
Ordering Service
The Ordering Service manages order registrations within our system.
HTTP POST /ordering/orders
endpoint places a new order containing several products
{
"customerId": "<guid>",
"items": [ { "productId": "<guid>", "count": 2 } ]
}
Once an order is placed, the ordering service will notify the inventory service to update the availability of the products.
We will be using the following Azure Services:
- Azure Container Apps as a hosting platform to run microservices
- Azure Service Bus as a message broker
- Azure API Management for routing
- Azure Redis for the state store Dapr component
Our sample application is available on GitHub.
Here, we’ll highlight the most important implementation steps:
-
Step 1. Add Dapr to our services
Before we start typing code, we need to reference Nuget packages in our API projects:
Dapr.AspNetCore
andDapr.Client
. Both Reenbit.Inventory.API and Reenbit.Ordering.API has these references.Next, add some initializations:
// Register DaprClient dependency builder.Services.AddDaprClient(); // Register the CloudEvents middleware to receive every incoming request with the content type of “application/cloudevents+json” app.UseCloudEvents(); // Subscribe to Dapr Pub/Sub component app.MapSubscribeHandler();
The
app.MapSubscribeHandler
method will add the endpoint, allowing it to return the available subscriptions. When called, this endpoint identifies all WebAPI actions marked with the Dapr.Topic attribute and instructs Dapr to set up subscriptions for these actions. In our case, this method is only needed in the Inventory service. -
Step 2. Publish Dapr event
When a new order is placed in the Ordering service, we want to publish a message to the Azure Service Bus Topic using Dapr.
public async Task<OrderResponse> Handle(PlaceOrderCommand request, CancellationToken cancellationToken) { var order = new Order(); order.Create(request.CustomerId, request.Items); ... // publish message to Service Bus OrderPlaced @event = CreateOrderPlacedEvent(order); await daprClient.PublishEventAsync(pubsubName: "pubsub", topicName: "order-placed", @event, cancellationToken); ... }
-
Step 3. Subscribe to Dapr event
To subscribe to the
order-placed
topic in the Inventory service, add HTTP POST endpoint withDapr.Topic
attribute:[HttpPost("order-placed")] [Topic(pubsubName: "pubsub", name: "order-placed")] public async Task<IActionResult> UpdateProductsRemainingAmount(OrderPlaced orderPlaced) { var command = new UpdateProductsRemainingCountCommand(orderPlaced.Items); await Mediator.Send(command); return Ok(); }
-
Step 4. Update Products state
Here, we will utilize the state store Dapr component “connected” to the Azure Redis database.
var products = await daprClient.GetStateAsync<List<Product>>(_stateStore, "products", cancellationToken: cancellationToken) ?? []; foreach (var orderedItem in request.OrderedItems) { var product = products.FirstOrDefault(x => x.ProductId == orderedItem.ProductId); product?.UpdateRemainingAmount(product.RemainingCount - orderedItem.Count); } await daprClient.SaveStateAsync(_stateStore, "products", products, cancellationToken: cancellationToken);
-
Step 5. Deployment to Azure Container Apps
Using Azure shell, run the following command and save the output as
AZURE_CREDENTIALS
secret in the GitHub repository.az ad sp create-for-rbac --name "github-actions-app" --role contributor --sdk-auth --scopes /subscriptions/{subscriptionId}
We implement the deployments using GitHub Actions. An initial manual run of the
infrastructure-deploy.yml
workflow is required to provision the Azure infrastructure. Reenbit.Infrastructure folder contains all bicep files needed to provision the environment. Subsequent deployments are automated via GitHub Actions and Bicep templates to ensure continuous integration and deployment of the .NET distributed application with Dapr and Azure Container Apps.
Conclusion
When you integrate Dapr and Azure Container Apps into your .NET distributed applications, you’ll experience a streamlined development process and enhanced maintainability. Developers can focus on writing business logic thanks to Dapr’s modular building blocks, which handle complex infrastructure concerns. Azure Container Apps provide a seamless deployment environment that scales efficiently with your application’s demands.
Integrating Dapr and Azure Container Apps can make a major difference in starting a new project or improving an existing application. Discover the true potential of your .NET distributed applications by embracing this potent duo.