Full spectrum website stack in Clojure and ClojureScript
As I figured out, it is not simple to find a full stack of cutting edge technology libraries for building a website in one example.
Cubanostack is aimed at this, building and illustrating the use of the following libraries:
- cublono-quiescent for easy writing of ReactJS component in,
- bidi for routing both in Clojure and ClojureScript,
- postal for sending mail,
- liberator for RESTfull backend coding,
- buddy for user authentication (using JWT),
- hiccup for HTML generation,
- slingshot for advanced exception handling,
- timbre for logging,
- tower for i18n,
- (optional) sente for WebSocket communication between frontend and backend,
- component for module definition,
- schema for data integrity and coercion,
- ring for the web application abstraction,
- http-kit for serving the web application,
- (optional) orientdb offering an embedded Java document noSql database,
- clj-http for inter-server communication,
- cljs-http to communicate with the RESTFull backend services.
In particular, the use of Stuart Sierra's component library allows an external observer to easily find and understand the inter-module dependencies.
Cubanostack is developed around the idea of being fully modularized and expandable without needing to change the source code of the core library or a third-party library. In particular, any modules features is made to be easily overidable.
At first sight, this make the code much verbose as it forces to declare all the "public" features a module is using / providing. After some usage, you will certainly see that this practice as a (mutch appreciated) kind of self-documenting process, which is very usefull when you are using other developer's module or when other developers will use your module.
Technically, the core of the modularization is the Wrapper and the Bus notion.
The Bus is a shared component that allow all others component to communicate between them. Basically, it is managing named Wrappers, receiving messages from component, and passing them to the corresponding Wrapper queue.
Wrappers are a stack of functions that will be applied to a payload in order to generate a response.
Wrappers can be registred in the WrapperManager component in order to be receiving messages from the Bus.
At the base, a Wrapper is something (technicaly a Protocol instanciation) that:
- do something (a fn) before the main process (like changing the payload),
- then apply the main process to the payload building a response,
- then do something (an other fn) after the main process (like changing the response).
As an example:
(deftype TimeCommandWrapper
Wrapper
(before [this payload]
(merge payload
{::start-time (System/nanoTime)}))
(after [this response payload]
(assoc response
:elapsed (- (System/nanoTime)
(::start-time payload)))))
When TimeCommandWrapper is called on a Wrapper stack, it will decorate the payload with a ::start-time info, then let the "normal" / other Wrapper in the stack apply their before and after processes, and then decorate the response with an :elapsed info giving the time the global process took for computation.
What is important is that any Wrapper (called A) can be encapsulated in another Wrapper (called B), allowing any of the following scenarii: - decorate the payload with info from Wrapper B but letting the normal Wrapper A process to occur on a modified payload, - letting the normal Wrapper A process to occure but decorating it's response with info from Wrapper B, - shortcuting Wrapper A process, replacing it with the Wrapper B process
All of this without changing Wrapper A source code.
Here are some applicable scenarii:
- a Wrapper that fixes another Wrapper bug(s) without changing it's source code (for legal/licence reasons for example),
- a Wrapper that adds a new feature to another Wrapper without changing it's source code:
- adding some log to a existing process,
- modifying a e-commerce checkout process (keeping the core checkout process behind the scene [one step checkout]),
- a Wrapper that decorates the output of another Wrapper, like adding some info in a HTML footer / menu,
- a Wrapper that replaces another Wrapper process, like using OrientDb instead of MongoDb.
By using Stuart's component, and registring Wrappers in the WrapperManager, we are able to fully control the Module installation / start / stop / deinstallation processes.
As a basic illustration, you should use the Cubane lein template that will mainly use Cubanostack as its main library, creating the needed project-base file you will need.
lein new cubane <your project name> --snapshot -- --http-kit --orient-db
This library is still in heavy development phase, in particular the following features are planned to be released (no release dates / development order is not fixed):
- user registration / password handling (using mail via postal),
- backend settings dashboard,
- backend modules de/activation dashboard,
- persitent frontend modules de/activation,
- wrapper order / dependency (using Stuart's dependency ?)
- cassendra module,
- AMQP module,
- bridging with the Bus,
- redis module,
- embedded SQL module (h2 ?),
- module installation from JAR,
- automatic ClojureScript rebuilding when new module get de/activated (as figwheel is able to),
- externaly modularize non-core modules:
- orientdb
- sente
- cublono-quiescent
Copyright © 2016 Sylvain Tedoldi
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.