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
canHandleto 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
canHandleto scope it.
3) (Optional) Attach multiple middlewares in a deliberate order
If you attach more than one middleware, define the order intentionally.
Recommended ordering:
Guard/policy middleware (block early when needed)
Tracing/correlation middleware (so everything downstream can reuse the correlation context)
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
canHandlecorrectly 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:
The middleware was attached to a different bus instance than the one used by the app code.
canHandlereturns false for the stage/message you are testing.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
canHandlereturn true and verify you seebeforecalls.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
beforewhile debugging.Adjust
canHandleto 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
beforethat rely onafterfor cleanup (because interrupt can skipafter).Implement
disposeif your middleware owns resources (subscriptions, timers, external handles).Keep
canHandlecheap: 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]