Dapr (Distributed Application Runtime) in ConnectSoft Microservice Template¶
Purpose & Overview¶
Dapr (Distributed Application Runtime) is a portable, event-driven runtime that makes it easy for developers to build resilient, microservice-based distributed applications. In the ConnectSoft Microservice Template, Dapr integration is planned as an alternative actor model implementation and a comprehensive runtime for building distributed applications.
Why Dapr?¶
Dapr offers several key benefits for distributed applications:
- Sidecar Architecture: Dapr runs as a sidecar process, providing capabilities without requiring SDK changes
- Language Agnostic: Works with any programming language or framework
- Built-in Building Blocks: Provides common patterns like pub/sub, state management, service invocation, and actors
- Polyglot Support: Different services can use different languages while sharing Dapr capabilities
- Cloud-Native: Designed for Kubernetes, containers, and any hosting environment
- Observability: Built-in metrics, tracing, and logging
- Security: mTLS, secrets management, and access control policies
- Actor Model: Virtual actors with state persistence and automatic lifecycle management
Dapr Status in Template
Dapr integration is planned for future implementation. The template currently uses Microsoft Orleans as the primary actor runtime. Dapr would provide an alternative actor implementation with polyglot support and sidecar-based architecture. This documentation describes how Dapr would integrate with the template once implemented.
Architecture Overview¶
Dapr Sidecar Architecture¶
Application Container
├── Your Application (.NET)
│ ├── Actor Client (Dapr SDK)
│ └── Business Logic
└── Dapr Sidecar
├── Actor Runtime
├── State Management
├── Pub/Sub
├── Service Invocation
└── Configurations
Dapr in ConnectSoft Architecture¶
API Layer (REST/gRPC/GraphQL)
↓
Application Layer (DomainModel)
├── Processors (Commands/Writes)
└── Retrievers (Queries/Reads)
↓ (Actor Invocation via Dapr SDK)
Dapr Sidecar
├── Actor Runtime
├── Actor State (Redis/Cosmos DB/etc.)
├── Pub/Sub (RabbitMQ/Azure Service Bus/etc.)
└── Service Discovery
↓
Storage Layer
├── State Stores (Redis, Cosmos DB, etc.)
└── Pub/Sub Brokers
Key Integration Points (Planned)¶
| Layer | Component | Responsibility |
|---|---|---|
| ActorModel | IBankAccountActor |
Domain actor interface (framework-agnostic) |
| ActorModel.Dapr | BankAccountActor, ActorProxy |
Dapr-specific actor implementation |
| ApplicationModel | DaprExtensions | Dapr client configuration and startup |
| DomainModel | Domain Logic | Business rules executed within actors |
| PersistenceModel | Actor State Storage | Persistent actor state via Dapr state stores |
Dapr Building Blocks¶
1. Actors¶
Virtual Actors with automatic lifecycle management:
- State Persistence: Actors can save state to configurable state stores
- Automatic Activation: Actors are activated on-demand
- Single-Threaded Execution: Guaranteed sequential message processing
- Timers and Reminders: Scheduled actor operations
- State Management: Automatic state persistence and recovery
2. State Management¶
Distributed state management with pluggable stores:
- State Stores: Redis, Cosmos DB, MongoDB, Azure Blob Storage, etc.
- Consistency Models: Strong consistency, eventual consistency
- ETags: Optimistic concurrency control
- Bulk Operations: Batch state operations
- Transactional Support: Multi-item transactions
3. Pub/Sub¶
Publish-subscribe messaging with multiple brokers:
- Message Brokers: RabbitMQ, Azure Service Bus, Kafka, Redis, etc.
- At-Least-Once Delivery: Guaranteed message delivery
- Topic Subscriptions: Subscribe to topics
- Dead Letter Queues: Handle failed messages
- CloudEvents: Standard event format
4. Service Invocation¶
Service-to-service communication:
- Service Discovery: Automatic service discovery
- Load Balancing: Built-in load balancing
- mTLS: Automatic mutual TLS
- Retries: Configurable retry policies
- Timeout Control: Request timeouts
5. Bindings¶
Connect to external systems:
- Input Bindings: Trigger applications from external events
- Output Bindings: Invoke external systems
- Protocol Abstraction: HTTP, gRPC, etc.
6. Secrets Management¶
Secure secret access:
- Secret Stores: Azure Key Vault, HashiCorp Vault, Kubernetes secrets, etc.
- Secret Rotation: Automatic secret rotation support
- Access Control: Fine-grained access policies
7. Configuration¶
Dynamic configuration management:
- Configuration Stores: Azure App Configuration, Consul, etc.
- Subscribe to Changes: React to configuration updates
- Versioning: Configuration versioning
8. Observability¶
Built-in observability:
- Metrics: Prometheus-compatible metrics
- Tracing: OpenTelemetry distributed tracing
- Logging: Structured logging
- Health Checks: Application health endpoints
Dapr Actors (Planned Implementation)¶
Actor Model Overview¶
Dapr Actors provide a virtual actor abstraction similar to Orleans:
- Virtual Actors: Actors are virtualized (referenced by ID, not location)
- Single-Threaded: Each actor processes one message at a time
- State Persistence: Actor state can be persisted to state stores
- Lifecycle Management: Automatic activation and deactivation
- Timers: Fire-and-forget timers
- Reminders: Durable, persistent reminders
Actor Interface (Planned)¶
// ActorModel/IBankAccountActor.cs (framework-agnostic)
namespace ConnectSoft.MicroserviceTemplate.ActorModel
{
public interface IBankAccountActor
{
Task<decimal> GetBalanceAsync(CancellationToken cancellationToken);
Task DepositAsync(decimal amount, CancellationToken cancellationToken);
Task<bool> WithdrawAsync(decimal amount, CancellationToken cancellationToken);
}
}
Dapr Actor Implementation (Planned)¶
// ActorModel.Dapr/BankAccountActor.cs
namespace ConnectSoft.MicroserviceTemplate.ActorModel.Dapr
{
using Dapr.Actors;
using Dapr.Actors.Runtime;
using ConnectSoft.MicroserviceTemplate.ActorModel;
public class BankAccountActor : Actor, IBankAccountActor
{
private const string StateName = "BankAccountState";
public BankAccountActor(ActorHost host)
: base(host)
{
}
public async Task<decimal> GetBalanceAsync(CancellationToken cancellationToken)
{
var state = await this.StateManager.GetStateAsync<BankAccountState>(
StateName, cancellationToken);
return state?.Balance ?? 0m;
}
public async Task DepositAsync(decimal amount, CancellationToken cancellationToken)
{
var state = await this.StateManager.GetStateAsync<BankAccountState>(
StateName, cancellationToken) ?? new BankAccountState();
state.Balance += amount;
state.LastUpdated = DateTime.UtcNow;
await this.StateManager.SetStateAsync(
StateName, state, cancellationToken);
}
public async Task<bool> WithdrawAsync(decimal amount, CancellationToken cancellationToken)
{
var state = await this.StateManager.GetStateAsync<BankAccountState>(
StateName, cancellationToken);
if (state == null || state.Balance < amount)
{
return false;
}
state.Balance -= amount;
state.LastUpdated = DateTime.UtcNow;
await this.StateManager.SetStateAsync(
StateName, state, cancellationToken);
return true;
}
protected override Task OnActivateAsync()
{
// Actor activation logic
return Task.CompletedTask;
}
protected override Task OnDeactivateAsync()
{
// Actor deactivation logic
return Task.CompletedTask;
}
}
public class BankAccountState
{
public decimal Balance { get; set; }
public DateTime LastUpdated { get; set; }
}
}
Actor Registration (Planned)¶
// ApplicationModel/DaprExtensions.cs
public static IServiceCollection AddDaprActors(
this IServiceCollection services)
{
services.AddActors(options =>
{
// Register actor types
options.Actors.RegisterActor<BankAccountActor>();
// Configure actor settings
options.ActorIdleTimeout = TimeSpan.FromMinutes(60);
options.ActorScanInterval = TimeSpan.FromSeconds(30);
options.DrainOngoingCallTimeout = TimeSpan.FromSeconds(60);
options.DrainRebalancedActors = true;
});
return services;
}
Actor Client Usage (Planned)¶
// Using actor client
public class BankAccountService
{
private readonly IActorProxyFactory actorProxyFactory;
public BankAccountService(IActorProxyFactory actorProxyFactory)
{
this.actorProxyFactory = actorProxyFactory;
}
public async Task<decimal> GetBalanceAsync(
string accountId,
CancellationToken cancellationToken)
{
var actor = this.actorProxyFactory.CreateActorProxy<IBankAccountActor>(
new ActorId(accountId),
"BankAccountActor");
return await actor.GetBalanceAsync(cancellationToken);
}
}
Dapr State Management¶
State Store Configuration¶
Component Configuration (components/statestore.yaml):
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
secretKeyRef:
name: redis-secret
key: redis-password
State Operations¶
Save State:
var client = new DaprClientBuilder().Build();
await client.SaveStateAsync(
"statestore",
"key",
"value",
cancellationToken: cancellationToken);
Get State:
var value = await client.GetStateAsync<string>(
"statestore",
"key",
cancellationToken: cancellationToken);
Delete State:
Transaction:
var transaction = new List<StateTransactionRequest>
{
new StateTransactionRequest("key1", "value1", StateOperationType.Upsert),
new StateTransactionRequest("key2", "value2", StateOperationType.Upsert),
};
await client.ExecuteStateTransactionAsync(
"statestore",
transaction,
cancellationToken: cancellationToken);
Dapr Pub/Sub¶
Pub/Sub Configuration¶
Component Configuration (components/pubsub.yaml):
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: amqp://localhost:5672
- name: consumerID
value: "myapp"
Publishing Events¶
var client = new DaprClientBuilder().Build();
await client.PublishEventAsync(
"pubsub",
"order-created",
new OrderCreatedEvent
{
OrderId = "123",
Amount = 100.00m
},
cancellationToken: cancellationToken);
Subscribing to Events¶
[Topic("pubsub", "order-created")]
[HttpPost("order-created")]
public async Task<IActionResult> HandleOrderCreated(
[FromBody] OrderCreatedEvent eventData)
{
// Handle event
await this.orderService.ProcessOrderAsync(eventData);
return Ok();
}
Dapr Service Invocation¶
Invoking Services¶
var client = new DaprClientBuilder().Build();
var response = await client.InvokeMethodAsync<Request, Response>(
"target-service",
"api/endpoint",
new Request { Data = "value" },
cancellationToken: cancellationToken);
Service Invocation Configuration¶
Component Configuration (components/serviceinvocation.yaml):
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: serviceinvocation
spec:
type: serviceinvocation.http
version: v1
Dapr Configuration¶
Application Configuration¶
Dapr Configuration (config.yaml):
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: dapr-config
spec:
tracing:
samplingRate: "1"
otel:
endpointAddress: "http://localhost:4317"
metrics:
enabled: true
mTLS:
enabled: true
workloadCertTTL: "24h"
allowedClockSkew: "15m"
Application Startup¶
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add Dapr
builder.Services.AddDapr();
var app = builder.Build();
// Use Dapr
app.UseCloudEvents();
app.MapSubscribeHandler();
await app.RunAsync();
Dapr vs Orleans Comparison¶
Architecture Comparison¶
| Feature | Dapr | Orleans |
|---|---|---|
| Deployment Model | Sidecar (separate process) | Embedded (in-process) |
| Language Support | Polyglot (any language) | .NET only |
| Actor Implementation | Virtual actors | Virtual actors (grains) |
| State Storage | External stores (Redis, Cosmos DB, etc.) | Built-in providers + external |
| Service Discovery | Built-in | Built-in |
| Pub/Sub | Built-in building block | Requires external library |
| Configuration | YAML-based | Code-based |
| Observability | Built-in (OpenTelemetry) | Built-in (OpenTelemetry) |
| Kubernetes | Native support | Requires configuration |
| Development | Sidecar required | In-process only |
When to Use Dapr vs Orleans¶
Use Dapr When: - Building polyglot microservices (multiple languages) - Need sidecar-based architecture - Want to leverage Dapr building blocks (pub/sub, state, bindings) - Deploying primarily on Kubernetes - Need language-agnostic actor model
Use Orleans When: - Building .NET-only applications - Want in-process actor runtime - Need deep .NET integration - Prefer code-based configuration - Want simpler local development (no sidecar)
Integration with Template (Planned)¶
Project Structure¶
ConnectSoft.MicroserviceTemplate.ActorModel/
├── IBankAccountActor.cs (framework-agnostic interface)
ConnectSoft.MicroserviceTemplate.ActorModel.Dapr/
├── BankAccountActor.cs (Dapr implementation)
├── ActorProxyExtensions.cs (Dapr client helpers)
└── DaprActorState.cs (state management)
ConnectSoft.MicroserviceTemplate.ApplicationModel/
├── DaprExtensions.cs (Dapr registration)
└── DaprConfiguration.cs (Dapr configuration)
Service Registration (Planned)¶
// ApplicationModel/DaprExtensions.cs
public static IServiceCollection AddDaprActors(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddDapr();
services.AddActors(options =>
{
// Register actors
options.Actors.RegisterActor<BankAccountActor>();
// Configure from configuration
var daprConfig = configuration.GetSection("Dapr");
options.ActorIdleTimeout = daprConfig.GetValue<TimeSpan>("ActorIdleTimeout");
options.ActorScanInterval = daprConfig.GetValue<TimeSpan>("ActorScanInterval");
});
return services;
}
Middleware Configuration (Planned)¶
// ApplicationModel/DaprExtensions.cs
public static IApplicationBuilder UseDaprActors(
this IApplicationBuilder application)
{
application.UseCloudEvents();
application.MapSubscribeHandler();
// Map actor endpoints
application.MapActorsHandlers();
return application;
}
Dapr Configuration Files¶
Statestore Component¶
File: components/statestore-redis.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
secretKeyRef:
name: redis-secret
key: redis-password
- name: enableTLS
value: "false"
Pub/Sub Component¶
File: components/pubsub-rabbitmq.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: amqp://localhost:5672
- name: consumerID
value: "connectsoft-microservice"
Dapr Configuration¶
File: config.yaml
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: dapr-config
spec:
tracing:
samplingRate: "1"
otel:
endpointAddress: "http://localhost:4317"
metrics:
enabled: true
mTLS:
enabled: true
workloadCertTTL: "24h"
Local Development¶
Running Dapr Locally¶
Install Dapr CLI:
# Windows
powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
# macOS
brew install dapr/tap/dapr-cli
# Linux
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
Initialize Dapr:
Run Application with Dapr:
dapr run --app-id connectsoft-microservice \
--app-port 5000 \
--dapr-http-port 3500 \
--components-path ./components \
dotnet run
Docker Compose Integration (Planned)¶
services:
connectsoft.microservicetemplate.application:
image: connectsoft.microservicetemplate.application
ports:
- "5000:5000"
depends_on:
- dapr-sidecar
environment:
- ASPNETCORE_URLS=http://+:5000
- DAPR_HTTP_PORT=3500
- DAPR_GRPC_PORT=50001
dapr-sidecar:
image: daprio/daprd:latest
command: ["./daprd",
"-app-id", "connectsoft-microservice",
"-app-port", "5000",
"-dapr-http-port", "3500",
"-components-path", "/components"]
volumes:
- ./components:/components
depends_on:
- redis
- rabbitmq
Kubernetes Deployment¶
Dapr Annotations¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: connectsoft-microservice
spec:
template:
metadata:
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "connectsoft-microservice"
dapr.io/app-port: "5000"
dapr.io/config: "dapr-config"
dapr.io/log-level: "info"
spec:
containers:
- name: connectsoft-microservice
image: connectsoft.microservicetemplate.application:latest
Dapr Component in Kubernetes¶
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: "redis-service:6379"
- name: redisPassword
secretKeyRef:
name: redis-secret
key: redis-password
Best Practices¶
Do's¶
-
Use Dapr Building Blocks
-
Configure Components Properly
-
Use CloudEvents for Pub/Sub
-
Handle Actor State Carefully
-
Configure Observability
Don'ts¶
-
Don't Mix Dapr and Direct Access
// ❌ BAD - Mixing Dapr and direct access await daprClient.SaveStateAsync("statestore", "key1", value1); await redisClient.SetAsync("key2", value2); // Inconsistent // ✅ GOOD - Use Dapr consistently await daprClient.SaveStateAsync("statestore", "key1", value1); await daprClient.SaveStateAsync("statestore", "key2", value2); -
Don't Ignore Actor Lifecycle
-
Don't Store Large State in Actors
Troubleshooting¶
Issue: Dapr Sidecar Not Starting¶
Symptoms: Application starts but Dapr sidecar fails.
Solutions:
1. Verify Dapr CLI is installed: dapr --version
2. Check Dapr is initialized: dapr init
3. Verify component files are valid YAML
4. Check Dapr logs: dapr logs
5. Verify ports are available
Issue: Actors Not Found¶
Symptoms: Actor proxy cannot find actor.
Solutions:
1. Verify actor is registered: options.Actors.RegisterActor<MyActor>()
2. Check actor type name matches
3. Verify Dapr sidecar is running
4. Check actor ID format
5. Review Dapr logs for errors
Issue: State Store Not Working¶
Symptoms: State operations fail.
Solutions: 1. Verify component file exists and is valid 2. Check state store connection (Redis, Cosmos DB, etc.) 3. Verify component name matches in code 4. Check Dapr logs for connection errors 5. Verify secrets are configured correctly
Issue: Pub/Sub Not Working¶
Symptoms: Events not published or received.
Solutions: 1. Verify pub/sub component is configured 2. Check topic names match 3. Verify subscription is registered 4. Check broker connection (RabbitMQ, Azure Service Bus, etc.) 5. Review Dapr logs for errors
Summary¶
Dapr in the ConnectSoft Microservice Template (planned) would provide:
- ✅ Polyglot Support: Actor model across multiple languages
- ✅ Sidecar Architecture: Runtime capabilities via sidecar
- ✅ Building Blocks: State, pub/sub, service invocation, actors
- ✅ Cloud-Native: Designed for Kubernetes and containers
- ✅ Observability: Built-in metrics, tracing, and logging
- ✅ Security: mTLS, secrets management, access control
- ✅ Flexibility: Pluggable components for different backends
- ✅ Standards-Based: CloudEvents, OpenTelemetry, etc.
By implementing Dapr, teams could:
- Build Polyglot Systems: Use different languages for different services
- Leverage Building Blocks: Use Dapr's built-in patterns
- Simplify Deployment: Sidecar-based architecture
- Improve Observability: Built-in tracing and metrics
- Enhance Security: Automatic mTLS and secrets management
- Scale Easily: Kubernetes-native scaling
Dapr would complement Orleans by providing a polyglot, sidecar-based alternative for building distributed applications, especially in multi-language environments and Kubernetes deployments.