1 min read
Dapr 1.7: New Features for Distributed Applications
I wrote “Dapr 1.7: New Features for Distributed Applications” to share practical, production-minded guidance on this topic.
What is Dapr?
Dapr (Distributed Application Runtime) is a portable, event-driven runtime that makes it easy to build resilient, microservice applications. It provides building blocks like service invocation, state management, pub/sub, and more.
Installing Dapr 1.7
# Install Dapr CLI
curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | bash
# Initialize Dapr
dapr init
# Verify installation
dapr --version
Service Invocation with Resiliency
Dapr 1.7 introduces resiliency policies:
# resiliency.yaml
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
spec:
policies:
timeouts:
general: 5s
important: 60s
retries:
retryForever:
policy: constant
maxInterval: 5s
maxRetries: -1
important:
policy: exponential
maxInterval: 10s
maxRetries: 5
circuitBreakers:
simpleCB:
maxRequests: 1
interval: 30s
timeout: 60s
trip: consecutiveFailures >= 5
targets:
apps:
inventory-service:
timeout: important
retry: important
circuitBreaker: simpleCB
payment-service:
timeout: general
retry: retryForever
Using Resiliency in Code
using Dapr.Client;
public class OrderService
{
private readonly DaprClient _daprClient;
private readonly ILogger<OrderService> _logger;
public OrderService(DaprClient daprClient, ILogger<OrderService> logger)
{
_daprClient = daprClient;
_logger = logger;
}
public async Task<OrderResult> ProcessOrderAsync(Order order)
{
try
{
// Service invocation with automatic retry/circuit breaker
var inventoryResult = await _daprClient.InvokeMethodAsync<ReserveRequest, ReserveResponse>(
"inventory-service",
"reserve",
new ReserveRequest(order.Items));
if (!inventoryResult.Success)
{
return new OrderResult(false, "Inventory reservation failed");
}
// Process payment
var paymentResult = await _daprClient.InvokeMethodAsync<PaymentRequest, PaymentResponse>(
"payment-service",
"process",
new PaymentRequest(order.TotalAmount, order.PaymentMethod));
if (!paymentResult.Success)
{
// Compensate: release inventory
await _daprClient.InvokeMethodAsync(
"inventory-service",
"release",
new ReleaseRequest(inventoryResult.ReservationId));
return new OrderResult(false, "Payment failed");
}
return new OrderResult(true, "Order processed successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "Order processing failed for order {OrderId}", order.Id);
throw;
}
}
}
Pub/Sub with Dead Letter Queues
Configure dead letter topics for failed messages:
# pubsub-component.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.azure.servicebus
version: v1
metadata:
- name: connectionString
secretKeyRef:
name: servicebus-secret
key: connectionString
- name: maxDeliveryCount
value: "5"
- name: lockDurationInSec
value: "30"
- name: deadLetterQueue
value: "true"
Subscribe with error handling:
using Dapr;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class OrderEventsController : ControllerBase
{
private readonly IOrderProcessor _orderProcessor;
private readonly ILogger<OrderEventsController> _logger;
public OrderEventsController(
IOrderProcessor orderProcessor,
ILogger<OrderEventsController> logger)
{
_orderProcessor = orderProcessor;
_logger = logger;
}
[Topic("pubsub", "orders", "deadLetterTopic=orders-dlq")]
[HttpPost("process")]
public async Task<IActionResult> ProcessOrder([FromBody] Order order)
{
try
{
await _orderProcessor.ProcessAsync(order);
return Ok();
}
catch (TransientException ex)
{
_logger.LogWarning(ex, "Transient error processing order {OrderId}", order.Id);
// Return error to trigger retry
return StatusCode(500);
}
catch (Exception ex)
{
_logger.LogError(ex, "Permanent error processing order {OrderId}", order.Id);
// Return success to move to dead letter queue after max retries
return Ok();
}
}
[Topic("pubsub", "orders-dlq")]
[HttpPost("dead-letter")]
public async Task<IActionResult> HandleDeadLetter([FromBody] Order order)
{
_logger.LogWarning("Handling dead letter message for order {OrderId}", order.Id);
// Store for manual review
await _orderProcessor.StoreForReviewAsync(order);
return Ok();
}
}
State Management with TTL
New TTL support for state entries:
using Dapr.Client;
public class CacheService
{
private readonly DaprClient _daprClient;
private const string StoreName = "statestore";
public CacheService(DaprClient daprClient)
{
_daprClient = daprClient;
}
public async Task SetWithTTLAsync<T>(string key, T value, TimeSpan ttl)
{
var metadata = new Dictionary<string, string>
{
{ "ttlInSeconds", ttl.TotalSeconds.ToString() }
};
await _daprClient.SaveStateAsync(StoreName, key, value, metadata: metadata);
}
public async Task<T?> GetAsync<T>(string key)
{
return await _daprClient.GetStateAsync<T>(StoreName, key);
}
public async Task SetSessionDataAsync(string sessionId, SessionData data)
{
// Session expires in 30 minutes
await SetWithTTLAsync($"session:{sessionId}", data, TimeSpan.FromMinutes(30));
}
public async Task CacheProductAsync(Product product)
{
// Product cache expires in 1 hour
await SetWithTTLAsync($"product:{product.Id}", product, TimeSpan.FromHours(1));
}
}
Distributed Lock
New distributed lock building block:
using Dapr.Client;
public class InventoryService
{
private readonly DaprClient _daprClient;
private const string LockStore = "lockstore";
public InventoryService(DaprClient daprClient)
{
_daprClient = daprClient;
}
public async Task<bool> UpdateInventoryAsync(string productId, int quantity)
{
var lockName = $"inventory:{productId}";
// Try to acquire lock
var lockResponse = await _daprClient.Lock(
LockStore,
lockName,
"owner-1",
expiryInSeconds: 30);
if (!lockResponse.Success)
{
return false; // Could not acquire lock
}
try
{
// Perform inventory update
var currentInventory = await _daprClient.GetStateAsync<int>("statestore", $"inventory:{productId}");
var newInventory = currentInventory + quantity;
await _daprClient.SaveStateAsync("statestore", $"inventory:{productId}", newInventory);
return true;
}
finally
{
// Always release lock
await _daprClient.Unlock(LockStore, lockName, "owner-1");
}
}
}
Configuration API
Access external configuration stores:
# config-component.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: configstore
spec:
type: configuration.azure.appconfig
version: v1
metadata:
- name: connectionString
secretKeyRef:
name: appconfig-secret
key: connectionString
- name: maxRetries
value: "3"
Using configuration in your app:
using Dapr.Client;
public class FeatureFlagService
{
private readonly DaprClient _daprClient;
private const string ConfigStore = "configstore";
public FeatureFlagService(DaprClient daprClient)
{
_daprClient = daprClient;
}
public async Task<bool> IsFeatureEnabledAsync(string featureName)
{
var config = await _daprClient.GetConfiguration(
ConfigStore,
new[] { $"feature:{featureName}" });
if (config.Items.TryGetValue($"feature:{featureName}", out var item))
{
return bool.Parse(item.Value);
}
return false;
}
public async Task SubscribeToConfigChangesAsync(Action<IDictionary<string, ConfigurationItem>> handler)
{
await _daprClient.SubscribeConfiguration(
ConfigStore,
new[] { "feature:*" },
handler);
}
}
Summary
Dapr 1.7 enhances distributed application development with:
- Resiliency policies for retries and circuit breakers
- Dead letter queue support for pub/sub
- TTL support for state management
- Distributed lock building block
- Configuration API for external config stores
These features help build more resilient and maintainable microservices.