Chronicle.Middleware behaviour (cratis_chronicle v3.16.1)

Copy Markdown View Source

Wraps the handling of an event by a reactor or reducer, for cross-cutting concerns: tracing, metrics, logging, impersonating the identity that caused the event, tenant scoping, transactional wrapping. Mirrors the .NET client's reactor middleware.

A middleware receives the event, its Chronicle.Events.EventContext and next, a function that continues down the pipeline (finally reaching handle/2 or reduce/3). It returns what the handler would return - call next and return its result, or return a result of its own to short-circuit:

defmodule MyApp.Middleware.Timing do
  @behaviour Chronicle.Middleware
  require Logger

  @impl true
  def call(event, context, next) do
    {micros, result} = :timer.tc(fn -> next.(event, context) end)
    Logger.info("handled #{inspect(event.__struct__)} in #{micros}us")
    result
  end
end

Middleware runs in the order listed, the first being the outermost. Configure it for every reactor and reducer of the application:

config :cratis_chronicle, middleware: [MyApp.Middleware.Timing]

or for one module:

use Chronicle.Reactors.Reactor, middleware: [MyApp.Middleware.Impersonate]
use Chronicle.Reducers.Reducer, model: MyApp.Account, middleware: [MyApp.Middleware.Timing]

Global middleware is the outer layer, module middleware the inner. An exception raised by a middleware or the handler is handled like one raised by the handler: the partition fails.

For a reducer, next returns the reduced model, so a middleware that short-circuits returns a model (typically the one it can see through the context's current state - or simply calls next). Prefer calling next in reducer middleware.

Summary

Functions

The middleware that applies to module: global middleware, then the module's own.

Runs final (a two-arity function of event and context) through middleware, outermost first.

Callbacks

call(event, context, next)

@callback call(event :: struct(), context :: map(), next :: (struct(), map() -> term())) ::
  term()

Functions

for_module(module, kind)

@spec for_module(module(), :reactor | :reducer) :: [module()]

The middleware that applies to module: global middleware, then the module's own.

run(list, event, context, final)

@spec run([module()], struct(), map(), (struct(), map() -> term())) :: term()

Runs final (a two-arity function of event and context) through middleware, outermost first.