mpdairy / posh

A luxuriously simple and powerful way to make front-ends with DataScript and Reagent in Clojure.
Eclipse Public License 1.0
460 stars 45 forks source link

Integration with re-frame and subscriber abstraction #4

Open kristianmandrup opened 8 years ago

kristianmandrup commented 8 years ago

Looks like Posh would fir perfectly in combination with Reagent and Reframe, however from the docs in your readme you state that the Posh methods are intended for use inside the Reagent components themselves, where as re-frame is based on subscribers which subscribe to changes in the global app state (via queries).

What are your thoughts on this? Can posh be used with re-frame? Any examples of such integration? Thanks :)

mpdairy commented 8 years ago

Well, Posh's pull and q functions return reactions, so you can use them anywhere in re-frame where you would use a reaction, including in your subscription handlers. If you wanted to go that route, you'd use (subscribe [:attrib-you-want]) in your component and then use a pull or q inside your subscription handler for :attrib-you-want.

But why do that when instead of using the limited query syntax of subscribe you can use q or pull directly from inside the component and have all the expressiveness of datalog or the pull-syntax?

So I think Posh's pull and q can just be thought of as advanced subscribe functions that are more expressive and don't necessitate the writing of separate subscription handlers. Can you think of some advantage the subscription model has over using pull and q directly in the components?

kristianmandrup commented 8 years ago

Hi @mpdairy ;) Thanks for responding so quickly. I forked the repo, looked at your code and I was glad to see there would be no problem using the functions outside Reagent components. I'd like to avoid polluting my components with too much logic.

I prefer the subscriber notion since it makes the intention clear (by name) and introduces a level of intermediation for increased flexibility/reuse (in case same subscriber logic is shared between multiple components) etc.

I'd like to extend your ToDo app with re-frame integration and start experimenting with the new datsync lib as well at some point (when it reaches more maturity and has some docs/examples) ;)

Besides re-frame adds so much more to the equation, such as a very sane flow/cycle of event/signal streams and chain reactions etc.

Having them as separate functions, also makes it easy to add "middleware" etc. to wrap them with functions, build them via macros etc. Having this logic "locked" inside a component will become a pain/limitation for more complex systems IMO.

Cheers!

ghost commented 8 years ago

How has this been going @kristianmandrup? I am writing a medium-ish reframe app in my spare time to evaluate its potential for enterprise scale apps down the road. Datomic has always interested me, but I've never found a matching use case until reagent/reframe.

I agree with your points about decoupling pull (some complex processing) from subscribe (just give me the data, I'm a stupid component). Any major hiccups / glitches along the way?

kristianmandrup commented 8 years ago

@aft-luke I haven't yet tried... I recommend taking the posh todo app, strip away as much as you can and go from there. Then polish the UI as well, perhaps using the datascript-todo app as a baseline. Might wanna look at: https://github.com/kristianmandrup/datascript-tutorial/blob/master/Todo%20app.md

I'm writing a book on Datascript + friends ;) Feel free to contribute!

mpdairy commented 8 years ago

@aft-luke, another person, @handojin, mentioned they were using posh with re-frame and re-com successfully

kristianmandrup commented 8 years ago

@mpdairy @aft-luke Please reference whatever sample repo/projects using these kinds of combis. Thanks :)

mpdairy commented 8 years ago

he mentions it at the bottom of this thread: https://github.com/mpdairy/posh/issues/5

handojin commented 8 years ago

Sorry. The thing I'm working on is not public but FWIW I'm doing nothing 'special', just using posh/pull and posh/q inside the re-frame subscriptions & datascript/transact inside the re-frame handlers. Seems to work well but I haven't tested perf. etc.

If you like I'll see if I can whip up a quick demo repo.

kristianmandrup commented 8 years ago

Thanks @handojin :)

aranhoide commented 8 years ago

FWIW I actually prefer Posh's way to handle subscriptions.

The only thing I kind of "envy" from re-frame is the indirection and declarativeness added at the event/mutation step. That seems like it would help with testing complex interfaces. It also makes it more natural to do the right thing when performing requests to the server: that is, perform the ensuing mutations on the db at the time the request completes, not on the db at the time when the request was started.

I imagine that would be very easy to implement on your own, so I'm not sure it merits library support, or that this library needs be Posh anyway. For some projects it may make more sense to just perform the transactions in the event handlers. Posh makes time travel and undo easy even if you do this; just keep references to old dbs.

handojin commented 8 years ago

De gustibus, non est disputandum :)

kristianmandrup commented 8 years ago

@aranhoide It is my general experience (and good practice) to keep things separated in order for easier refactoring, reuse, testing, overview etc. etc. Of course it is always a balance. Break em up when it makes sense, keep em locked up when they are simple enough together...

aranhoide commented 8 years ago

@handojin @kristianmandrup: true. I'm not arguing against integrating with re-frame. I think, though, that such integration can be layered on top of the existing interface. Whether that is done in a different project or as an optional part of Posh seems less important to me, but I think the current interface should be preserved either way.

I like re-frame's choices, but I deeply appreciate the fact that Posh seems less opinionated. For what it does, Posh's interface seems kind of inevitable, and a great building block for more opinionated packages.

metasoarous commented 8 years ago

FYI, I've been extracting something that looks a bit like re-frame for posh as datreactor, part of the datsys/datview stack. Initially it was going to be bundled with datview, but I realized it might be something people would want to use on it's own, and that would also help make datsync more automated on the client side, without requiring datview necessarily. So check it out if you're interested. Note though; It's really just a mock of re-frame. I didn't like some of the decisions re-frame made about how it tracked state, and wanted something more component friendly. Ultimately though, I'd like to see if we can get these things to plug in more directly and simply/easily to re-frame itself, since there seem to be a lot of things building around that now.

kristianmandrup commented 8 years ago

Cool :) Thanks mate! I just got back from 3 months in America. Now I have time to work on this again...

metasoarous commented 8 years ago

Awesome! Cheers!

Conaws commented 7 years ago

Just posted a quick outline on the wiki of how I've been approaching integration. Thoughts, questions and criticism welcome https://github.com/mpdairy/posh/wiki/Re-frame-Integration

denistakeda commented 7 years ago

I've spent a lot of time with this issue and want to share it now. For integrate posh and re-frame we need only the ways to write subscriptions and handlers.

Subscriptions:

(reg-sub-raw
  :task-ids
  (fn [_ _]
    (q '[ :find  [?tid ...]
          :where [?tid :task/title]] conn)))

;; Usage
...
(let [task-ids (subscribe [:task-ids])] 
   [:span (str @task-ids)])
...

We have to use reg-sub-raw here because posh returns reaction and we don't want to wrap it one more time. If you are going to use re-frame database here you have to deref it also You can use the same way for pull

For write handlers I've introduced an effect and coeffect:

(reg-fx
  :transact
  (fn [datoms]
    (transact! conn datoms)))

With this effect one can execute transactions in re-frame way, declaratively It allows to write handlers in this way:

(reg-event-fx
  :update-task
  (fn [_ [_ id path value]]
    { :transact [{ :db/id id
                   path   value }] }))

;; Usage
...
[:button { :on-click #(dispatch [:update-task task-id :task/title new-title]) }]

If you want to get DataScript database as a parameter in a handler you can declare co-effect

(reg-cofx
  :ds
  (fn [coeffects _]
    (assoc coeffects :ds conn)))

and use it in that way:

(reg-event-fx
  :event-name
  [(inject-cofx :ds)]
  (fn [{:keys [ds]} [_ id path value]] ;; Now you have DataScript connection here, you can find any data you need but please don't transact anything
    { :transact [[:db/add id path value]] })) 

For more details, you can look at my repo / subscriptions / handlers

@Conaws This solution allows one to not inject Datascript database into the re-frame but even have DataScript as a single source of true. The problem with the regular reg-sub is that re-frame automatically wrap the result into the reaction and as far as posh returns reaction itself we will have to deref it twice.

Hope it'll help

denistakeda commented 7 years ago

Here the helper function

(defn reg-event-ds
  ([k interceptors handler]
    (reg-event-fx
      k
      (into [] (concat [(inject-cofx :ds)] interceptors))
      (fn [{:keys [ds]} signal]
        { :transact (handler @ds signal) })))
  ([k handler]
     (reg-event-ds k [] handler)))

It will allow us to write handlers in this way:

(reg-event-ds
  :update-task ;; Event name
  (fn [ds [_ id path value]]  ;; Function that gets datascript database and signal
    [[:db/add id path value]])) ;; and returns transaction 

It works exactly the same as re-frame reg-event-db except the first parameter is a dereferenced datascript database and the result is a transaction that is going to be commited.

denistakeda commented 7 years ago

I've just released a library that allows combining re-frame and DataScript together DataFrame