Claude Code for Dapr: Service Invocation, Pub/Sub, and Distributed State — Claude Skills 360 Blog
Blog / Architecture / Claude Code for Dapr: Service Invocation, Pub/Sub, and Distributed State
Architecture

Claude Code for Dapr: Service Invocation, Pub/Sub, and Distributed State

Published: October 5, 2026
Read time: 8 min read
By: Claude Skills 360

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.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free