Skip to content
Back to Blog
1 min read

Dapr 1.7: New Features for Distributed Applications

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.

Michael John Peña

Michael John Peña

Data & AI Director and Microsoft MVP based in Sydney. Writing about Microsoft Fabric, Azure, data engineering, and AI.