Postboy Help

Middleware in v3.5+: What Changed and How to Adopt It Safely

Summary

Starting with Postboy v3.5, the library introduces middleware: a pipeline interception mechanism around message processing. Middleware enables cross-cutting behaviors—logging, tracing, metrics, and policy enforcement—without modifying individual handlers/subscribers.

This page explains:

  • what middleware adds to the system,

  • what you should consider when upgrading/adopting it,

  • and safe rollout steps.

Applies to

  • Postboy v3.5 and newer.

If you are on an older version line, middleware APIs are not available.

What changed in v3.5+

New capability: pipeline interception

Middleware introduces a consistent “around” hook model for message processing:

  • filter: canHandle(context)

  • pre-hook: before(context) → Continue / Interrupt

  • post-hook: after(context, result?) (success-only)

  • cleanup: dispose()

This enables system-wide concerns to be implemented once and applied broadly.

New vocabulary: stages

Middleware executes per operation stage:

  • Publish

  • Callback

  • Execute

Stage is a primary scoping dimension for middleware.

New failure mode: cancellation by middleware

Middleware can explicitly cancel an operation by returning Interrupt in before. This is a deliberate control-flow feature for policies and guards.

Cancellation:

  • happens before handler/subscriber execution,

  • short-circuits the middleware chain,

  • raises a cancellation error with diagnostic metadata (stage, message id, middleware name, reason).

Compatibility and behavior notes (what to watch for)

1) Middleware changes “what can stop an operation”

Before v3.5, your system’s “stop points” were typically:

  • handler/subscriber errors,

  • missing registrations,

  • application-level checks.

In v3.5+, middleware can stop execution earlier, before handlers see the message. This is powerful for policy enforcement, but it also introduces a new class of issues if a guard is too broad.

2) after() is not guaranteed

The after hook runs only after a successful completion. It does not run on cancellations and may not run on failures depending on where failures occur.

If you add middleware that stores per-operation state (timing, correlation maps), ensure it cannot leak if after does not run.

3) Ordering becomes part of runtime behavior

Middleware runs in registration order. The first Interrupt “wins”. If you attach multiple guard or observability middleware instances, ordering affects:

  • which middleware cancels,

  • whether attempt logs exist for cancelled operations,

  • which spans/metrics are recorded.

Adoption guide (safe rollout steps)

Step 1: Start with “read-only” middleware

First middleware should be passive (no Interrupt):

  • structured logging (attempt + success),

  • metrics (attempts + success latencies),

  • tracing (start/end spans for Execute stage).

Why:

  • You learn how stages map to your app’s operations.

  • You can confirm middleware wiring without changing behavior.

Step 2: Scope aggressively (stage-first)

Use canHandle to keep blast radius small:

  • start with Execute only

  • then expand to Callback if you need request-level visibility

  • use message-type/category filters to avoid instrumenting internal/infrastructure operations (if any)

Step 3: Add guards only when you have diagnostics

Before introducing Interrupt-based policies, ensure:

  • middleware names are stable and searchable

  • cancellation reasons are short and safe

  • you have logs/metrics to see “attempts” vs “successes” vs “cancellations”

Step 4: Roll out policies in “monitor mode” first (optional)

If you are unsure about the impact of a policy:

  • log “would interrupt” decisions without actually interrupting

  • confirm the set of affected operations

  • then enable Interrupt

Step 5: Update tests and teardown practices

If you attach middleware in tests:

  • ensure middleware is removed/disposed between tests

  • avoid global attachment unless your harness resets the bus

If you add timing/correlation maps:

  • add tests ensuring they do not grow unbounded (especially under cancellation scenarios).

Migration notes (from pre-3.5 thinking)

Where previous patterns map now

  • Ad-hoc logging inside handlers → move to logging middleware.

  • Scattered feature-flag checks → consolidate in guard middleware (Execute/Callback).

  • Cross-cutting metrics in each handler → centralize in metrics middleware.

What should remain in handlers

  • domain workflow logic,

  • domain validation that must return typed responses,

  • use-case-specific orchestration.

Middleware is infrastructure, not domain.

Diagram: pre-v3.5 vs v3.5+ flow (conceptual)

@startuml title Pre-v3.5 vs v3.5+ (conceptual)

actor Caller participant "Postboy" as Bus participant "Middleware chain" as MW participant "Handlers/Subscribers" as H

== Pre-v3.5 == Caller -> Bus: start operation Bus -> H: run handler/subscribers H --> Caller: result/stream

== v3.5+ == Caller -> Bus: start operation Bus -> MW: before (can interrupt) alt interrupted Bus --> Caller: cancelled (error) else continue Bus -> H: run handler/subscribers Bus -> MW: after (success-only) Bus --> Caller: result/stream end

@enduml

Common upgrade pitfalls (and fixes)

Pitfall: “New middleware broke production by blocking messages”

Cause:

  • guard middleware canHandle too broad or stage misunderstood.

Fix:

  • stage-first scoping

  • add message-type filters

  • add “monitor mode” before enabling Interrupt

Pitfall: “Metrics/logging missing for cancelled operations”

Cause:

  • you only record in after.

Fix:

  • record attempts in before

  • place observability middleware before guards if you want to see cancelled attempts

Pitfall: “Memory usage increased after adding timing middleware”

Cause:

  • per-operation maps cleaned only in after.

Fix:

  • use bounded state (TTL/LRU)

  • do not assume after always runs

  • clean long-lived resources in dispose

Next steps

  • [Middleware: overview]

  • [Middleware lifecycle and contract]

  • [Middleware stages]

  • [Decision & Cancellation reference]

  • [Best practices]

  • [Troubleshooting]

29 июня 2026