← Back to scenarios
ScenarioCatalog DB

Approval workflow chain

Pass an expense request through approvers according to amount and policy constraints.

Problem

An expense workflow must route requests through the necessary approvers based on changing limits and policies while avoiding a caller that is tightly coupled to every approval level.

Related patterns

Implementations

Chain of Responsibility

TypeScriptApplication

Chain of Responsibility in TypeScript

Routes a support request through specialized handlers until one can resolve it, without coupling the requester to a specific handler.

TypeScript
type SupportRequest = {
  kind: "billing" | "technical" | "account";
  message: string;
};

abstract class SupportHandler {
  private next?: SupportHandler;

  setNext(handler: SupportHandler): SupportHandler {
    this.next = handler;
    return handler;
  }

  handle(request: SupportRequest): void {
    if (this.canHandle(request)) {
      this.resolve(request);
      return;
    }

    if (this.next) {
      this.next.handle(request);
      return;
    }

    console.log(`No handler found for: ${request.message}`);
  }

  protected abstract canHandle(request: SupportRequest): boolean;
  protected abstract resolve(request: SupportRequest): void;
}

class BillingHandler extends SupportHandler {
  protected canHandle(request: SupportRequest) {
    return request.kind === "billing";
  }

  protected resolve(request: SupportRequest) {
    console.log(`Billing resolved: ${request.message}`);
  }
}

class TechnicalHandler extends SupportHandler {
  protected canHandle(request: SupportRequest) {
    return request.kind === "technical";
  }

  protected resolve(request: SupportRequest) {
    console.log(`Technical support resolved: ${request.message}`);
  }
}

class AccountHandler extends SupportHandler {
  protected canHandle(request: SupportRequest) {
    return request.kind === "account";
  }

  protected resolve(request: SupportRequest) {
    console.log(`Account support resolved: ${request.message}`);
  }
}

const billing = new BillingHandler();
const technical = new TechnicalHandler();
const account = new AccountHandler();

billing.setNext(technical).setNext(account);

billing.handle({
  kind: "technical",
  message: "I cannot sign in to the dashboard.",
});

Associated technologies