← Back to scenarios
ScenarioCatalog DB

HTTP client

Wrap an HTTP client with caching and metrics without changing its request interface.

Problem

An application must add optional cross-cutting behavior such as caching and metrics around HTTP requests while keeping callers dependent on one stable client interface.

Related patterns

Implementations

Decorator

TypeScriptApplication

Decorator in TypeScript

Adds logging and retry behavior to a notification sender by composing wrappers that preserve the same sending interface.

TypeScript
interface NotificationSender {
  send(recipient: string, message: string): Promise<void>;
}

class EmailSender implements NotificationSender {
  async send(recipient: string, message: string) {
    console.log(`Sending email to ${recipient}: ${message}`);
  }
}

abstract class NotificationSenderDecorator
  implements NotificationSender
{
  constructor(
    protected readonly sender: NotificationSender,
  ) {}

  send(recipient: string, message: string) {
    return this.sender.send(recipient, message);
  }
}

class LoggingSender extends NotificationSenderDecorator {
  async send(recipient: string, message: string) {
    console.log(`Starting notification for ${recipient}`);

    await super.send(recipient, message);

    console.log(`Completed notification for ${recipient}`);
  }
}

class RetryingSender extends NotificationSenderDecorator {
  async send(recipient: string, message: string) {
    try {
      await super.send(recipient, message);
    } catch (error) {
      console.warn("First attempt failed; retrying once.");
      await super.send(recipient, message);
    }
  }
}

const sender = new LoggingSender(
  new RetryingSender(new EmailSender()),
);

void sender.send("team@example.com", "Deployment completed.");

Associated technologies