Skip to content

Instantly share code, notes, and snippets.

@ikitommi
Last active October 20, 2018 09:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ikitommi/9d4663fdc705b873c3914e6cd8a5daf6 to your computer and use it in GitHub Desktop.
Save ikitommi/9d4663fdc705b873c3914e6cd8a5daf6 to your computer and use it in GitHub Desktop.
Interceptors

Interceptors

Some Clojure/Script implementations:

Problems

  • each work bit differently (:enter vs :before)
  • there is no Interceptor Spec (like there is RING SPEC)
  • Different async models: core.async, manifold & callbacks.
  • Pedestal is clj only, opinionated towards core.async, uses pedestal-spesific keys, lot's of extra features yielding some overhead
  • Re-frame is bit different in all the ways

Solutions

  1. Separate ways
    • let's not agree on anything, there will many ways to do this. Each implementation will have it's own fans and support libs.
    • not good for the Clojure/Script as a community.
  2. a new Interceptor SPEC
    • a new and neutral Interceptor SPEC
    • needs breaking changes (for Pedestal)

Interceptor SPEC Draft

  • support for both Clojure & ClojureScript
  • support for pluggable async steps
  • no implementation classes
  • bare minimum
    • e.g. extra keys like :requires and :provides for dependent interceptors are out of the spec
    • same for reitit-spesific keys like :spec and :compile

Stages

  • :enter, :leave & :error, as in Pedestal.

Context

  • option 1:

    • execution context is the request, response or error value. e.g. enters are request->request.
    • interceptors can't store local state, which is bad
  • option 2:

    • execution context is a map-like context, which holds :request, :response and :error keys
    • allows interceptors to contain local state, e.g. in :enter a current time is stored, in :leave we calculate how long time the execution took
key description
:request optional request of anything, available on all stages
:response optional response of anything, available on the :leave stage
:error exact thwon Exception/Error, available on the :error stage

==> option 2

Interceptor

Can be presented as a Map-like structure with:

key description
:name optional Keyword name
:enter optional Context => IntoContext function, going in, Context has the :request key
:leave optional Context => IntoContext function, going out, Context has the :response key
:error optional Context => IntoContext function, on error (out), Context has the :error key
  • Do we need a small shared namespace for the IntoContext protocol to make library interceptors support async?
  • bit different than in Pedestal

Handler

Not part of a spec: optional last interceptor in a chain that doesn't see the context but is a request -> response function. Provides bridge for existing non-interceptors impls like ring-handlers. Handlers could be defined as interceptor-type maps too. The runner implentations should convert functions into interceptors (see IntoInterceptor).

Execution

Interceptor runner will run the given chain of interceptors with a given request or context. Runners are out of the scope of the interceptor spec.

(execute 1 [{:enter #(update % :request inc)}])
; => 2

;; the handler case, function of request->response as last step
(execute 1 [{:enter #(update % :request )} inc])
; => 3

Dynamic queues

  • option 1:
    • :queue ans :stack as extra mandatory keys in context, "the Pedestal model / everything is an interceptor"
    • allows interceptors to modify the queue, e.g. router pushes route-spesific interceptors to the queue
    • bit slower and more complex implementations
    • most of the things could be done with static queue manipulation?
    • is this really needed?
  • option 2:
    • static execution queue
    • enables faster implementations
    • is this ok?
  • option 3:
    • interceptors can define wether they want to manipultate the queue via a special key :queue, with valid values :static or :dynamic -> interceptor runner can optimize itself if no :dynamic interceptors are mounted.
    • NOTE: most if not all of the library spesific interceptors can be :static!!

=> Comments!

Async

  • context-functions (:enter, :leave & :error) can return async context, which the interceptor runner will handle in async fashion.
  • out of spec how to interpret the async return values? a shared small lib with some protocols?

IntoInterceptor (OUT)

Implementation detail, how different Executors turn different clojure data into optimized Interceptors. e.g. reitit has a custom compilation stage, not part of the spec.

Executor (OUT)

A Protocol to execute a interceptor chain on a given context, e.g. with a function of [Interceptor] Context => IntoContext. Not needed in the spec?

  • enables cross-use like reitit as a routing engine in Pedestal or sieppari as a interceptor engine in reitit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment