Middleware: Mental Model and Its Place in Postboy Architecture
Summary
Middleware in Postboy is an infrastructure mechanism that lets you intercept message processing at well-defined pipeline stages. It is designed for cross-cutting concerns (logging, metrics, access control, policy checks) without coupling those concerns to message handlers.
When to use / When not to use
Use Middleware when you need
Cross-cutting behavior applied to many message types (e.g., logging, tracing, metrics).
Centralized policy enforcement (e.g., block certain messages in specific environments).
Observability around message lifecycle (measure timing, record outcomes).
Non-invasive instrumentation: add behavior without touching handlers.
Do not use Middleware when you need
Business logic that is part of a specific use case (put it in the handler/executor).
Message transformation / routing rules that should be explicit in your domain flow.
Framework lifecycle integration (that belongs in ecosystem docs; core middleware should remain framework-agnostic).
Hidden dependencies: middleware that silently changes system behavior without clear ownership or tests.
Core idea: where Middleware sits
Postboy can be viewed as a bus that processes messages through a small “pipeline”:
A message is published/executed.
The bus invokes middleware hooks around the operation.
The actual message delivery/handler execution happens.
Middleware can observe the result and optionally interrupt (cancel) the operation before it runs.
Architectural placement
Think of middleware as a thin infrastructure layer around the bus, not around individual handlers.
Handlers implement “what to do” for a specific message type.
Middleware implements “what always needs to happen around message processing”.
Registrators / namespaces / lifecycle tools decide “when registrations exist” and “what gets cleaned up”.
A good mental model: middleware is to Postboy what interceptors are to an RPC pipeline or what “middleware” is to HTTP servers—except Postboy operates on typed messages and has multiple stages (publish/callback/execute).
A simple flow diagram (conceptual)
Vocabulary and boundaries
Message
A typed object representing an intent (event, query, command, synchronous executor). The message is the unit middleware observes and can guard.
Stage
A named point in the processing lifecycle where middleware can run. Stages let you apply different policies to different kinds of operations.
Pipeline context
A small data object passed to middleware hooks. It lets middleware inspect:
the current stage
the current message instance
Middleware should treat the context as read-only: inspection and decision-making are the primary responsibilities.
Middleware decision
The middleware can:
Continue: allow processing to proceed.
Interrupt: stop the operation before it reaches handlers/subscribers.
How Middleware differs from other Postboy mechanisms
Middleware vs Handlers
Handler: owns the domain behavior for a specific message type.
Middleware: owns cross-cutting infrastructure concerns across many message types.
Rule of thumb:
If it must be true for one message type → handler.
If it must be true for many message types → middleware.
Middleware vs RxJS operators (pipes)
RxJS pipes shape observable streams (filtering, mapping, retrying) usually within a specific subscription flow. Middleware shapes the execution boundary at the bus level and is not tied to any single subscription.
Middleware vs Registrators / Namespaces / lifecycle
Registrators and namespaces control setup and teardown (what is registered, when subscriptions exist). Middleware controls runtime interception (what happens during processing).
Example (minimal, conceptual; not code)
Imagine you want to ensure “no destructive commands run in a read-only environment”.
A guard middleware can:
Check the stage (e.g., only enforce for execute-like operations).
Check the message type/category (e.g., “DeleteUserCommand” or “DangerousExecutor”).
Interrupt the pipeline with a cancellation reason.
Handlers remain clean and focused, and the policy is centralized.
Pitfalls / Troubleshooting
1) Putting domain logic into middleware
Symptom: system behavior becomes hard to reason about because business rules run “invisibly” before handlers. Fix: move business decisions to handlers, keep middleware for infrastructure policies and observability.
2) Overly broad middleware (canHandle always true)
Symptom: performance overhead, noisy logs, hard-to-debug cancellations. Fix: use narrow filtering rules; scope middleware to stages and message categories.
3) Order-dependent behavior without documentation
Symptom: swapping middleware registration order changes system behavior. Fix: document ordering expectations and keep critical policies early in the chain.
Best practices (short)
Keep middleware small and single-purpose.
Make filtering explicit: stage + message category/type checks.
Treat middleware as infrastructure: log, measure, enforce policy; avoid “doing work” that belongs to handlers.
Prefer transparency: when middleware interrupts, ensure the reason is diagnosable.