A Clojure library that lets you decouple your application, while making communication easy.
clj-starbuck helps you to...
- ...make network communications in your app (e.g. between browser and server in a webapp) completely transparent.
- ...make internal communications in your app (between different modules within your app) just as transparent.
- ...make communications between network components (e.g. browser & server) reactive.
- ...implicitly and transparently use multithreading (modules may run on any arbitrary threads).
- ...avoid dependency hell within the software you write by keeping modules independent from each other.
- ...provide all these benefits in Clojure as well as Clojurescript.
-
Define a routing ruleset:
(def ruleset {:routes {:book-flight {:transitions {nil :booker :booker :invoice-generator}}}})
-
Define the necessary core.async channels:
(require '[clojure.core.async :as a]) (require '[ow.starbuck.impl.core-async :as ica]) (def component-ch (a/chan)) (def component-pub (ica/component-pub component-ch)) (def router-ch (a/chan))
-
Define the components involved:
(def booker (ica/component "booker" ;; name (ica/component-sub component-pub :booker) ;; core.async sub channel router-ch ;; where to put result (fn [this msg] ;; you app's business logic (assoc msg :booked? true)))) (def invoice-generator (ica/component "invoice-generator" (ica/component-sub component-pub :invoice-generator) router-ch (fn [this msg] (println "invoice generated") msg)))
-
Build config for Router:
(require '[ow.starbuck.client :as c]) (def config (c/config ruleset {:components {:booker booker :invoice-generator invoice-generator}}))
-
Create Router:
(def router (ica/router config router-ch))
-
Start Router & Components:
(def router (ica/start router)) (def booker (ica/start booker)) (def invoice-generator (ica/start invoice-generator))
You now have a router waiting for incoming messages on router-ch
. It will use config
to determine the next
component for a message and put it onto component-ch
. The corresponding component will receive the message
via its own subscription channel created via component-sub
. After the component is done with the message,
it will be put onto router-ch
again and the process starts again for as long as there is a next component
defined.
You can create and send a message asynchronously:
(let [msg (c/message :book-flight
{:from :london :to :berlin})]
(a/put! router-ch msg))
The message will be picked up by the router and will then be routed through the components defined in the config's ruleset.
clj-starbuck assumes that you structure your app in a way that it consists of independent modules (called components). Those modules communicate with each other via messages. However, the components don't know anything about each other (so they don't depend on each other). Instead, clj-starbuck provides a Router component that delivers messages to the right components. A component processes a messages, potentially altering it and implicitly returns it back to the Router that knows which component the message has to go to next.
Messages are simple maps of data. As messages flow through the system, they need to maintain some info about the routing process, so you create messages via
(require '[ow.starbuck.client :as sc])
(def msg1 (sc/message {:foo :bar}))
{:foo :bar}
is the user data your application needs for processing its business logic, so it can be anything
(as long as it is a map on the outside).
- Components
- Units + Tunnels
- Routing table / ruleset
This library itself is based on:
- core.async
TBD
Copyright © 2017 Oliver Wegner
Distributed under the Apache License, Version 2.0.