/ Docs

Policies

What is a Policy?

A policy reacts to an event and triggers a side effect. Policies are the mechanism for responding to domain events with actions such as dispatching commands, calling external services, or initiating workflows. They decouple the event producer from downstream consequences.

Policies in Bounda

Policies live inside domain/<aggregate>/policies/ and follow the naming convention <action>-on-<event-name>.ts. This makes it clear which event triggers the policy and what action it performs.

domain/
  order/
    policies/
      send-confirmation-on-order-placed.ts
      reserve-inventory-on-order-placed.ts
      notify-warehouse-on-order-confirmed.ts

Each policy file exports a handler function.

Defining a Policy

import type { Policy } from "./+types/send-confirmation-on-order-placed";

export async function handler({ event, commands }: Policy.HandlerArgs) {
  await commands.sendConfirmation({
    aggregateId: event.aggregateId,
    correlationId: event.metadata?.correlationId,
    causationId: event.id,
  });
}

The handler receives:

ArgumentDescription
eventThe full event object that triggered this policy
commandsTyped command dispatchers for issuing new commands

Naming Convention

The file name encodes both the action and the triggering event:

<what-to-do>-on-<event-name>.ts

Examples:

  • send-confirmation-on-order-placed.ts
  • reserve-inventory-on-order-placed.ts
  • update-shipping-on-address-changed.ts

This convention makes the policy’s purpose immediately visible from the file name alone.

Resilience Configuration

Policies may fail due to transient errors (network issues, service outages). Bounda provides retry and dead letter queue (DLQ) configuration in bounda.config.ts:

policies: {
  retry: {
    retryStrategy: "exponential",
    maxRetries: 3,
  },
  maxChainDepth: 5,
  timeout: 30000,
}

Retry Strategies

StrategyDescription
noneNo retries; fail immediately
fixedRetry at a constant interval
linearIncrease delay linearly between retries
exponentialDouble the delay between each retry

Configuration Options

OptionDefaultDescription
retryStrategy"exponential"Which retry strategy to use
maxRetries3Maximum number of retry attempts
timeout30000Maximum execution time per attempt in milliseconds
maxChainDepth25Maximum depth of policy-to-command-to-policy chains

Note: maxChainDepth prevents infinite loops where a policy dispatches a command that emits an event that triggers another policy, and so on.

Dead Letter Queue

When a policy exhausts all retries, the failed event is placed in a dead letter queue (DLQ) for later inspection and replay.

policies: {
  retry: {
    retryStrategy: "exponential",
    maxRetries: 3,
  },
  maxChainDepth: 5,
  dlq: {
    type: "sqlite",
    path: "./db/policies-dlq.db",
  },
}

DLQ entries can be inspected and replayed using the Bounda CLI. See CLI for details on managing the dead letter queue.

Guidelines

  • Keep policies focused on a single responsibility. If a policy does multiple unrelated things, split it into separate policies.
  • Use correlationId and causationId when dispatching commands from policies to maintain traceability across the event chain.
  • Policies should be idempotent when possible, since retries may cause a handler to execute more than once for the same event.
  • Commands — policies dispatch commands to perform further actions
  • Events — policies are triggered by events
  • Process Managers — for multi-step workflows that span multiple events