Back to Blog
5 min read

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:

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.