Postboy Help

Middleware Recipes: Copy-Safe Patterns (Logging / Guards / Validation / Metrics)

Summary

This page contains “recipes”: small, practical middleware patterns you can copy and adapt. Each recipe includes:

  • context (when it’s useful),

  • assumptions,

  • tradeoffs,

  • common mistakes.

Recipes are intentionally framework-agnostic. They focus on Postboy stages (Publish / Callback / Execute) and the middleware contract (canHandle / before / after / dispose).

Recipe 1: Stage-first structured logging (“attempt” + “success”)

Context

You want consistent logs for operations without touching handlers. You want to correlate attempt and completion.

Assumptions

  • You have a logger that can log structured fields (key/value).

  • You can derive a stable message label (type name/category).

Pattern

  • In canHandle: filter to relevant stages and message subset.

  • In before: log an “attempt” entry.

  • In after: log a “success” entry.

  • Do not log payloads by default.

Tradeoffs

  • You will not automatically log cancellations in after (because after does not run on cancel). If you need cancellation logs, log attempts in before and/or log at the cancellation boundary.

Common mistakes

  • Logging full message objects (performance + secrets).

  • Not filtering by stage/message type, causing noisy logs.

Recipe 2: Guard middleware (“policy gate” using Interrupt)

Context

You must block certain operations globally (read-only mode, feature flag off, authorization policy).

Assumptions

  • You can decide “allowed vs blocked” synchronously in middleware.

  • You have a clear, stable reason string or policy id for diagnostics.

Pattern

  • In canHandle: narrow to the stage(s) you intend to guard (often Execute and/or Callback).

  • In before: evaluate policy and return Interrupt when violated.

Tradeoffs

  • Guards are powerful and can surprise users if overused. Keep scope narrow and diagnostics clear.

  • Order matters: the first guard that interrupts “wins”.

Common mistakes

  • Guarding Publish stage broadly (can create hidden inconsistencies).

  • Making guard decisions depend on slow I/O or mutable global state without clear control.

Recipe 3: “Allowlist” middleware for sensitive executors

Context

You want to run only a known-safe set of synchronous operations in a restricted environment (e.g., production maintenance mode).

Assumptions

  • You can classify executors by a stable label (type name, category, explicit tag).

Pattern

  • Stage filter: Execute only.

  • Message filter: if executor label not in allowlist → Interrupt.

Tradeoffs

  • Requires keeping the allowlist in sync with the codebase.

  • Better for small/critical surfaces than for large, fast-changing domains.

Common mistakes

  • Using a high-cardinality or unstable label for the allowlist key.

  • Failing closed without proper diagnostics (“why was this blocked?”).

Recipe 4: Validation middleware (“fast reject” for obvious invalid input)

Context

You want to reject clearly invalid messages early (missing required fields, invalid enum values) in a consistent way.

Assumptions

  • Validation is purely syntactic/structural (not business workflow validation).

  • You can validate synchronously.

Pattern

  • Filter to the message subset you want to validate (usually commands/queries).

  • In before: run validation checks.

    • If invalid: Interrupt (and rely on cancellation diagnostics).

    • If valid: Continue.

Tradeoffs

  • If you need rich, typed validation errors as part of domain responses, consider handler-level validation instead of cancelling.

  • Cancellation is “all-or-nothing” and does not execute handlers.

Common mistakes

  • Putting domain rules into middleware (turns into hidden business logic).

  • Using validation middleware as a substitute for API-layer validation where user feedback must be explicit.

Recipe 5: Metrics middleware (attempts + latency for Execute)

Context

You want stable dashboards: operation volume and latency for synchronous executors.

Assumptions

  • You have a metrics sink (Prometheus/OpenTelemetry/statsd/etc.).

  • You can derive a stable message label.

Pattern

  • In canHandle: filter to Execute stage (and optionally a message subset).

  • In before:

    • increment attempts counter

    • store start time (keyed by message id, bounded)

  • In after:

    • record duration

    • increment success counter

Tradeoffs

  • Cancelled operations won’t reach after.

  • Avoid metric labels with high cardinality (never use message id as a metric label).

Common mistakes

  • Unbounded maps for start timestamps (leaks when after isn’t called).

  • Too many labels, causing cardinality explosion.

Recipe 6: “Trace span per operation” (thin tracing adapter)

Context

You want distributed tracing around Postboy operations.

Assumptions

  • Your app has a trace context mechanism.

  • You can start/end spans synchronously around the bus boundary (especially for Execute).

Pattern

  • In before:

    • start a span with tags: stage, message label, message id

  • In after:

    • mark span success and end

For Callback stage, explicitly define what you trace:

  • request accepted vs request completed

Tradeoffs

  • End-to-end callback completion tracing may require hooking into callback completion signals beyond simple before/after boundaries.

Common mistakes

  • Treating Callback “after” as always representing completion without verifying semantics.

  • Not propagating trace context across async boundaries (spans become disconnected).

Recipe 7: Resource-safe middleware template (cleanup-first design)

Context

Your middleware allocates resources (subscriptions, timers, caches) and must not leak.

Assumptions

  • You can keep long-lived resources under middleware ownership.

Pattern

  • Allocate long-lived resources once (constructor or first use).

  • Release them in dispose.

  • Keep per-operation state bounded; do not rely on after for cleanup.

Tradeoffs

  • Slightly more boilerplate, much safer behavior.

Common mistakes

  • Creating subscriptions in before repeatedly (leaks and duplicates).

  • Clearing per-operation state only in after (breaks on cancellation).

Recipe 8: Noise control (“only log slow operations”)

Context

You want useful logs without spamming: record only operations slower than a threshold.

Assumptions

  • You measure latency (typically Execute stage).

Pattern

  • Record start time in before.

  • In after compute duration and log only if duration >= threshold.

Tradeoffs

  • You only see slow successes; cancellations and failures need separate visibility.

Common mistakes

  • Using this as the only observability (you lose visibility into frequent small errors/cancels).

  • Not filtering by message subset, resulting in slow-log overload.

Diagram: composing recipes in a safe chain

@startuml title Example middleware chain: tracing + guard + metrics + logging

actor Caller participant "Postboy" as Bus participant "Tracing MW" as Trace participant "Guard MW" as Guard participant "Metrics MW" as Metrics participant "Logging MW" as Log participant "Handlers/Subscribers" as H

Caller -> Bus: start operation Bus -> Trace: before() Bus -> Guard: before() alt Guard interrupts Bus --> Caller: cancelled else continues Bus -> Metrics: before() Bus -> Log: before() Bus -> H: run operation H --> Bus: success Bus -> Trace: after() Bus -> Metrics: after() Bus -> Log: after() Bus --> Caller: completed end

@enduml

Best practices (recipes edition)

  • Prefer one concern per middleware (guard vs logging vs metrics).

  • Filter aggressively in canHandle (stage-first, then message subset).

  • Treat cancellations as a first-class outcome:

    • decide whether to log attempts

    • decide where to count cancels

  • Keep diagnostics stable (middleware name + reason strings that are searchable).

  • Avoid high-cardinality metric labels (never label by message id).

Next steps

  • [How-to: attach middleware to Postboy]

  • [How-to: enable middleware only for a subset of messages]

  • [How-to: block execution (guard middleware)]

  • [How-to: logging and tracing messages]

  • [How-to: measure duration and collect metrics]

  • [Best practices and anti-patterns for middleware]

29 июня 2026