Fork me on GitHub
Vincent Cantin03:09:21

The idea is to have a structure of subscribers on the different parts (different paths) of the db via a hierarchy of hashmaps which list sets of subscribers in its nodes. I call this structure a subscription-tree. That is probably not a new thing, I guess reagent has something like that. The part which might be new is to have a subscription-tree on each node of the compute graph. It means that the action of listening changes on a part of a data is a generalized and homogenous feature in Vrac. The selection of the right subscribers is fast when done based on a diff. An since each compute node is going to return a diff, everything should work well and efficiently.

Vincent Cantin03:09:05

Precision: the diff approach is only related to structural changes. Most operations which will be in the compute node (like first-name + last-name -> full-name) won’t really need it, and will still be written as a classical function implementation which does not output diffs. Those functions will be wrapped to fit in the compute graph.

Vincent Cantin03:09:33

Thanks to the subscription-tree, the template below will use zero compute nodes and will feed directly on the client db through this structure:

;; Subscription-tree on the client db:
{:children {:user {:children {:first-name {:subscribers #{1}}
                              :last-name {:subscribers #{2}}}}}}

;; Subscribing template:
(let [user (:user global)
      {:keys [first-name last-name]} user]
  [:div first-name " " last-name])

Vincent Cantin03:09:39

I am going to start implementing a (slow) reference version of the compute graph, as a proof-of-concept of the algorithms and diff-flow.

Vincent Cantin03:09:48

Here is a bit of the impl:

(def empty-subscription-tree {})

(defn subscribe-on-path [tree path subscriber]
  (if (seq path)
    (let [[path-element & path-rest] path]
      (update-in tree [:children path-element] subscribe-on-path path-rest subscriber))
    (update tree :subscribers (fnil conj #{}) subscriber)))

(defn- update-coll-then-dissoc-empty [coll key f & args]
  (let [new-val (apply f (get coll key) args)]
    (if (seq new-val) ; new-val is assumed to be a collection
      (assoc coll key new-val)
      (dissoc coll key))))

(defn unsubscribe-from-path [tree path subscriber]
  (if (seq path)
    (let [[path-element & path-rest] path]
      (update-coll-then-dissoc-empty tree :children
        update-coll-then-dissoc-empty path-element
        unsubscribe-from-path path-rest subscriber))
    (update-coll-then-dissoc-empty tree :subscribers disj subscriber)))

;; Testing
#_ (-> empty-subscription-tree
       (subscribe-on-path [:user :first-name] 1)
       (subscribe-on-path [:user :last-name] 2))

; => {:children {:user {:children {:first-name {:subscribers #{1}}
;                                  :last-name {:subscribers #{2}}}}}}

#_ (-> *1
       (unsubscribe-from-path [:user :first-name] 1)
       (unsubscribe-from-path [:user :last-name] 2))
; => {}

Vincent Cantin03:09:59

— End of today’s brainstorming for me.