RO EN

Azure Container Apps (3) — Dapr integration

Azure Container Apps (3) — Dapr integration ✨ Imagine generată cu AI
Doru Bulubașa
03 July 2026
44 views

The third part of the series about Azure Container Apps. In the second part we configured scaling rules. Here we see how services communicate with each other using Dapr.


What is Dapr and what problem does it solve

Dapr (Distributed Application Runtime) is a runtime that provides building blocks for distributed applications: service invocation, pub/sub, state management, secrets, bindings. The central idea: instead of writing code specific to each technology (Service Bus SDK, Cosmos DB SDK, HTTP client with retry, etc.), you interact with a uniform API, and Dapr translates to the actual infrastructure.

The concrete benefit in microservices: you separate what the application does from how it communicates with the infrastructure. You change the message broker from Service Bus to RabbitMQ by modifying a configuration file, not the code.

In Container Apps, Dapr is built-in — you don't install anything, you just enable it. ACA automatically runs the Dapr sidecar alongside your container.


Enabling Dapr

az containerapp create \
  --name order-service \
  --resource-group my-rg \
  --environment my-aca-env \
  --image myregistry.azurecr.io/order-service:latest \
  --target-port 8080 \
  --ingress internal \
  --enable-dapr \
  --dapr-app-id order-service \
  --dapr-app-port 8080

dapr-app-id is the identifier through which other services address this service. dapr-app-port is the port on which Dapr communicates with your application. In .NET, you add the Dapr SDK:

dotnet add package Dapr.Client
dotnet add package Dapr.AspNetCore

Service invocation — calls between services

Without Dapr, to call another service you need its URL, a configured HttpClient, a retry policy, mTLS. With Dapr, you address the service by app-id and Dapr takes care of the rest (service discovery, retry, mTLS between sidecars).

// Program.cs
builder.Services.AddDaprClient();

// Call to another service by app-id, not by URL
public class OrderService
{
    private readonly DaprClient _dapr;
    public OrderService(DaprClient dapr) => _dapr = dapr;

    public async Task GetCustomerAsync(string customerId)
    {
        // Invoke "customer-service" -- Dapr resolves where it runs
        return await _dapr.InvokeMethodAsync(
            HttpMethod.Get,
            appId: "customer-service",
            methodName: $"customers/{customerId}");
    }
}

The called service does not need anything special — it is a normal HTTP endpoint. Dapr injects the request through the sidecar.


Pub/Sub with Service Bus

Pub/sub is where Dapr shines. You configure a Dapr component that points to Service Bus, and the code publishes and consumes messages through a uniform API.

The Dapr component (pub/sub)

# pubsub-servicebus.yaml
componentType: pubsub.azure.servicebus.topics
version: v1
metadata:
  - name: namespaceName
    value: "my-servicebus.servicebus.windows.net"
  - name: azureClientId
    value: ""  # authentication without secret
scopes:
  - order-service
  - notification-service
# Register the component in the environment
az containerapp env dapr-component set \
  --name my-aca-env \
  --resource-group my-rg \
  --dapr-component-name orderpubsub \
  --yaml pubsub-servicebus.yaml

Publishing

public async Task PublishOrderCreatedAsync(Order order)
{
    await _dapr.PublishEventAsync(
        pubsubName: "orderpubsub",
        topicName: "order-created",
        data: order);
}

Consuming (subscribe)

// Program.cs
app.MapSubscribeHandler(); // endpoint for registering Dapr subscriptions

// Controller that receives the events
[ApiController]
public class NotificationController : ControllerBase
{
    [Topic("orderpubsub", "order-created")]
    [HttpPost("order-created")]
    public async Task OnOrderCreated(Order order)
    {
        await _emailService.SendConfirmationAsync(order);
        return Ok();
    }
}

The [Topic] attribute binds the method to the Dapr topic. When a message arrives on order-created, Dapr delivers it to this method. No Service Bus SDK code, no manual connection management.


State management with Cosmos DB

Dapr also offers an abstract key-value state store, with Cosmos DB (or Redis, or others) behind it.

# statestore-cosmos.yaml
componentType: state.azure.cosmosdb
version: v1
metadata:
  - name: url
    value: "https://my-cosmos.documents.azure.com:443/"
  - name: database
    value: "StateDb"
  - name: collection
    value: "state"
  - name: azureClientId
    value: ""
scopes:
  - order-service
// Save and read state through a uniform API
public async Task SaveCartAsync(string userId, Cart cart)
{
    await _dapr.SaveStateAsync("statestore", $"cart-{userId}", cart);
}

public async Task GetCartAsync(string userId)
{
    return await _dapr.GetStateAsync("statestore", $"cart-{userId}");
}

Note again the azureClientId in all components: Dapr authenticates to Service Bus and Cosmos DB through Managed Identity, without any connection string. The security foundation from previous articles applies fully.


When to use Dapr and when not

Dapr adds real value to a microservices architecture with inter-service communication, pub/sub, and portability needs. For a single monolithic service, it is an unjustified overhead — native Azure SDKs are sufficient. As always in the series: you adopt complexity only when it solves a concrete problem.


What’s next

In the fourth and final part we put everything together with a complete CI/CD pipeline: GitHub Actions that builds the image, pushes it to Azure Container Registry, and deploys it to Container Apps — all with secretless authentication via OIDC.

Questions? Write to me at contact@ludoprogramming.com.