Simplifying .NET Microservices with Dapr and Azure Container Apps

Yuriy Butkevych (2)
Yuriy Butkevych
Technology Evangelist at Reenbit

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 Scheme

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.

Sample Application

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:

Ordering Service

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 and Dapr.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 with Dapr.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.

Your browser does not support the Canvas element.

Tell us about your challenge!

Use the contact form and we’ll get back to you shortly.

    Our marketing team will store your data to get in touch with you regarding your request. For more information, please inspect our privacy policy.

    thanks!

    We'll get in touch soon!

    contact us