Provides a Clojure library to layer functions into a single process workflow. Like the Clojure multimethod functionality, process allows for the ability to execute branches of logic based on the context of function call(arguments, etc).
Similaritities | multimethod | process |
---|---|---|
Define a callable function | clojure.core/defmulti | pliant.process/defprocess |
Define branches of logic to execute | clojure.core/defmethod | pliant.process/deflayer |
But process differs in how the dispatching is performed. Where multimethod defines a single dispatch function and matches the value it returns to a single method to execute, process allows for each layer that is added to it, starting with the first layer in order, to determine if it will execute or not, as well as whether it will allow the next layer to be executed.
Differences | multimethod | process |
---|---|---|
Dispatching | Single dispatch function that is matched to a single method to execute | Each layer determines if it is executed. |
Workflow of execution | None. Only one method is executed. | Can execute one or many layers on a single call, as well as skipping over layers and performing callbacks. |
Default logic execution | Available when method is added with :default dispatch value | Inherent in the defining of defmethod |
Ordering vs Prefer | Prefers one method over another with same dispatch value with prefer-method | Can set order of execution of layers with before |
Creating a process is just like creating a function, except instead of using defn you use pliant.process/defprocess .
(use 'pliant.process)
(defprocess myprocess
"This is my process doc"
[arg1 arg2]
(println "Running default logic with args:" arg1 arg2))
The process function myprocess can be called just like a normal function.
user=> (myprocess 1 2)
Running default logic with args: 1 2
nil
Adding a layer to the process is done using pliant.process/deflayer.
(deflayer myprocess mylayer1
[arg1 arg2]
(if (= arg2 4)
(println "Running layer1 logic with args:" arg1 arg2)
(continue)))
user=> (myprocess 1 2)
Running default logic with args: 1 2
nil
user=> (myprocess 1 4)
Running layer1 logic with args: 1 4
nil
Differences emerge from multimethod when execution is based off of different criteria:
(deflayer myprocess mylayer2
[arg1 arg2]
(if (:doit arg1)
(println "Running layer2 logic with args:" arg1 arg2)
(continue)))
(deflayer myprocess mylayer3
[arg1 arg2]
(if (roles/is-manager (session/user))
(println "Running layer3 logic with args:" arg1 arg2)
(continue)))
By default layers are executed in the order that they are read into Clojure. To change the order you can currently use the pliant.process/before function.
user=> (before mylayer3 mylayer2)
nil
Controlling the flow of a process is performed by calling one of the flow control functions at the end of your logic, which returns a value to the processes internal controller. If a layer returns any value, including nil, that is not generated from one of the flow control functions the execution of the process is considered complete and that value is returned. The available flow control functions are:
Control Flow Function | Instruction |
---|---|
pliant.process/continue | Continue to and attempt to execute the next layer on the process. |
pliant.process/skip | Continue to and attempt to execute the next layer on the process. Skip a layer if it matches any of the layer functions that have been provided to the skip function. |
pliant.process/callback | Continue to and attempt to execute the next layer on the process. When finished executing layers execute the function provided with the value returned from the last layer. |
pliant.process/skipback | Continue to and attempt to execute the next layer on the process. Combines the ability to skip layers and perform a callback. |
(deflayer myprocess mycontinue
[arg1 arg2]
(if (= arg1 arg2)
true
(continue)))
(deflayer myprocess myskip
[arg1 arg2]
(if (= arg1 arg2)
(skip mycontinue)
(continue)))
(deflayer myprocess mycallback
[arg1 arg2]
(if (= arg1 arg2)
(callback (fn [val] (if val "Yes" "Nope")))
(continue)))
(deflayer myprocess myskipback
[arg1 arg2]
(if (= arg1 arg2)
(skipback (fn [val] (if val "Yes" "Nope")) mycontinue)
(continue)))
In order to combine the patterns that multimethod and process provide, the pliant.process/as-method function enables integration between the two.
(defmulti handle-http uri->process) ;; uri->keyword psuedo fn to change a http request uri to a keyword
(defprocess root [request] (redirect "/login"))
(defprocess login [request] (render-login))
(as-method root handle-http :get)
(as-method login handle-http :get-login)
(deflayer login shib-login [request] (if (auth/use-shib? request) (redirect-to-shib request) (continue))