top | item 7719957

Flux Application Architecture

235 points| sehr | 12 years ago |facebook.github.io | reply

87 comments

order
[+] wingspan|12 years ago|reply
From first-hand experience, I can say that React+Flux has scaled well to 8+ developers over 800+ JS files and ~60k lines of code in a large single page app here at Facebook. I'm happy to answer any questions! Some things that we've struggled with:

1. All data should be kept in stores. You may have some local component state, but it shouldn't be anything you want to persist if the component is unmounted. We have tried using state several times, and always go back to keeping it in singleton stores. It also works better with the action-dispatcher pattern.

2. Now all your data is in stores, but how do you get it into the specific component that needs it? We started with large top level components which pull all the data needed for their children, and pass it down through props. This leads to a lot of cruft and irrelevant code in the intermediate components. What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components. Since most of our data is fetched asynchronously and cached, we've created mixins that make it easy to declare which data your component needs, and hook the fetching and listening for updates into the lifecycle methods (componentWillMount, etc).

3. Actions don't have callbacks, as they are by design fire-and-forget. So if you need to be notified when some item has finished being created, for example, you need to listen for the follow up action that the CREATE action fires (yeah, actions firing actions, a bit ugly). Even then, how do you know that CREATE_COMPLETED action correlates to the CREATE that you fired, and not another? Well, actions also come with a payload, so what we ended up doing was passing a context object into the payload and plumbing it all the way down into the CREATE_COMPLETED and CREATE_FAILED actions. Being really strict about actions is a major reason why Flux has scaled well for us.

[+] rattray|12 years ago|reply
Could you explain how one might modify a Backbone+React application to follow the Flux model? One still needs models, no? Does one simply "wrap them in a store", which passes information to the UI, rather than having them come directly from the models? Or do you get rid of backbone entirely, and... then where do you store/manage/sync your data? What's the correct model for communicating with the server?
[+] couchand|12 years ago|reply
Thanks for the encouragement! I'm using React to build a new project at work and so far I've been very satisfied with how much it gets out of the way.

Perhaps promises would be a good solution to your action spaghetti? They can deliver progress, completed, and error messages targeted to the place that cares about them, rather than passing context through the entire rest of the system.

[+] arobbins|12 years ago|reply
Om[1], a ClojureScript wrapper over React, solved problem 2 with Cursors[2]. Each component gets a reference into the data store, and can update its own data using the cursor almost transparently. In this case, it isn't that components declare what data they need, but their parents provide them with a cursor when instantiating them.

[1] https://github.com/swannodette/om [2] https://github.com/swannodette/om/wiki/Cursors

[+] terhechte|12 years ago|reply
I'm currently using React with ClojureScript/Om and I really like working with it. However I'm never really sure how to model non-persistent ui changes. I.e. a user clicks a button that sets the display property of the target note to "block" so that more content is visible. I wouldn't want to keep this in the main store. So I'm pondering whether to store this in component local state and have the change appear with a React rerender, or whether it is fine to simply access the DOM and set a different style property? (i.e. getElementById...)
[+] spicyj|12 years ago|reply
> What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components.

How does this interact with shouldComponentUpdate? Generally it seems that when moving data out of props/state, it's harder to take advantage of the performance hooks that React gives you because you don't have the old and new data to compare when rerendering.

[+] kaonashi|12 years ago|reply
How do you handle multiple stores which interact with asynchronous behavior?

In the dispatcher, it looks like each callback is executed in order if you use waitsFor, but they do not wait for any aysnc behavior to complete.

Do you have stores register listeners with each other during their dispatcher-fired callbacks?

[+] dustingetz|12 years ago|reply
do you just not use react state at all then? I have a similar sized app as you, we started off naively using state at various levels, over time we refactored the state higher and higher, and now we are at the point where all the state is kept only at the root of the view hierarchy (we use cursors). We're about one step away from lifting even that state out of react and into a store layer that has no react dependencies.
[+] lhorie|12 years ago|reply
I really like this architecture - it's clearly based on lessons learned from the same types of pains that I myself encountered w/ non-trivial jQuery, and the unidirectional data flow makes it a lot easier to reason about the code. It's very similar to what I'm doing with my own micro mvc framework Mithril ( http://lhorie.github.io/mithril ).

One thing that raise my eyebrows about this article though is how says that Flux eschews MVC, and then goes on to say that it has stores that are "somewhat similar to a model", and "the dispatcher exposes a method that allows a view to trigger a dispatch to the stores", which, as far as classic MVC goes, is the exact definition of a controller. What they call controller-view is, imho, just an implementation detail within the view layer: in classic MVC, views were responsible for subscribing to model observables, or, as the article puts it, it "listens for events that are broadcast by the stores that it depends on".

When talking about this architecture with the Mithril community, I find that referring back to the MVC pattern makes it even easier to reason about the flow of data: it's basically M -> V -> C -> M ...

It's unfortunate that the general understanding of the MVC pattern became misunderstood over the course of many frameworks. The whole point of design patterns should be that we could just name them for people to "get" it, rather than having to reinvent new ways of naming things and having everyone relearn the nomenclature.

[+] m0th87|12 years ago|reply
Thanks for Mithril - we're experimenting with it vs react.js for production use. There's a lot of very smart design decisions there expressed in very terse code.

One thing that threw me off was startComputation() / endComputation(). It seems you have to be explicit about when properties are being updated for views to update. I worry this might be error-prone vs react.js - if you forget an endComputation(), or an exception occurs outside of a try/finally, your views will freeze forever, no?

[+] jingc|12 years ago|reply
Thanks lhorie, it's great to hear that you've come to similar conclusions. I found that https://tech.dropbox.com/2014/04/building-carousel-part-i-ho... also described similar concepts, which is really encouraging.

To address your second comment: One distinction between the dispatcher and controllers in the MVC framework is that the dispatcher generally doesn't contain any business logic. It's essentially a hub for passing messages through: all sources of changes get funneled through the dispatcher, and the stores listen for those events, but the dispatcher doesn't actually modify or initiate the events. In my talk, I described it as the traffic controller of the system, which might be a more appropriate description.

As things get more complicated, the dispatcher may do more things at the framework level (Bill refers to the dispatcher handling dependencies between stores), but it stays separate from the application logic. In other words, we use the same dispatcher for multiple apps, so it plays a different role from the controller.

Hope that clarifies a bit, I'll see if we can make this distinction clearer in the documentation :)

[+] acjohnson55|12 years ago|reply
Agreed. At some point, in a well designed app, you've got a data store, an I/O layer, and something that coordinates amongst the components. There are a million ways to decide where the "business logic" should live, whether application state and external data should be treated differently, or exactly how all the pieces are glued together. At the end of the day though, it's all pretty much the same paradigm.

It sounds to me like Flux goes with a model layer that handles all state (internal to the app and not), pushes most binding between pieces of the UI to the view layer, and uses a thin controller layer to mediate the other two layers and coordinate parts of the model layer with overlapping concerns.

I once had the epiphany that an entire web app is pretty much a whole bunch of nested or concatenated MVCs. I think the confusion arrives when people assume that there is one-and-only-one MVC and try to shoehorn all the parts of a new workflow into that rigid structure. I don't think that the proliferation of acronyms purporting to be something different helps with understanding.

[+] clarkevans|12 years ago|reply
The functional flux/react architectural style is truly excellent. Over the last few months, Andrey Popp implemented a declarative form engine using React and it's much simpler to reason about than our older JQuery equivalent. Having undo/redo emerge as an almost-free feature from this architecture is super useful.

http://prometheusresearch.github.io/react-forms/

[+] jingc|12 years ago|reply
Agreed, we found that property really nice too - optimistic updating and rolling back on error were similarly easy
[+] rdtsc|12 years ago|reply
I don't know web client development very well.

I could figure out jQuery back in the day pretty easily.

Then we started using Angular and I have tried and tried to understand all its concepts my brain just couldn't keep track of list of: dom transclusion, scopes, controllers, directives, services, dependency injections and so on. More experienced JS developers loved and had no problem picking it up.

But after watching a few videos about React.js and worked through tutorials, I really started to understand better. I really like the concepts and how this library is put together. So far this is looking really good.

[+] OliverM|12 years ago|reply
I like the approach to managing information flow that is outlined here, but it's over-stating the case to say that lack of such control is a failing of the Model-View-Controller architecture. There are patterns you're supposed to use with MVC such as the 'V' pattern, where information flows from the views via the controllers to the models, where the models update themselves, and then update the controllers which then update the views. Visually it looks like:

  View receives event-------\..../----views render new state
  Controllers handle event---\../--controllers mediate new state
  Models react to new data----\/--models update controllers
The Sproutcore javascript MVC framework espouses this, for example, and I'm sure many other MVC frameworks do too.

I'd be really interested to see how Flux would augment statecharts...

[+] OliverM|12 years ago|reply
I should have added - these are the only actions you can take at each level, so no arbitrary updating of views when you're in the left-hand side of the controller stage, for example. That stops the arbitrary circular information flows cited in the flux introduction.
[+] rattray|12 years ago|reply
Video seems to start discussion of Flux just after the 10 minute mark.

Direct Link: http://youtu.be/nYkdrAPrdcw?t=10m21s

EDIT: So far, the video is much more helpful to me in terms of bringing the concepts of Flux to life. Jing does a terrific job explaining in my opinion.

[+] shaohua|12 years ago|reply
Jing did an amazing job explaining Flux. She should do a lot of more of those talks.
[+] jamesgpearce|12 years ago|reply
Yeah, we should have embedded it to skip me, for sure :)
[+] blktiger|12 years ago|reply
Seems like the dispatcher is a more manual version of the Ember/Sproutcore run-loop. The advantage I see to the run-loop is that it batches all UI updates so they happen only once, where the dispatcher simply batches up model changes to reduce the number of redraws. Maybe I didn't fully understand what the dispatcher does though...
[+] peterhunt|12 years ago|reply
In this architecture the batching occurs at the seam between flux and react (setState) which can be flushed with an arbitrary strategy (ie the default react run loop, or famous, or your own etc)
[+] frik|12 years ago|reply
I intuitively designed a very similar architecture to Flux & React, last year. It's a lightweight PHP+JS framework. (used in this pet project: http://www.youtube.com/watch?v=-Lta5xSj4mY )

Is there a name for such a pattern?

[+] mattgreenrocks|12 years ago|reply
The Dispatcher part reminded me of the Proactor pattern from days of yore. This is a different domain, of course.
[+] porker|12 years ago|reply
Is your framework available anywhere? I'd be curious to take a look.
[+] sarhus|12 years ago|reply
Someone in the video asked a question about how AngularJS compares against Flux+React.

Anyone has a more detailed explanation?

[+] couchand|12 years ago|reply
I've been doing a fair amount of Angular development at work and recently been getting into React on the side (and now introducing it for a new project at work!). I'd say they have the same general goal - build your app with declarative and expressive markup. However, Angular tries to shoehorn custom components into the regular DOM, which gets problematic in many cases.

For instance: the directives `ng-show` and `ng-hide` simply apply CSS styles. As far as I know it's not possible to completely remove an Angular component from the DOM and then replace it later (without getting too deep into imperative JS). However, that's trivial to do in React.

Angular chokes on large data sets, probably because it's doing everything directly on the DOM. React handles huge numbers of records effortlessly. Getting comparable performance with Angular requires much more imperative voodoo than I'd like out of an ostensibly declarative system.

I also find it harder to reason about Angular's data flow, since there are many ways to pass things around: nested scope, isolate scope, sibling scope, same scope, dependency injection to name a few. Finding where a particular handler or value comes from is sometimes quite the hunt. With React, it's all pretty much self-contained.

[+] gbrits|12 years ago|reply
Watching the video I heard the speaker say unit testing is rather easy, since state is always sitting next to code in your components. This is great and sounds logical: test different input states and check against expected / consistent output.

What I'm wondering, given the unidirectional flow and the design of flux as a whole: would Integration Testing be needed at all anymore?

I mean, there's no state outside of the component that could possibly influence the component's consistency. Therefore, all needed testing could be done by simply unit-testing all components in isolation.

That sounds pretty huge.

[+] mmerickel|12 years ago|reply
It seems you could just pass the stores into the components as data - that's really what they represent. I'm not clear why this approach is eschewing that basic principle in favor of singletons.
[+] jingc|12 years ago|reply
The stores contain the data, but they also include the logic for updating that data. For example, a store of all the comments would also subscribe to events for new comments and add them into the comment thread at the correct location. We've found it more maintainable to keep the application logic for a set of data and the data itself in one place rather than having a model that external pieces can modify.
[+] modarts|12 years ago|reply
Same here: i'm not seeing the value of the additional indirection.
[+] swah|12 years ago|reply
Can this architecture implement the client-side offline update while network is down? (User clicks a "Like", its marked as Liked even if network is down...)
[+] jingc|12 years ago|reply
Yup, we've used it that way, and it's easy to implement due to the fact that all changes are expressed as a payload object, which allows you to serialize actions. Optimistic actions (showing the Like when the server hasn't confirmed yet) are built the same way - the stores just need to resolve between the optimistic/offline update and the eventual server response.
[+] alisnic|12 years ago|reply
This architecture describes how data affects presentation and vice versa. Your question is a bit off, it involves implementation, not structure.
[+] vdm|12 years ago|reply
I think you could do this in a Store component which uses a ServiceWorker.
[+] hamxiaoz|12 years ago|reply
This looks good. I'm wondering has anyone combined react and meteor in a real project?