Removing app state from re-frame app


After watching several event sourcing talks, I wanted to try this technique client side.
I’m already familiar with re-frame and thought it would lose a lot of benefits if I’m not using the app-db but re-frame is more about events, subscriptions and effects than it is about state management. db is just another effect and coeffect after all.

view = f(app-state) and app-state = f(events) then we can potentially have view = f(events)

Clojure, re-frame and spec (with their data oriented way of doing things) will help a lot here.
I think there is a good opportunity to use s/conform to get the data you are interested in from an event.
E.g: when dispatching the event [::create-todo "go hiking"] it is nice not to have to name things.
This could be an id, a text or a title depending on where and when you read this event. Then it’s up to the reader to conform the event if needed.

My plan is to try the following :

  • keep an event log in app db.
  • bypass reg-event since we only store the event log (ignoring external effect such as xhr for now)
  • have a ::event/log subscription that take a filter fn so each subscription only get the events it’s interested in.
    Re-frame will take care of not running the sub if no new “interesting” event has been logged

A subscription for a simple counter app would be for example :

(reg-sub ::count
  :<- [::events/log #{::count-up}]
  (fn [log _]
   (count log)))

Here is my todo-mvc implementation on nightcoders using this architecture
and the preview that you can get clicking “View App” on the editor. Thanks again @sekao for the IDEs :sunny:

I’m still working on it and I’ll explore pattern so those quotes from Greg Young are more obvious :

Modelling events forces behavioural focus. as opposed to a structural focus
Time becomes a crucial factor of your system. How things correlate over time …

Having time built in events should allow easier management of timeout or things like showing notifications without having to toggle some flag to show/hide.

1 Like

I never used reframe (I use rum+citrus, but they’re very similar) but I remember the readme a sentence from the README of precept :

Like Datascript, Redux, re-frame, and other front-end data stores, Precept does not attempt to maintain a history of all facts. We agree with Datomic’s design with respect to this, but because Precept runs in a web browser, space is relatively limited. For our application, we see more value in that space being occupied by tens of thousands of facts that represent the current state of the world instead of e.g. 100 facts with 100 histories.

Precept builds on a EAV triples store/db, but they considered that as memory is limited in the browser it’s better to store what’s necessary for the current state. You may hit some limit for bigger apps. Maybe @alex-dixon (main force behind Precept) made some benchmarks and can tell us more about it ?

By the way it’s interesting to have a keep-all-events-and-derive approach, I’m just curious about how it scales with app size with regards to performance.

I think that’s valid concerns but I’m first trying to see if I can see some benefits. And to do that it’s better to ignore a lot of potential issues and focus on the simple bit and iterate.
I imagine it was not so scalable to have a global mutable state few years back but we found nice patterns and data structure to make it possible.

That’s why I only focus on : is it easier / faster to write app that way ? is it easier to read ?
Did you find the todomvc example easy to understand ?

I can see some benefits already where the relation between events and the relation between derived views and their events are more obvious (and even drawable).

There is a whole kind of event handlers that just set a flag in the app db that can be removed with the approach.

I also want to explore if not having an app state and not having to name things inside the app state would make adding feature easier.

Very exciting approach. In a current frontend Javascript project we have a fairly event-driven app that uses Redux Saga, and I keep wishing we had core.async instead. Oh well.

Event sourcing is a lot easier for me to reason about personally. This kind of approach with reframe just seems to make natural sense.

I try not to exert undo effort on performance until it’s clear that’s necessary. In the case of Precept, we could keep every state and chain off it because Clara’s sessions are immutable. Heck, we still may decide to do that…never really tried. But for a long lived application that seemed to entail memory use that would grow without bound, and without some kind of equivalent to Datomic’s “excision” (forgetting history) we decided transforming the same state was enough to start out with and suited most use cases.

I’d be curious to know the exact cost and with structural sharing and garbage collection it’s hard to get some kind of reasonable idea without perf tests as @DjebbZ mentioned.