Clojure Publishers-Subscribers and MVVM

I have used the MVVM pattern for the desktop app I am developing.

For the views, I have tried a wrapper for Java Swing and a wrapper for JavaFx. Eventually I chose JavaFx because I found easier giving styles to the UI and also because Oracle has integrated it in the SDK.

MVVM conventions:

MVVM

View:

  • Views only contains functions, they do not contain atoms.
  • Each view only contains a public function: “construct-view”.
  • Views do not use the Pub-Sub Events Manager.
  • The View may listen (watch) for changes in the ViewModel.
  • A View may depend on other Views and shall depend in one or more ViewModels.
  • Views will use the Internationalisation component.

ViewModel:

  • ViewModels define their own records giving service to the Views.
  • A ViewModel may depend on other ViewModel and shall depend on one or more Models.
  • ViewModels may use the Pub-Sub Events Manager.
  • The ViewModel also listen (watch) for changes in the Model.
  • The records in the ViewModel tend to be composed by atoms enabling the bindings of the View.
  • A ViewModel may keep a snapshot of calculated/aggregated data.

Model:

  • A Model define its own records and may keep a snapshot of some of the data.
  • Models read the information from the Web Services or from the DB.
  • Models shall use a parser when the information from the source is not in Clojure format.
  • Models use the Pub-Sub Events Manager.
  • The records in the Model tend to be wrapped by an atom.

Publisher-Subscriber Event System

During the app development, I had the need for an event system between modules. Like in any other framework or technology stack.
I have used core.async, “A Clojure library designed to provide facilities for async programming and communication.”

This diagram shows a composition using some of the elements defined in the library core.async in order to achieve the Publisher-Subscriber pattern.

PUB-SUB

Here is the code, I will publish it on GitHub or Bitbucket as soon as I had added some tests. You are free to use it, do not forget to mention me if you like it.
I will also add some comments for you very soon.

;Publishers-Subscribers Event Manager
(ns your-project.controller.pubsub
   (:require [clojure.core.async :refer [<! >! chan pub sub go go-loop close! alts! timeout]]))


(def publisher (chan 10))
(def publication (pub publisher :topic))


(defn publish
   ([topic] (go (>! publisher {:topic topic :data nil})))
   ([topic data] (go (>! publisher {:topic topic :data data}))))


(defn publish-to-many [topics]
   (doseq [topic topics]
      (publish topic)))


(defn subscribe [topic channel]
   (sub publication topic channel))


(defn subscribe-to-many [topics channel]
   (doseq [topic topics]
      (subscribe topic channel)))


(defn create-publication-handler [topics handler]
   (let [channel (chan 10)]
      (if (coll? topics)
         (subscribe-to-many topics channel)
         (subscribe topics channel))
      (go-loop []
         (when-let [event (<! channel)]
            (handler event)
            (recur)))))


(defn synchronize-on [topics-actions mils]
   (let [topic-keys (keys topics-actions)
         channels (doall (for [topic topic-keys]
                           (let [channel (chan 1)]
                              (subscribe topic channel)
                              channel)))
         time (timeout mils)]
      (go
         (doseq [channel channels]
            (if-let [event (first (alts! [channel time] :priority true))]
               ((get topics-actions (:topic event)) (:data event)))
            (close! channel)))))