Dapr 1.7: New Features for Distributed Applications
Dapr 1.7 brings significant improvements for building distributed applications. This release focuses on observability, security, and new component capabilities that make microservices development easier.
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.
References: