reduxjs / redux

A JS library for predictable global state management
https://redux.js.org
MIT License
60.88k stars 15.27k forks source link

Investigate Falcor #560

Closed gaearon closed 9 years ago

gaearon commented 9 years ago

Falcor is out:

http://techblog.netflix.com/2015/08/falcor-developer-preview.html https://github.com/Netflix/falcor

Can we integrate with it?

banderson commented 9 years ago

@gaearon you read my mind, and I can certainly dive in to contribute here. Any particular aspect you're looking for help on, or just to dive in?

gaearon commented 9 years ago

Just curious how integration could look like.

bbirand commented 9 years ago

It seems that the integration with Falcor would be more straightforward than Relay. Just as one can use any data structure as the state, one should be able to use the Falcor model: http://netflix.github.io/falcor/documentation/paths.html

Pretty exciting stuff!

ajchambeaud commented 9 years ago

+1

The idea of one model every were in the form of a JSON graph sounds similar to the Redux state shape. It would be great to have falcor to manage the state across the network. I need to read more about Falcor, but seems like the Falcor graph is mutable

ekosz commented 9 years ago

Was just thinking / researching this today. The real fiction seems to be that the Falcor model is mutable and complex. While in redux, state is immutable and by convention plain old JS objects.

What may work is holding two pieces of "state" instead of one. One would be a plain old JS object managed by redux and the other would be a Falcor model. When the UI wants a new piece of data it asks the Falcor model to get it, passing the path promise to a redux action. But when it comes to rendering, the view only ever reads from data in the redux state.

When the promise resolves, redux merges the Falcor model's state with its own in a reducer. It seems Falcor already prefers data structures that almost look like like normalizr output with their ById structure. This may make merging the two states easier.

danmaz74 commented 9 years ago

While in redux, state is immutable and by convention plain old JS objects.

A bit OT, but is it really the default to use plain old JS objects for state instead of something like Immutable.js? I'm all in favor of giving a default way to do things - including, if it really works out well, using Falcor for server sync - but, it that's the case, then choosing what the default is should get a very high priority. I got the impression that using plan javascript objects was just to keep the examples simpler.

ekosz commented 9 years ago

You're absolutely right. I think I was just thinking of objects that look like plain old JS objects. But even that doesn't matter. As long as the state is a different object when it changes, one could use anything.

It looks like to get a new version of Falcor model every time one of its promises resolves you could call Falcor.Model.prototype._clone, but that is really reaching in to a private API. Maybe wait for a more public API?

johanneslumpe commented 9 years ago

@danmaz74 it completely depends on the size of the data you're working with. There is no need to use Immutable.js for everything. Plain objects can work just as well for a lot of use cases.

danmaz74 commented 9 years ago

@johanneslumpe I agree that plain objects can work well in a lot of use cases, but from an educational point of view, it would be a good idea to show readers of the docs what are the best practices. That is, of course, supposing there is a best practice. For example, if using Immutable is recognized as a best practice when your project grows, it would be a good idea to use it as default in the examples.

People are naturally going to assume that the examples show the best practices, wether you meant it that way or not :)

nhagen commented 9 years ago

This is off topic at this point, but just to add a counter-point: I really disagree with complicating examples with additional external libraries. If someone does not know the benefits and requirements associated with using Immutablejs already, pushing it on them will be counter productive to Redux's adoption and getting buy in from 'non-believers'. Targeting primary documentation towards advance users over newcomers is not productive IMO. Leave that for blog posts, tutorials, and advocates of these addon libraries.

Bringing it back to Falcor: same thing--there should be intros on how to ultimately integrate with this, but all example shouldn't be using falcor (if faclor is later deemed to be a good drop in replacement for the Store)

danmaz74 commented 9 years ago

@nhagen suppose that Falcor became the best practice to do synchronization with the server using Redux, and that it allowed you to simplify the examples compared to using API calls manually. If that was the case, it would IMHO be a mistake to not tell first-time users right away, and leave them to discover this by themselves through external resources, making their lives 10 times more difficult.

Anyway, I agree that I brought this discussion too much OT - sorry about that, I'll stop here.

cef62 commented 9 years ago

:+1: to investigating redux and Falcor integrations!

ekosz commented 9 years ago

If people are interested I hacked on this today. Here are the relevant files:

Basically the logic is this, components ask Falcor for the data they need but never read directly from the Falcor model. When the Falcor promise resolves, it is the reducers job to read from the Falcor model and merge in the relevant data into the redux state tree. Views only read from this tree. I think this allows all of the tools to do their jobs well without interfering with one another.

If you super interested you can watch the videos of me build this here here as I live streamed the process of figuring this all out.

nstadigs commented 9 years ago

@ekosz I really like your approach! I'm thinking that it would be easy to abstract this into a library that exports a reducer, the action creators and a higher order component for the lifecycle stuff :)

jonathan commented 9 years ago

So, has @gaearon's original question been answered? Can redux integrate with falcor?

yusefnapora commented 9 years ago

@ekosz thanks for the excellent example!

I was curious on your thoughts about mutations. It seems like you'd use essentially the same approach to set() as you're using for get(), where you'd have an action creator that accepts the path of the object to be mutated and the value to set. That action creator would then call falcor.setValue(), and when the promise resolves, the reducer would just merge the returned json into the app state, as with the RETRIEVE_PATH in your example.

Does that sound correct?

ekosz commented 9 years ago

@yusefnapora Yep thats sounds correct to me. I actually haven't dealt with modifying state just yet in my falcor projects. I'm still playing around.

Let me know if that works out the way we think it should!

fingermark commented 9 years ago

I'm kind of new to both redux and falcor. Can someone share the advantage of using both redux and falcor over just falcor?

And how would local UI state fit in here? Would your redux app state be something like {falcor: ..., local: ...} or would you merge everything together? I'm having a hard time figuring that out without knowing more about both technologies.

greim commented 9 years ago

@fingermark An advantage of keeping it all in falcor would be having the flexibility to move any aspect of the data to an off-client store with minimal rewriting.

volkanunsal commented 9 years ago

I've been working with Falcor recently. The main disadvantage I find is it doesn't support the database as a DataSource, i.e. you should only use it as a middleware to other APIs. The reason it doesn't support direct queries to the database (yet) is probably because the Falcor Router cannot aggregate over the database queries and run them efficiently, so you'd be making a ton of unoptimized database queries if you tried that. The best way of using Falcor is to aggregate multiple API endpoints, say from multiple microservices.

The Redux app with Falcor would be probably less heavy on reducers. I have 5 reducers in an app where I'm not using Falcor. When I switched to Redux, I simply replaced stores with reducers, but that creates a lot of friction.

gaearon commented 9 years ago

Closing as not directly actionable, but feel free to continue the discussion.

ekosz commented 9 years ago

While I'm a little late to the party here, I was on a flight yesterday and figured I would publish my work as a node module. You can find the source here: https://github.com/ekosz/redux-falcor

The more work I do with redux + falcor the more I like it over relay + graphql. It just seems more flexible and easier to integrate with.

As an aside, that was also my first real NPM package I've published. Any feedback would be very welcomed.

wmertens commented 9 years ago

@ekosz so if I understand correctly, your package stores a copy of the Falcor data in the redux store, by way of actions that are dispatched when Falcor promises resolve?

The only problem I see with that is that Falcor implements cache size maintenance, and the redux copy won't know what to remove. It would be useful if a listener could be attached to Falcor on compaction.

However, Falcor seems to only be used to present an abstract JSON object where the data has to be accessed via promises, and thus any React application will have to store the data as a copy. Therefore, that is probaby the right approach.

Indeed, Falcor seems to be orthogonal to Redux, yey :) (unlike graphql) Definitely want to take a closer look at it.

One thing though, right now your package doesn't show in the state that a path is being fetched. How about e.g. making special object that you can use as a unique constant to show that a part of the subtree is fetching? E.g. const isFetching = { fetching: true } and then while waiting for a value in the json subtree you can set the subtree to isFetching and verify the state by doing state.falcor.foo === reduxFalcor.isFetching.

ekosz commented 9 years ago

@wmertens Yep thats the gist of things. I'm currently working so that there's less of a chance for the two data stores to get out of sync, buts a little tough. I don't just want store the falcor cache (model.getCache()) by itself. As its not expanded out. It still has all of its sentinels.

{ 
  users: {
    0: { $type: 'ref', value: ['usersById', 25] },
  },
  usersById: {
    25: { name: 'Eric' },
  }
}

What I would rather store would be:

{ 
  users: {
    0: { name: 'Eric' },
  },
}

Which is what my library tries to do. I think there's a nice solution out there, but I just need to dig a little deeper.

As for the fetching, the library does support that. Every require fires off a "request" action. Those look like:

FALCOR_RETREIVE_PATH_REQUEST
FALCOR_RETREIVE_VALUE_REQUEST
FALCOR_SET_PATH_REQUEST

And so on. I was thinking of users writing their own way of handling those actions, but I could just as easily update the reducer to set a top level value of entities.fetching = true or something like that.

wmertens commented 9 years ago

Under what conditions would they get out of sync?

I was thinking that maybe you should let Falcor handle the caching and in redux only store the currently needed data. So store currentMessage instead of all messages…

On Wed, Oct 7, 2015, 01:18 Eric Koslow notifications@github.com wrote:

@wmertens https://github.com/wmertens Yep thats the gist of things. I'm currently working so that there's less of a chance for the two data stores to get out of sync, buts a little tough. I don't just want store the falcor cache (model.getCache()) by itself. As its not expanded out. It still has all of its sentinels.

{ users: { 0: { $type: 'ref', value: ['usersById', 25] }, }, usersById: { 25: { name: 'Eric' }, } }

What I would rather store would be:

{ users: { 0: { name: 'Eric' }, }, }

Which is what my library tries to do. I think there's a nice solution out there, but I just need to dig a little deeper.

As for the fetching, the library does support that. Every require fires off a "request" action. Those look like:

FALCOR_RETREIVE_PATH_REQUESTFALCOR_RETREIVE_VALUE_REQUESTFALCOR_SET_PATH_REQUEST

And so on. I was thinking of users writing their own way of handling those actions, but I could just as easily update the reducer to set a top level value of entities.fetching = true or something like that.

— Reply to this email directly or view it on GitHub https://github.com/rackt/redux/issues/560#issuecomment-146031291.

Wout. (typed on mobile, excuse terseness)

rmahoney-bl commented 9 years ago

@wmertens I think you've got the right idea. As a newb here, it seems as if falcor replaces a library like isomorphic-fetch. Falcor's data / cache / etc is not becoming the application state, it's a middleware providing a layer of abstraction and a particular opinion on data fetching. How the state is modeled, managed, etc on the redux side doesn't change much nor does redux even have to know much about falcor at all. While using Falcor requires some modification to how the front-end application is constructed, it's biggest impact is on how the backend is written.

I think it takes a while to "get" Falcor. Same thing with React. What happens in a lot of controller-like pieces of applications is that they either make multiple calls to a granular REST api (bad) or each page has a corresponding backend where someone manually wires together the various responses of multiple operations into a single cohesive JSON wrapper (tedious, redundant). Falcor simply allows for the APIs to remain granular and diminishes the need for per-page backend data call "bundlers" through a single end-point API. With Falcor, you may be able to do away with the typical RESTful API altogether, and just have a well organized set of granular data fetching calls. Of course, you could still have a RESTful API if you need it, but for the purposes of a standalone react/redux application you probably wouldn't need them.

fxck commented 8 years ago

are there any followups on this?

ekosz commented 8 years ago

@fxck I just rewrote my library, redux-falcor, from scratch. The new version is still in beta, but it now stays in sync with falcor 100% and updates when the falcor cache updates. This now includes cache invalidations and expirations. I have yet update the readme / documentation, but I plan to in the next week or so. I've been using the new version in production for a bit and the two technologies play pretty nicely with one another now.

After I finish writing the docs / new tests I'll properly publish the new version.

greim commented 8 years ago

Probably worth tracking this thread also: https://github.com/Netflix/falcor/issues/572. Sounds like plans are underway to make the integration easier.

ekosz commented 8 years ago

As an update, I finally released version 3 of redux-faclor and have updated the documentation. It now makes the interaction between redux and falcor very smooth.

antonoberg commented 8 years ago

Great job @ekosz. Im looking into it now.

caiovaccaro commented 8 years ago

I'm joining the same party here (Redux + Falcor + React + Immutable). So far so good, but I've only seen (and able to do) good examples with GET operations but not POST (or model.set).

Any updates or ideas regarding that? Thanks.