6 min read
Dapr on Azure for Cloud-Native Applications
Introduction
Dapr (Distributed Application Runtime) is a portable, event-driven runtime that simplifies building resilient, stateless, and stateful microservices. With excellent Azure integration, Dapr provides building blocks for service invocation, state management, pub/sub messaging, and more. This guide demonstrates Dapr patterns with Azure services.
Dapr Building Blocks
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Dapr Sidecar │
├─────────────┬─────────────┬─────────────┬───────────────────┤
│ Service │ State │ Pub/Sub │ Bindings │
│ Invocation │ Management │ Messaging │ (Input/Output) │
├─────────────┼─────────────┼─────────────┼───────────────────┤
│ Actors │ Secrets │Observability│ Configuration │
└─────────────┴─────────────┴─────────────┴───────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Azure Services │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Cosmos DB │ │Service Bus│ │ Blob │ │ Key Vault │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────┘
Setting Up Dapr Components
State Store with Cosmos DB
# components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.azure.cosmosdb
version: v1
metadata:
- name: url
value: "https://mycosmosdb.documents.azure.com:443/"
- name: masterKey
secretKeyRef:
name: cosmos-secret
key: masterKey
- name: database
value: "dapr-state"
- name: collection
value: "state"
- name: actorStateStore
value: "true"
Pub/Sub with Service Bus
# components/pubsub.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: orderpubsub
spec:
type: pubsub.azure.servicebus
version: v1
metadata:
- name: connectionString
secretKeyRef:
name: servicebus-secret
key: connectionString
- name: consumerID
value: "order-processor"
Secrets with Key Vault
# components/secretstore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: azurekeyvault
spec:
type: secretstores.azure.keyvault
version: v1
metadata:
- name: vaultName
value: "mykeyvault"
- name: azureTenantId
value: "tenant-id"
- name: azureClientId
value: "client-id"
- name: azureClientSecret
secretKeyRef:
name: azure-secret
key: clientSecret
Service Invocation
Calling Services with Dapr SDK
public class OrderService
{
private readonly DaprClient _daprClient;
public OrderService(DaprClient daprClient)
{
_daprClient = daprClient;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Check inventory via service invocation
var inventoryCheck = await _daprClient.InvokeMethodAsync<InventoryRequest, InventoryResponse>(
"inventory-service",
"check",
new InventoryRequest { Items = request.Items });
if (!inventoryCheck.AllAvailable)
{
throw new InsufficientInventoryException(inventoryCheck.UnavailableItems);
}
// Create order
var order = new Order
{
Id = Guid.NewGuid(),
CustomerId = request.CustomerId,
Items = request.Items,
Status = OrderStatus.Created,
CreatedAt = DateTime.UtcNow
};
// Save to state store
await _daprClient.SaveStateAsync("statestore", order.Id.ToString(), order);
// Publish order created event
await _daprClient.PublishEventAsync("orderpubsub", "orders", new OrderCreatedEvent
{
OrderId = order.Id,
CustomerId = request.CustomerId,
Items = request.Items
});
return order;
}
public async Task<Order?> GetOrderAsync(Guid orderId)
{
return await _daprClient.GetStateAsync<Order>("statestore", orderId.ToString());
}
}
ASP.NET Core Integration
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
builder.Services.AddControllers().AddDapr();
var app = builder.Build();
app.UseCloudEvents();
app.MapSubscribeHandler();
app.MapControllers();
app.Run();
Pub/Sub Messaging
Publishing Events
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly DaprClient _daprClient;
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
var order = new Order
{
Id = Guid.NewGuid(),
CustomerId = request.CustomerId,
Items = request.Items
};
// Publish to Service Bus topic
await _daprClient.PublishEventAsync(
"orderpubsub",
"orders",
order,
new Dictionary<string, string>
{
["cloudevent.type"] = "order.created",
["cloudevent.source"] = "order-service"
});
return Created($"/api/orders/{order.Id}", order);
}
}
Subscribing to Events
[ApiController]
public class OrderEventsController : ControllerBase
{
private readonly IOrderProcessor _processor;
private readonly ILogger<OrderEventsController> _logger;
[Topic("orderpubsub", "orders")]
[HttpPost("/orders/events")]
public async Task<IActionResult> HandleOrderEvent(Order order)
{
_logger.LogInformation("Received order event: {OrderId}", order.Id);
await _processor.ProcessAsync(order);
return Ok();
}
// Programmatic subscription
[Topic("orderpubsub", "inventory-updates", "event.type == \"inventory.reserved\"", 1)]
[HttpPost("/inventory/events")]
public async Task<IActionResult> HandleInventoryReserved(InventoryReservedEvent @event)
{
await _processor.HandleInventoryReservedAsync(@event);
return Ok();
}
}
State Management
CRUD Operations
public class ShoppingCartService
{
private readonly DaprClient _daprClient;
private const string StoreName = "statestore";
public async Task<Cart> GetCartAsync(string userId)
{
var cart = await _daprClient.GetStateAsync<Cart>(StoreName, $"cart-{userId}");
return cart ?? new Cart { UserId = userId, Items = new List<CartItem>() };
}
public async Task AddItemAsync(string userId, CartItem item)
{
var cart = await GetCartAsync(userId);
cart.Items.Add(item);
cart.UpdatedAt = DateTime.UtcNow;
await _daprClient.SaveStateAsync(StoreName, $"cart-{userId}", cart);
}
public async Task ClearCartAsync(string userId)
{
await _daprClient.DeleteStateAsync(StoreName, $"cart-{userId}");
}
// Optimistic concurrency
public async Task<bool> UpdateCartAsync(string userId, Cart cart)
{
var (currentCart, etag) = await _daprClient.GetStateAndETagAsync<Cart>(
StoreName,
$"cart-{userId}");
try
{
await _daprClient.TrySaveStateAsync(
StoreName,
$"cart-{userId}",
cart,
etag);
return true;
}
catch (DaprException)
{
return false; // Concurrency conflict
}
}
}
Dapr Actors
Actor Interface and Implementation
// Actor Interface
public interface IOrderActor : IActor
{
Task<Order> GetOrderAsync();
Task CreateAsync(CreateOrderRequest request);
Task AddItemAsync(OrderItem item);
Task SubmitAsync();
Task SetReminderAsync();
}
// Actor Implementation
public class OrderActor : Actor, IOrderActor, IRemindable
{
private const string OrderStateName = "OrderState";
public OrderActor(ActorHost host) : base(host)
{
}
public async Task<Order> GetOrderAsync()
{
return await StateManager.GetStateAsync<Order>(OrderStateName);
}
public async Task CreateAsync(CreateOrderRequest request)
{
var order = new Order
{
Id = Guid.Parse(Id.GetId()),
CustomerId = request.CustomerId,
Items = new List<OrderItem>(),
Status = OrderStatus.Draft,
CreatedAt = DateTime.UtcNow
};
await StateManager.SetStateAsync(OrderStateName, order);
}
public async Task AddItemAsync(OrderItem item)
{
var order = await StateManager.GetStateAsync<Order>(OrderStateName);
order.Items.Add(item);
await StateManager.SetStateAsync(OrderStateName, order);
}
public async Task SubmitAsync()
{
var order = await StateManager.GetStateAsync<Order>(OrderStateName);
order.Status = OrderStatus.Submitted;
order.SubmittedAt = DateTime.UtcNow;
await StateManager.SetStateAsync(OrderStateName, order);
}
public async Task SetReminderAsync()
{
await RegisterReminderAsync(
"OrderExpiration",
null,
TimeSpan.FromMinutes(30),
TimeSpan.FromMilliseconds(-1));
}
public async Task ReceiveReminderAsync(
string reminderName,
byte[] state,
TimeSpan dueTime,
TimeSpan period)
{
if (reminderName == "OrderExpiration")
{
var order = await StateManager.GetStateAsync<Order>(OrderStateName);
if (order.Status == OrderStatus.Draft)
{
order.Status = OrderStatus.Expired;
await StateManager.SetStateAsync(OrderStateName, order);
}
}
}
}
Using Actors
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly IActorProxyFactory _actorProxyFactory;
public OrdersController(IActorProxyFactory actorProxyFactory)
{
_actorProxyFactory = actorProxyFactory;
}
[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
var orderId = Guid.NewGuid();
var actorId = new ActorId(orderId.ToString());
var proxy = _actorProxyFactory.CreateActorProxy<IOrderActor>(
actorId,
"OrderActor");
await proxy.CreateAsync(request);
await proxy.SetReminderAsync();
return Created($"/api/orders/{orderId}", new { OrderId = orderId });
}
[HttpGet("{orderId}")]
public async Task<IActionResult> GetOrder(Guid orderId)
{
var proxy = _actorProxyFactory.CreateActorProxy<IOrderActor>(
new ActorId(orderId.ToString()),
"OrderActor");
var order = await proxy.GetOrderAsync();
return Ok(order);
}
[HttpPost("{orderId}/items")]
public async Task<IActionResult> AddItem(Guid orderId, OrderItem item)
{
var proxy = _actorProxyFactory.CreateActorProxy<IOrderActor>(
new ActorId(orderId.ToString()),
"OrderActor");
await proxy.AddItemAsync(item);
return NoContent();
}
}
Bindings for Azure Services
Blob Storage Binding
# components/blobstorage.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: blobstore
spec:
type: bindings.azure.blobstorage
version: v1
metadata:
- name: storageAccount
value: "mystorageaccount"
- name: storageAccessKey
secretKeyRef:
name: storage-secret
key: accessKey
- name: container
value: "orders"
Using Output Binding
public class OrderExportService
{
private readonly DaprClient _daprClient;
public async Task ExportOrderAsync(Order order)
{
var json = JsonSerializer.Serialize(order);
await _daprClient.InvokeBindingAsync(
"blobstore",
"create",
json,
new Dictionary<string, string>
{
["blobName"] = $"orders/{order.Id}.json",
["contentType"] = "application/json"
});
}
}
Kubernetes Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "order-service"
dapr.io/app-port: "80"
dapr.io/log-level: "info"
spec:
containers:
- name: order-service
image: myacr.azurecr.io/order-service:latest
ports:
- containerPort: 80
env:
- name: ASPNETCORE_URLS
value: "http://+:80"
Conclusion
Dapr simplifies building cloud-native applications by providing consistent APIs for common microservices patterns. With native Azure integration, you can leverage Cosmos DB for state, Service Bus for messaging, and Key Vault for secrets, all through standardized Dapr APIs. This portability allows applications to run on any cloud or on-premises infrastructure.