Postboy Help

How-to: Attach Middleware to Postboy (Global)

Problem

You want to enable cross-cutting behavior (logging, guards, metrics) by attaching one or more middleware instances to a Postboy bus, so they run for Publish / Callback / Execute operations.

Prerequisites

  • You have a Postboy instance (the “bus”) available in your application runtime.

  • You have a middleware implementation (a class/object that follows the middleware contract: canHandle/before/after/dispose).

  • You know where in your app lifecycle you want middleware to be active (startup, per module, per test, etc.).

Steps

1) Create a middleware instance

Create the middleware object you want to attach.

Guidelines:

  • Keep it single-purpose (one concern per middleware).

  • Prefer stage-first filtering in canHandle to reduce overhead.

2) Attach the middleware to the bus

Attach middleware using the library’s middleware registration mechanism.

Typical pattern:

  • You execute a “registration operation” on the bus (an infrastructure action) that adds the middleware to the processing chain.

Expected outcome:

  • The middleware is now part of the global middleware chain for that bus instance.

  • It will be evaluated for every operation, and you should rely on canHandle to scope it.

3) (Optional) Attach multiple middlewares in a deliberate order

If you attach more than one middleware, define the order intentionally.

Recommended ordering:

  1. Guard/policy middleware (block early when needed)

  2. Tracing/correlation middleware (so everything downstream can reuse the correlation context)

  3. Logging/metrics middleware (depending on whether you want to log attempts, successes, or both)

Why this matters:

  • Middleware is evaluated in registration order.

  • The first middleware that interrupts determines the cancellation diagnostics.

  • Middlewares after an interrupt are not executed for that operation.

4) Decide how middleware lifecycle is managed

Middleware is an object with resources. Decide who owns it and when it is disposed:

  • App-level global middleware:

    • Created at app startup

    • Disposed at app shutdown (or when the bus is disposed)

  • Module-scoped middleware:

    • Attached when the module/registrator activates

    • Removed when the module/registrator deactivates

  • Test middleware:

    • Attached during test Arrange

    • Removed/disposed during test cleanup to avoid cross-test leakage

Verification (expected outcome)

Quick behavioral check

After attaching middleware, trigger any Postboy operation:

  • Publish an event (Publish stage)

  • Fire a callback message (Callback stage)

  • Execute an executor (Execute stage)

You should observe one of the following:

  • Your middleware runs (e.g., logs a message, increments a metric), or

  • It does not run because canHandle correctly filtered it out.

Cancellation check (if you implement a guard)

If your middleware can interrupt:

  • Trigger an operation that should be blocked.

  • Verify that:

    • the handler/subscribers are not invoked

    • the caller receives a cancellation error with clear diagnostics (stage, message id, middleware name, reason)

Troubleshooting

Middleware does not run at all

Most common causes:

  1. The middleware was attached to a different bus instance than the one used by the app code.

  2. canHandle returns false for the stage/message you are testing.

  3. You attached middleware too late (after the operations already happened).

Fix steps:

  • Confirm you attach middleware to the same bus instance used by publishers/executors.

  • Temporarily make canHandle return true and verify you see before calls.

  • Attach middleware during the earliest stable “startup” phase of your runtime.

Middleware runs, but only for some operations

Cause:

  • Stages differ: Publish vs Callback vs Execute.

  • Your stage filter is too strict.

Fix steps:

  • Log the stage value in before while debugging.

  • Adjust canHandle to include the intended stages.

Middleware affects other tests or modules unexpectedly

Cause:

  • Middleware was attached globally and never removed/disposed.

Fix steps:

  • Remove middleware in teardown.

  • Prefer per-test or per-module attachment if you need isolation.

Notes on lifecycle and safety

  • Don’t allocate per-operation resources in before that rely on after for cleanup (because interrupt can skip after).

  • Implement dispose if your middleware owns resources (subscriptions, timers, external handles).

  • Keep canHandle cheap: it runs frequently.

Diagram: where attachment fits in the runtime

@startuml title Attaching middleware to a Postboy bus (runtime overview)

actor App as App participant "Postboy (bus instance)" as Bus participant "Middleware chain" as MW participant "Handlers/Subscribers" as H

App -> Bus: create bus (startup) App -> Bus: attach middleware (registration operation) Bus -> MW: middleware added to chain

App -> Bus: run operations (publish/callback/execute) Bus -> MW: before(stage, message) Bus -> H: handler/subscriber work (if not interrupted) Bus -> MW: after(stage, message, result?)

App -> Bus: shutdown / teardown Bus -> MW: dispose middleware (when removed or bus disposed)

@enduml

Next steps

  • [Middleware lifecycle and contract (before/after/canHandle/dispose)]

  • [Middleware stages: Publish / Callback / Execute]

  • [Interrupt/cancel semantics: what happens when middleware blocks an operation]

  • [How-to: filter middleware to specific messages]

  • [How-to: safely dispose and avoid leaks]

29 июня 2026