Dapr (Distributed Application Runtime) provides building blocks for distributed systems as a sidecar — your application calls a local HTTP/gRPC API, and Dapr handles the distributed systems concerns: service discovery, retries, pub/sub brokers, state store backends, and secret stores. Swap the underlying infrastructure (Redis to Cosmos DB, Kafka to RabbitMQ) by changing a YAML component file, not application code. Claude Code configures Dapr components, implements the invocation and pub/sub patterns, and sets up distributed tracing.
CLAUDE.md for Dapr Projects
## Dapr Runtime
- SDK: @dapr/dapr (Node.js), dapr-client (Python), or HTTP API directly
- Sidecar port: 3500 (HTTP), 50001 (gRPC)
- App port: defined in dapr run --app-port
- State store: statestore (component name in local dev: Redis; prod: Azure Cosmos DB)
- Pub/sub: pubsub (component: Redis streams locally; Azure Service Bus in prod)
- Secret store: secretstore (local: local file; prod: Azure Key Vault)
- Tracing: Zipkin locally, Azure Monitor in prod
- All Dapr operations fail fast — handle them defensively
Dapr Components (YAML)
# components/statestore.yaml — development (Redis)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: actorStateStore
value: "true"
---
# components/pubsub.yaml — development (Redis Streams)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
---
# components/secretstore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: secretstore
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: ./components/secrets.json
# Kubernetes: production components (Azure)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
namespace: production
spec:
type: state.azure.cosmosdb
version: v1
metadata:
- name: url
secretKeyRef:
name: cosmos-secret
key: url
- name: masterKey
secretKeyRef:
name: cosmos-secret
key: key
- name: database
value: orders
- name: collection
value: state
Service Invocation
Call the inventory service from the order service.
Use Dapr service invocation for retries and mTLS.
// src/services/orderService.ts
import { DaprClient } from '@dapr/dapr';
const dapr = new DaprClient({
daprHost: process.env.DAPR_HOST ?? 'localhost',
daprPort: process.env.DAPR_HTTP_PORT ?? '3500',
});
// Dapr handles: service discovery, retries (3x by default), mTLS between sidecars
export async function reserveInventory(
items: OrderItem[],
): Promise<{ reserved: boolean; reservationId: string }> {
try {
const response = await dapr.invoker.invoke(
'inventory-service', // Target app-id (matches --app-id in dapr run)
'reserve', // Method path
HttpMethod.POST,
{ items },
);
return response;
} catch (err) {
// Dapr returns 500 if the target service is unreachable after retries
throw new Error(`Inventory reservation failed: ${err.message}`);
}
}
// With resiliency policy (Dapr 1.7+): define retry/circuit breaker in YAML
// components/resiliency.yaml
# components/resiliency.yaml — declarative retry and circuit breaker
apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
name: myresiliency
spec:
policies:
retries:
retryThreeTimes:
policy: constant
duration: 5s
maxRetries: 3
circuitBreakers:
simpleCB:
maxRequests: 1
interval: 8s
timeout: 45s
trip: consecutiveFailures >= 5
targets:
services:
inventory-service:
outbound:
retry: retryThreeTimes
circuitBreaker: simpleCB
Pub/Sub Messaging
Publish an order.placed event when an order is created.
Other services subscribe to process it.
// Publisher: order service
import { DaprClient } from '@dapr/dapr';
const dapr = new DaprClient();
export async function publishOrderPlaced(order: Order) {
await dapr.pubsub.publish(
'pubsub', // Component name
'orders', // Topic name
{
orderId: order.id,
customerId: order.customerId,
items: order.items,
totalCents: order.totalCents,
occurredAt: new Date().toISOString(),
},
);
}
// Subscriber: notification service
import { DaprServer } from '@dapr/dapr';
const server = new DaprServer({
serverHost: '127.0.0.1',
serverPort: process.env.APP_PORT ?? '3001',
clientOptions: {
daprHost: '127.0.0.1',
daprPort: process.env.DAPR_HTTP_PORT ?? '3500',
},
});
// Dapr POSTs to this handler when a message arrives on the topic
await server.pubsub.subscribe('pubsub', 'orders', async (data) => {
const event = data as OrderPlacedEvent;
try {
await sendOrderConfirmationEmail(event.customerId, event.orderId);
// Return nothing = success (Dapr ACKs the message)
} catch (err) {
// Throw = Dapr will redeliver (NACK)
throw err;
}
});
await server.start();
# subscription.yaml — declarative subscription (alternative to code)
apiVersion: dapr.io/v2alpha1
kind: Subscription
metadata:
name: order-notification-subscription
spec:
pubsubname: pubsub
topic: orders
routes:
default: /orders/notification
deadLetterTopic: orders-dlq
# Scoped to specific app-id
scopes:
- notification-service
State Management
// src/services/cartService.ts — Dapr state store
// Consistent key-value API regardless of backend (Redis, Cosmos, DynamoDB)
export async function saveCart(userId: string, cart: Cart): Promise<void> {
await dapr.state.save('statestore', [
{
key: `cart-${userId}`,
value: cart,
options: {
concurrency: 'last-write',
consistency: 'eventual',
},
metadata: {
ttlInSeconds: '3600', // 1-hour expiry
},
},
]);
}
export async function getCart(userId: string): Promise<Cart | null> {
const result = await dapr.state.get('statestore', `cart-${userId}`);
return result ?? null;
}
// Optimistic concurrency with etag
export async function updateCartOptimistic(userId: string, update: Partial<Cart>) {
const { data: current, etag } = await dapr.state.getStateAndEtag('statestore', `cart-${userId}`);
if (!current) return null;
const updated = { ...current, ...update };
const success = await dapr.state.saveWithEtag('statestore', {
key: `cart-${userId}`,
value: updated,
etag, // Will fail if someone else modified it since our read
options: { concurrency: 'first-write' },
});
if (!success) throw new Error('Cart was modified by another request — retry');
return updated;
}
Secrets Management
// Retrieve secrets from the Dapr secret store
// Same code works locally (file), on K8s (Vault/Key Vault), on AWS (Secrets Manager)
export async function getStripeKey(): Promise<string> {
const secrets = await dapr.secret.get('secretstore', 'stripe-secret-key');
return secrets['stripe-secret-key'];
}
// Bulk secret retrieval
export async function getAllSecrets(): Promise<Record<string, string>> {
return dapr.secret.getBulk('secretstore');
}
Local Development
# Run both your app and the Dapr sidecar together
dapr run \
--app-id order-service \
--app-port 3000 \
--dapr-http-port 3500 \
--components-path ./components \
--log-level debug \
-- node dist/server.js
# Multi-service local setup with Docker Compose + Dapr
# (Each service has its own dapr run command)
# Invoke a service locally (for testing)
dapr invoke --app-id inventory-service --method reserve --verb POST \
--data '{"items":[{"sku":"ABC","qty":1}]}'
# Publish a message manually
dapr publish --publish-app-id order-service --pubsub pubsub --topic orders \
--data '{"orderId":"test-123"}'
# Check component status
dapr components --app-id order-service
Dapr Workflow (Orchestration)
// Dapr workflows: durable orchestration similar to Temporal
import { WorkflowRuntime, DaprWorkflowClient } from '@dapr/dapr';
// Define the workflow
async function orderWorkflow(ctx: WorkflowContext, input: OrderInput): Promise<OrderResult> {
// Activities are retried automatically on failure
const inventory = await ctx.callActivity(reserveInventoryActivity, input.items);
if (!inventory.reserved) {
return { success: false, reason: 'Out of stock' };
}
try {
const payment = await ctx.callActivity(processPaymentActivity, {
customerId: input.customerId,
amountCents: input.totalCents,
});
await ctx.callActivity(fulfillOrderActivity, {
orderId: ctx.instanceId,
reservationId: inventory.reservationId,
});
return { success: true, orderId: ctx.instanceId };
} catch {
// Compensate
await ctx.callActivity(releaseInventoryActivity, inventory.reservationId);
return { success: false, reason: 'Payment failed' };
}
}
// Start a workflow instance
const client = new DaprWorkflowClient();
const instanceId = await client.scheduleNewWorkflow(orderWorkflow, orderInput);
const result = await client.waitForWorkflowCompletion(instanceId, undefined, 30);
For the event-driven patterns that Dapr pub/sub enables at scale, see the event sourcing guide. For the Kubernetes deployment configuration for Dapr-enabled services, the zero-downtime deployments guide covers rolling update strategies. The Claude Skills 360 bundle includes distributed systems skill sets covering Dapr building blocks, pub/sub patterns, and workflow orchestration. Start with the free tier to try Dapr component configuration.