Fork me on GitHub

@manutter51: having smaller subs and separating them is the more common approach. I found using virtual namespaces and separating by that really helped me organize things.


Virtual namespaces as in namespaced keywords you mean?


@fenton Rich Hickey has said "Programmers know the benefits of everything and the tradeoffs of nothing". Your approach certainly has benefits. But there's a dark side to that decision.


Your components end up knowing all about the structure of app-db


That means they get fragile WRT to changes in app-db.


To put that another way: when you change the structure in app-db, you have to go to the components and change them.


@mikethompson I'm not sure I consider that to be bad


or at least that bad


It is certainly one of the design goals for re-frame that components know nothing about the structure of app-db.


its kind of like having a function take an argument that it expects to be a certain way...but if that data structure changes, yes the function will not continue to work....


i actually like being very clear, explicit with what my data looks like...


i guess I dont really value that indirection...


If it works for you, all good. Just pointing out that there is most definitely a tradeoff here. And, given the sort of applications I build, it isn't a tradeoff that I would take.


sure, fair enough.


here is a cleaned up version of giving a data only reaction for non-UI events


@manutter51: Probably - docs call them synthetic namespaces at the bottom of


@fenton: If the only way to use re-frame is setting values and getting them from a certain path, ratoms (possibly combined with a core.async channel) should be enough and re-frame is in your case unneccessary complexity. As a newbie in Clojure, I'm curious to know, why you decided to use re-frame and not just ratoms?


I agree @mikethompson that, at least ideally, components dispatching events or subscribing changes should know nothing about app-db internal structure.


To me this argument always seemed moot. When my app-db structure changes then in 90% of the cases it's because the functionality it represents changes, so the components that use it change with it. I'm using a :set handler and a :get subscription as well. Without that I'd just invent a second name for the path into app-db with no perceivable benefit. I'm curious to know in what specific situations people find hiding paths into app-db behind a subscription or handler name beneficial.


It feels a bit like the promise of encapsulation and black-box objects of OOP, that never came true for me. Almost always you have to look at the inner workings of the black box to use it correctly, and more often than not you have to change the black box to adapt it to new requirements. Pretending there's something I can and should hide from the user of the API has usually made it harder to make changes to the code.


@mbertheau - Of course both our accounts are anecdotal but I find the opposite. Encapsulation and data-hiding have served me well over the years writing OOP and I can completely see how hiding DB structure would be very useful too


But you might be somebody who plans ahead better than me ^^


@mbertheau see the todomvc example


@shaun-mahood Cool, I hadn’t looked at that page yet, thanks!


@mikethompson Yeah - that looks like sensible examples.


Still, I wouldn't go as far as discouraging all uses of a :get subscription and a :set event handler with this argument.


I would add a datapoint to a :get/:set debate here: I too asked about this in this channel a few weeks ago, got a similar reply from mikethompson and implemented :get-in sub in my second re-frame app anyway :) however, after few weeks of that second webapp development I've noticed two things: — as your app grows and you have more complicated/optimised DB and subscription structure (e.g. you're minimising re-drawing of a big table), the relative amount of "dumb" :get-like subscriptions becomes very small (I've actually found myself removing some "dumb" subscriptions in favor of more involved ones) — indeed there are cases (I would say around 20-30% in my case) when I was able to refactor/rewrite/optimise DB and subscriptions without touching my views So yeah, :get is conductive to somewhat less-than-ideal code, so I personally would think twice now before introducing it


However, there is another pattern that bothers me a bit and that is normalisation of app DB (and joining data back together). AFAIK it's handled nicely in (@am-a_metail may correct me) and is almost completely absent from re-frame docs and code


The topic is partly covered by , but it would be nice to have a canonical solution/naming scheme (smth like "save foos to :foo/by-id (a map) and their IDs to :foos (a vector) if you want to preserve order"), too!


@si14 can you elaborate on what you mean by normalisation of app db?


@danielcompton: from's page:

(def init-data
  {:list/one [{:name "John" :points 0}
              {:name "Mary" :points 0}
              {:name "Bob"  :points 0}]
   :list/two [{:name "Mary" :points 0 :age 27}
              {:name "Gwen" :points 0}
              {:name "Jeff" :points 0}]})
 [[:person/by-name "John"]
  [:person/by-name "Mary"]
  [:person/by-name "Bob"]],
 [[:person/by-name "Mary"]
  [:person/by-name "Gwen"]
  [:person/by-name "Jeff"]],
 {"John" {:name "John", :points 0},
  "Mary" {:name "Mary", :points 0, :age 27},
  "Bob" {:name "Bob", :points 0},
  "Gwen" {:name "Gwen", :points 0}, 
  "Jeff" {:name "Jeff", :points 0}}}


@danielcompton sure! Let's say you can get a list of users' posts from an API. The idea is to store messages as a separate map (e.g. in :messages/by-id) and to have e.g. a vector of message IDs in a [:users :messages] path. There are two benefits here: 1) because there is a "canonical" place to store messages, it's fine to reference the same message from two places in the state 2) you can preserve order and also have an efficient retrieval by ID


does it make sense?


the latter is pretty big IMO, because unless I'm missing something there are only three ways to preserve API ordering while having an efficient retrieval: 1) save the same data twice as a vector and as a map. It's fine efficiency-wise b/c of shared references, but now you need to update data in two places 2) add an index into each "object" in the list and use sorted-map-by. It feels somewhat cumbersome to me 3) store everything in a map by id and also store a vector of IDs


here is an example:


of course it can be the case that this is the wrong way to do things 😕


@si14: i use the map-by-id and refs approach a lot with re-frame... you don't get any particular help from re-frame, but it's straightforward to unpack api responses before applying them to app-db and re-join in a sub... i could see a lib helping, especially with more complex aspects such as cache invalidation


@mccraigmccraig totally! I'm just wondering what's an "official" stance on the topic :)


My reasoning, why you should avoid generic event and subscription names such as :get, set: is partially same as @si14. You can also find good reasons to that from re-frame's readme. As my reply is a bit verbose, I enter it as a post…


Will save this one ^ thanks!


Very nice write-up! Maybe this can go in the re-frame wiki?


Sure. From my part, why not.


@si14 open an issue on the re-frame tracker for this, it’s worth discussing. Not sure what form it would take, suspect it may be a library, but it’s always good to be able to take good ideas from others 🙂


@ilmirajat great write-up, add it to the wiki


it’s something that comes up in discussion a fair bit, so it’d be great to have something to link to