[spootnik/maniflow "0.1.5"]
The manifold.lifecycle
namespace provides a lightweight take on a
mechanism similar to interceptors
or sieppari. A key difference with
interceptors is that individual handlers have no access to the step chain
and thus cannot interact with it. Guards and early termination are still
provided.
A lifecycle is nothing more than a collection of handlers through which a value is passed.
In essence:
@(run 0 [(step inc) (step inc) (step inc)])
Is thus directly equivalent to:
(-> 0 inc inc inc)
As shown above, each step in a lifecycle is a handler to run on a value. Steps can be provided in several forms, all coerced to a map.
{:id :doubler
:enter (fn [input] (* input 2))
:guard (fn [context] ...)}
When a step is a plain function, as in (run 0 [inc])
, the
resulting map will be of the following shape:
{:id :step12345
:enter inc}
If the function is provided as a var, the qualified name of the var
is used as the id, so for (run 0 [#'inc])
we would have instead:
{:id :inc
:enter inc}
Often times, the payload being threaded between lifecycle steps will be a map. As with interceptors, it might be useful to hold on to information accumulated during the chain processing. To help with this, maniflow provides three parameters for steps:
:in
: specifies a path that will be extracted from the payload and
fed as input to the handler:out
: specifies where in the payload to assoc the result of the
handler:lens
: when present, supersedes the previous two parameters and
acts as though both :in
and :out
where provided{:id :determine-deserialize
:enter (partial = :post)
:in [:request :request-method]
:out [::need-deserialize?])
Based on the current state of processing, it might be useful to guard execution of a step against a predicate, keeping with the last example:
{:id :deserialize
:enter deserialize
:lens [:request :body]
:guard ::need-deserialize?}
Sometimes,
{:id :debug
:enter prn
:discard? true}
step
The manifold.lifecycle/step
function is provided to build steps
easily, the above can thus be rewritten:
(enter-step :determine-deserialize (partial = :post) :in [:request :request-method] :out ::need-deserialize?)
(enter-step :debug prn :discard? true)
(enter-step :deserialize deserialize :guard ::need-deserialize?)
(enter-step :handler run-handler)
When running a lifecycle, an options map can be passed in to further modify the behavior:
{:augment (fn [context step] ...)
:initialize (fn [value] ...)
:executor ...}
augment
: A function of context and current step called for each step
in the lifecycle.initialize
: A function of init value to prepare the payload.executor
: An executor to defer execution on.There are intentionally no facilities to interact with the sequence of
steps in this library. Exceptions thrown will break the sequence of
steps, users of the library are encouraged to use
manifold.deferred/catch
to handle errors raised during execution.
All errors contain the underlying thrown exception as a cause and
contain the last known context in their ex-data
(-> (d/run 0 [(step inc) (step #(d/future (/ % 0))) (step inc)])
(d/catch (fn [e]
(type (.getCause e)) ;; ArithmeticException
(:context (ex-data e)) ;; last context)))
Assuming a map payload, timing can be recorded for each step
with the initialize
and augment
implementations provided
in manifold.lifecycle.timing
:
@(run
{:x 0}
[(step :inc inc :lens :x) (step :inc inc :lens :x)]
timing/milliseconds)
{:timing
{:created-at 1569080525132,
:updated-at 1569080525133,
:index 2,
:output [{:id :inc, :timing 0} {:id :inc, :timing 1}]},
:x 2}
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close