Design Patterns And Data Architecture

Pipeline And Middleware Pattern

Pipelines are useful when several cross-cutting steps must run in a deliberate order. Authentication, authorization, rate limiting, request size checks, JSON parsing, CSRF checks, logging, tracing, transactions, validation, and response headers can be modeled as middleware. A pipeline keeps those concerns out of every controller while making the order explicit.

The pattern can also become confusing. If too much behavior hides in middleware, developers cannot tell why a request failed or which step changed state. A good pipeline has clear step names, observable failure behavior, and tests for ordering.

The Pipeline Shape

A pipeline has input, ordered stages, and output. Each stage receives the current input and a way to call the next stage. In HTTP middleware, the input is commonly a request and the output is a response. In a command bus, the input may be a command and the output may be a result.

Middleware can do work before calling the next step, after the next step returns, both, or neither if it rejects early. Authentication middleware may reject before the controller. Logging middleware may record duration after the response is produced. Transaction middleware may begin before the handler and commit after it succeeds.

Order matters. Rate limiting before expensive authentication can reduce load. Authentication before authorization is usually required because authorization needs an actor. Error handling may need to wrap the whole pipeline.

A Small Pipeline Example

This example passes a string through stages. It is not HTTP middleware, but it shows the core flow.

PHP example
<?php

declare(strict_types=1);

/** @param list<callable(string, callable(string): string): string> $stages */
function pipeline(string $input, array $stages, callable $destination): string
{
    $next = array_reduce(
        array_reverse($stages),
        fn (callable $next, callable $stage): callable => fn (string $value): string => $stage($value, $next),
        $destination,
    );

    return $next($input);
}

$stages = [
    fn (string $value, callable $next): string => $next(trim($value)),
    fn (string $value, callable $next): string => $next(strtolower($value)),
];

$result = pipeline('  ADA ', $stages, fn (string $value): string => 'Hello ' . $value);

echo $result . PHP_EOL;

// Prints:
// Hello ada

Each stage receives the value and the next callable. The destination runs after all stages pass control onward.

Middleware In Web PHP

Most modern PHP frameworks use HTTP middleware. Middleware can handle sessions, cookies, authentication, authorization, CSRF protection, trusted proxies, localization, content negotiation, security headers, and request logging.

Middleware is a strong fit when the concern applies to many routes. A controller should not repeat security headers or request logging. The middleware stack can apply them consistently.

Route-specific middleware is also useful. Admin routes may need additional authorization. API routes may need JSON parsing and token authentication. Web routes may need sessions and CSRF protection.

Route Groups And Scope

Middleware scope should be explicit. A session and CSRF stack may belong on browser routes but not on public webhooks. Token authentication may belong on API routes but not on asset requests. Admin authorization may wrap only admin route groups. Applying every middleware to every request can create performance cost, confusing failures, and security exceptions that developers work around later.

Route groups make intent visible. A reviewer can see that web routes use cookies and sessions, API routes use bearer tokens and JSON errors, and health checks avoid expensive dependencies. Keep exceptions narrow. If a route bypasses authentication or CSRF protection, the reason should be obvious from its name, tests, or configuration.

Mutation And Request Attributes

Middleware often annotates a request with derived data: authenticated actor, locale, parsed body, request ID, tenant, or correlation context. That can be useful, but the names and lifetime matter. Later middleware and controllers may depend on those attributes. If two stages write the same key with different meaning, debugging becomes painful.

Prefer typed request attributes or framework-supported context objects where available. Do not store request-specific context in globals or static properties, especially under long-running PHP runtimes. The pipeline should carry state through the request object or an explicit context, not through hidden shared memory.

Pipeline Order

Pipeline order is part of behavior. If body parsing happens after validation, validation may see raw input. If authorization happens before authentication, there may be no known actor. If response compression wraps error handling incorrectly, errors may bypass compression or logging.

Write the order down in configuration and tests. Do not rely on developers remembering framework defaults. When a new middleware is added, review what it must run before and after.

Some middleware should be outermost. Error handling and tracing often wrap the whole stack. Some should be early, such as request size limits. Some should be late, such as response header decoration.

Short-Circuiting

A middleware can short-circuit by returning a response without calling the next step. Rate limiting may return 429. Authentication may return 401. Maintenance-mode middleware may return 503.

Short-circuiting is powerful because it prevents unnecessary work. It is also risky if it bypasses logging, cleanup, or metrics that were expected later. Put logging or tracing middleware outside short-circuiting steps if it must observe every request.

A short-circuit response should be explicit and tested. Silent returns make debugging difficult.

Pipelines Beyond HTTP

Command buses often use middleware for validation, transactions, logging, retries, and authorization. Import pipelines can parse, validate, normalize, enrich, and persist rows. Image pipelines can decode, resize, optimize, store, and publish metadata.

The same design questions apply: what is the input, what is the output, which steps can stop processing, which steps mutate state, and what happens on failure?

Do not force unrelated workflows into one generic pipeline. A CSV import pipeline and an HTTP request pipeline have different evidence, failure, and retry needs.

Transactions In Pipelines

Transaction middleware is attractive because it can wrap every command handler. It is also dangerous if the wrapped work includes external calls that cannot roll back. A transaction middleware should be used only when handlers follow rules: database changes inside, external side effects through outbox or after commit.

If some handlers need a transaction and others do not, make that explicit. Do not wrap read-only commands or long external workflows in database transactions merely because the middleware is convenient.

Tests should prove rollback on handler failure and no premature side effects before commit.

Error Handling And Observability

A pipeline should make failures diagnosable. Log which stage rejected work, which exception category occurred, and how long the pipeline took. Use bounded operation names, not raw payloads.

For HTTP middleware, include route name, status code, request ID, and authenticated actor ID where safe. For command middleware, include command class, result category, attempt count, and duration.

Do not let middleware swallow every exception and return a generic success. That breaks callers and hides incidents.

Debugging A Pipeline

When a pipeline fails, debug it as an ordered chain rather than as one black box. Confirm the route group, list the middleware in effective order, identify which stage first rejects or mutates the input, and inspect the response produced at that point. Framework tools, debug headers in local environments, and structured logs can make this visible.

For production, avoid dumping full request bodies or secrets. Use request IDs, route names, middleware names, status codes, actor IDs where safe, and failure categories. The goal is to make the failing stage discoverable without exposing private data.

Document pipeline assumptions near configuration. Future maintainers should know why JSON parsing runs before validation, why authentication runs before authorization, and why health checks bypass session work.

This documentation is part of the design. Middleware order is executable architecture, and undocumented order becomes guesswork during incidents and reviews.

Keep the chain visible, named, and testable.

That is what makes middleware safer than scattered conditionals.

Common Failure Modes

One failure mode is hidden business logic. Middleware that changes order state or billing state can surprise developers because it runs outside the visible use case.

Another failure mode is order drift. A middleware inserted in the wrong place can break authentication, validation, sessions, or logging.

A third failure mode is over-generalization. A single pipeline abstraction for every workflow can force weak input and output types.

A fourth failure mode is untested short-circuit behavior. A rejected request may bypass cleanup or produce inconsistent error shapes.

Review Criteria

When reviewing a pipeline, ask what each stage owns, whether order is documented, and what stages may short-circuit. Check that shared concerns belong in middleware and feature-specific business rules remain in actions, handlers, or domain services.

Check failure behavior, transaction boundaries, and observability. Check tests for ordering and rejection paths. The pipeline should make cross-cutting behavior clearer, not invisible.

What To Check

Before moving on, make sure you can:

  • explain pipeline and middleware flow
  • identify input, output, stages, destination, and short-circuit behavior
  • choose middleware for cross-cutting route or command concerns
  • keep business workflow out of hidden middleware
  • review and test pipeline order
  • design transaction middleware safely
  • add logging and metrics without swallowing failures

After this lesson, you should be able to inspect a PHP middleware stack or command pipeline and explain exactly which steps run, where rejection can happen, and which behavior belongs elsewhere.

Practice

Task: Design A Middleware Pipeline

Design an API middleware pipeline for authenticated JSON routes.

Requirements

Include request ID, error handling, JSON body parsing, authentication, authorization, rate limiting, controller dispatch, and response headers. State the order.

Check your work

The order should make authentication available before authorization.

Show solution

A reasonable order is request ID, outer error handling, request logging/tracing, request size limit, JSON body parsing, authentication, rate limiting keyed by actor or client, authorization, controller dispatch, and response header decoration.

Authentication must run before authorization because authorization needs an actor. Error handling should wrap most of the stack so failures produce consistent JSON errors. Request ID should be earliest so all logs include it.

Rate limiting can be before or after authentication depending on key choice. If limits are per actor, authenticate first. If limits are per IP for abuse control, an earlier coarse limiter may also be useful.

Task: Review Pipeline Order

A stack runs authorization, controller dispatch, authentication, error handling, and request logging in that order.

Review the order and propose a safer one.

Requirements

Explain what is wrong and which middleware should wrap or precede other steps.

Check your work

The answer should mention that authorization needs an authenticated actor.

Show solution

The order is wrong because authorization runs before authentication, so it has no reliable actor. Error handling runs too late to catch failures from earlier middleware. Logging may miss short-circuited failures.

A safer order is request logging or tracing with request ID near the outside, error handling wrapping the stack, authentication before authorization, then controller dispatch. Response decoration can happen after controller dispatch or in outer middleware after $next() returns.

Tests should cover unauthenticated, unauthorized, successful, and exception paths.

Task: Test Pipeline Behavior

A command bus pipeline has validation, transaction, handler dispatch, and logging middleware.

Plan tests for pipeline order and failure behavior.

Requirements

Include validation failure, handler failure, successful commit, rollback, and logging coverage.

Check your work

The tests should prove middleware order, not only handler output.

Show solution

Use recording middleware or fakes to assert order. Validation failure should stop before transaction and handler dispatch if validation is outside the transaction. Handler failure should trigger rollback and still produce a failure log.

A successful command should begin a transaction, dispatch the handler, commit, and log success with duration and command name. A rollback test should prove no commit happens after handler exception.

Logging tests should include validation rejection and handler failure, not only successful commands. If logging must see every command, it should wrap the stack outside short-circuiting middleware.