flowgic
is a clojure DSL to describe the internal logic flow of controller like fns.
Controller-fns are those that play a Controller Role, managing different services, inputs, outputs, steps, decisions and rules.
In this complected scenario, our understanding decreases exponentially. flowgic
tries to improve the readability and understading of this logic flows
[ch.deepimpact/flowgic "0.1.0"]
- the code becomes clearer :)
- the code is so clear that task's communication between devs and clients or project managers is improved :)
flowgic
code can be easily parsed and analysed to generate dynamic flow diagrams- you could logically derive wich parts/fns need to be tested from the others that dont' need.
- is easy to add any middleware due as you always work with fns
- your logic flow is a complex and nested one
- your controller-fns are tricky to understand, even by the author after a few days example
- your fns must receive a map and return a map (yep! like a ring handler). Of course you could always adapt your controller-fns to this great pattern => Prismatic/Graph , Prismatic/fnk
Starting here the example code will use prismatic/defnk
and prismatic/fnk
to simplify working with fns that receive a map of keys/values. it's not required to use these macros but we think they are great syntatic sugar focused in clarify too.
;; example of clojure fn that receives a map and you are interested in 3 keys of this map
(defn example-fn [{:keys [a b c] :as data-map}]
(println a b c)
(println data-map))
(example-fn {:a 1 :b 2 :c 3})
;; the same but using prismatic/defnk
(defnk example-fnk [a b c :as data-map]
(println a b c)
(println data-map)
)
(example-fnk {:a 1 :b 2 :c 3})
Agreeing that a controller-fn
is a sequence of logics to be evaluated having a context, we can derive following Logic
protocol
(defprotocol flowgic.core/Logic
(evaluate [this ^clojure.lang.PersistentArrayMap context]))
In other words and to clarify a bit more: A flowgic.core/Logic
needs a logical-context to be able to solve an evaluation. As you can realise, the context
is a common clojure map.
(require '[ch.deepimpact.flowgic :as f])
;; f stands for flowgic in this doc
A Continuation
means: after a Logic/evaluation, flow must continue on next logic (except core/Return, see next impl)
Typically, Continuation
is represented as a intermediate box between 2 others (other of same or different type). In following graph examples: a
, b
and c
represent a Continuation in a logic flow
f/continue
: Three ways to create a core/Continuation
;; if you don't need to select anything of the result or add any static data to the result
(def cont (f/continue (defnk a [])))
(= [:continue {:a 1}]
(f/evaluate cont {:a 1}))
;;if you need to select some values from the result
(def cont1 (f/continue (fn [map] {:cont1 "A"}) [:cont1]))
(= [:continue {:a 1 :cont1 "A"}]
(f/evaluate cont1 {:a 1}))
;;if you need to select some values and add some others
(def cont2 (f/continue (defnk c2 [:as map] {:cont2 "B"}) [:cont2] {:cont2-flag true}))
(= [:continue {:a 1 :cont2 "B" :cont2-flag true}]
(f/evaluate cont2 {:a 1}))
Return
is the break circuit in a flow.
A Return means: after logic evaluation, flow must end returning the fn return value
The behaviour is similar to exception but just sending its own result to the end.
In the following picture, red boxes represents core/Return
f/exit
: creating a new Return
(def step-exit (f/exit (fn [map] {:res "EXIT"})))
(= [:exit {:res "EXIT"}]
(f/evaluate step-exit {}))
Clojure vector is used as the logic flow container. It also can be nested.
In flowgic
, clojure vector means: evaluate the context with the first logic, then if result is a Continuation
pass the result of first evaluation merged with the initial context to the second logic and repeat until the end (that it returns the context merged with all the continuation results).
If some logic is of Return type, then we return the result of Return.
(def logics [cont cont1 cont2])
(= [:continue {:initial-data "hello",
:cont1 "A",
:cont2 "B",
:cont2-flag true}]
(f/evaluate logics {:initial-data "hello"}))
Nested declaration examples:
;; with another vector at the end
(def logics1 [cont cont1 cont2
[(f/continue (defnk d [:as map]))
(f/continue (defnk d1 [:as map]))
(f/continue (defnk d2 [:as map]))]])
;; with another vector in third position
(def logics2 [cont cont1
[(f/continue (defnk d [:as map]))
(f/continue (defnk d1 [:as map]))
(f/continue (defnk d2 [:as map]))]
cont2])
Inserting the nested vector in other position
A Rule
means: flow will evaluate
the provided Logic
if matching condition on the result of your-fn-rule
with current context
. On the contrary evaluate next container
logic.
Simple example of "the result of calling (your-fn-rule context)
"
(def context {:a true :b false})
(def your-fn-rule :a)
(= true (your-fn-rule context))
;; of course you can use complex fns instead of keyworks,
;; but for the sake of simplicity we will use keywords in following examples
(def your-complex-fn #(= 2 (count (keys %))))
(= true (your-complex-fn context))
In flow diagrams, rules are commonly represented with diamond shapes.
Although you can easily create your own Rule
, this lib currently provides following ones:
f/true?
f/>true?
f/>false?
f/emtpy?
f/>emtpy?
f/>not-emtpy?
You want to specify what ought to happen on true and on false your fn-rule result.
(= [:continue {:initial-data "hello"
:rule-fn true
:condition-was true}]
(f/evaluate (f/true? :rule-fn
(f/continue (defnk on-true-fn [:as map]) [] {:condition-was true})
(f/continue (defnk on-false-fn [:as map]) :condition-was false ))
{:initial-data "hello"
:rule-fn true}))
The same as previous,but if false
you always want to continue next container Logic
(= [:continue {:initial-data "hello"
:rule-fn true
:condition-was true}]
(f/evaluate (f/>true? :rule-fn
(f/continue (defnk on-true-fn [:as map]) [] {:condition-was true}))
{:initial-data "hello" :rule-fn true}))
(f/>false? fn-passing-current-context logic-on-false)
The same as previous but, if true
you always want to continue next container Logic
.
(= [:continue {:initial-data "hello"
:rule-fn false
:condition-was false}]
(f/evaluate (f/>false? :rule-fn
(f/continue (defnk on-false-fn [:as map]) [] {:condition-was false}))
{:initial-data "hello" :rule-fn false}))
the same as flowgic/true? flowgic/>true? flowgic/>false?
but matching nil instead of true/false
(= [:continue {:initial-data "hello"
:rule-fn nil
:condition-was true}]
(f/evaluate (f/empty? :rule-fn
(f/continue (defnk on-true-fn [:as map]) []{:condition-was true})
(f/continue (defnk on-false-fn [:as map]) []{:condition-was false})
)
{:initial-data "hello" :rule-fn nil}))
(= [:continue {:initial-data "hello"
:rule-fn nil
:condition-was true}]
(f/evaluate (f/>empty? :rule-fn
(f/continue (defnk on-true-fn [:as map]) []{:condition-was true}))
{:initial-data "hello" :rule-fn nil}))
(= [:continue {:initial-data "hello"
:rule-fn "not empty!"
:condition-was false}]
(f/evaluate (f/>not-empty? :rule-fn
(f/continue (defnk on-false-fn [:as map]) []{:condition-was false}))
{:initial-data "hello" :rule-fn "not empty!"}))
explain following types (flowgit/Evaluation impls)
more
-
core/Merge => f/merge
-
core/ControllerFn => f/controller-fn
-
f/just
flowgic
is really very influenced by Prismatic/graph and its Dependency Injection at the function&args level
yep, flowgic
remains a bit similar to pipeline programming
Yes, indeed I realised that it was the same as ring handler (handler request)
but adding more data to the handler fn, something like (#(evaluate (FlowgicImpl. data) %) request)
-
Releases are published to TODO_LINK
-
Latest stable release is TODO_LINK
-
All released versions TODO_LINK
Leiningen dependency information:
[ch.deepimpact/flowgic "0.1.0"]
Maven dependency information:
<dependency>
<groupId>ch.deepimpact</groupId>
<artifactId>flowgic</artifactId>
<version>0.1.0</version>
</dependency>
- Version 0.1.0
- extracted Evaluation protocol from graph and meta protocols
- Version 0.1.1-SNAPSHOT
- using one public namespace
flowgit
to get all functionality. Move the rest tocore
,graph
, andmeta
Copyright © 2015 DEEPIMPACT.ch
Distributed under the Eclipse Public License, the same as Clojure.