taoensso / sente

Realtime web comms library for Clojure/Script
https://www.taoensso.com/sente
Eclipse Public License 1.0
1.74k stars 193 forks source link

Sente and good (functional) programming practice #32

Closed danielsz closed 10 years ago

danielsz commented 10 years ago

Sente is an awesome library. As such, it will be used in production-grade applications. It is therefore important to provide guidelines regarding the integration of said library.

Common challenges are:

The example project, while extremely useful to introduce the features of Sente, should be complemented with guidelines aiming to address those challenges specifically.

Stuart Sierra has been advocating a workflow that works well and we may want to adopt its principles.

Two key points from the README:

Large applications often consist of many stateful processes which must be started and stopped in a particular order. The component model makes those relationships explicit and declarative, instead of implicit in imperative code.

And:

Instead of having mutable state (atoms, refs, etc.) scattered throughout different namespaces, all the stateful parts of an application can be gathered together.

I would like to open for discussion my implementation of a Sente component. It is working well for me, but I might have overlooked things or, worse, inadvertently introduced new problems.

(ns front-end.framework.components.channel-sockets
  (:require [com.stuartsierra.component :as component]
            [taoensso.sente :as sente]
            [front-end.webapp.handler :refer [event-msg-handler]]))

(defrecord ChannelSockets [ring-ajax-post ring-ajax-get-or-ws-handshake ch-chsk chsk-send! chsk-router]
  component/Lifecycle
  (start [component]
    (let [{:keys [ch-recv send-fn ajax-post-fn ajax-get-or-ws-handshake-fn]}
      (sente/make-channel-socket! {})]
      (assoc component 
        :ring-ajax-post ajax-post-fn 
        :ring-ajax-get-or-ws-handshake ajax-get-or-ws-handshake-fn
        :ch-chsk ch-recv
        :chsk-send! send-fn
        :chsk-router (sente/start-chsk-router-loop! event-msg-handler ch-recv))))
  (stop [component]
    component))

(defn new-channel-sockets
  []
  (map->ChannelSockets {}))

My development system looks like this:

(defn dev-system []
  (component/system-map
   :sente (new-channel-sockets)
   :web (new-web-server (Integer. (env :http-port)) (env :trace-headers))))

One would start the system like this:

(def system)
(alter-var-root #'system (fn [_] (component/start (dev-system))))

As a result, all the relevant sentefunctions are in the system map, allowing us to call chsk-send!, for example, from any namespace.

((:chsk-send! (:sente  (system-map))) (:uid session) [:myapp/account {:status "OK"}])

Following this, the compojure routes look like this;

  (GET  "/chsk" req ((:ring-ajax-get-or-ws-handshake (:sente (system-map))) req))
  (POST "/chsk" req ((:ring-ajax-post (:sente (system-map))) req)))

Thoughts?

ptaoussanis commented 10 years ago

Hi Daniel!

Looks okay to me, though I'm not very familiar with the objectives of this style so can't comment on the utility there. Only thing that's apparent to me: you may want to shut down the :chsk-router on stop? Have just pushed v0.12.0-SNAPSHOT to Clojars which returns a (fn stop! []) on start-chsk-router-loop! calls. You can hang onto that fn and call it to terminate the original router.

Have a good weekend, cheers! :-)

danielsz commented 10 years ago

Wonderful. Thanks, Peter.

I'm leaving a couple of links that are sure to shed more light on the motivations of this workflow. If you have an interesting workflow of your own, I would love to learn more about it.

The canonical reference is this: My Clojure Workflow, Reloaded

And here are more references all touching on the topic. Clojure in the Large Component Retrofitting the Reloaded pattern into Clojure projects 5 faces of dependency injection in Clojure REPL functions to support the reloaded workflow

ptaoussanis commented 10 years ago

If you have an interesting workflow of your own, I would love to learn more about it.

I think the correct approach depends largely on the context: what you're trying to do, how, and what problems you're actually running into in practice. The projects I'm involved with tend to be on the large side so I've adopted some conventions that are a little unusual.

I tend to generate namespaces and ns imports programatically from templates, and there'll be a top-level environment config that gets passed down to relevant namespaces based on their access privileges for data, etc. This solves similar issues (those that I care about, anyway) - in a way that I find more flexible for my particular needs.

This'd be the wrong choice in smaller projects though since the tradeoffs are geared to managing the kind of complexity you end up with when dealing with lots of code.

Cheers :-)

danielsz commented 10 years ago

This sounds really interesting / intriguing.

This calls for a blog post explaining the methodology and possibly a lein template to get started :-)