-
-
Save appsforartists/3fbb573ee46239c42ed5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Funx Proposal |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Composable Flux:
Using the concepts of functionally-reactive programming to make Flux ambidextrous
Collections + Selectors
As an industry, we've made digital incarnations of newspapers, shopping catalogs, mailboxes, image galleries, and many other artifacts of the urban world. Although in meatspace these are all very distinct concepts from one another, their digital counterparts all embody a single paradigm: show the user a list of {articles, products, messages, images} and allow the user to choose one to see in more detail.
You can model this relationship as the intersection of a collection and a selector. The collection represents all the known objects and the selector tells you which one(s) you're interested in:
The selector might be determined by the URL:
Or, it can even come from another collection:
Combining data sources to make new data sources is the hallmark of a technique known as functionally-reactive programming. Here's what the first example might look like with FRP:
If either
products
orcurrentProductID
changes, that function will be re-run. If its result has changed, any other stores or components that depend on it will receive the new value ofcurrentProduct
. They, too, will update, propagating their changes throughout the system.By applying this technique to the Flux architecture, we can make our codebases both simpler and more expressive, while also making them more easily isomorphic.
Advantages to This Approach
On the legacy web, when you search for a product, open your inbox, or go to the home page of your favorite news source, you're presented with a list of options. A typical product listing includes the product's name, price, photo, rating, and manufacturer. When you chose one, you'll be taken to a details page that prominently features each of those bits. Even though you already have most of the information you need to render the details page, the server has no way to take advantage of this. Before you can see the details page, you'll have to wait while the server looks it all up again.
With a well-designed architecture, we can completely eliminate this wait: the details page will render immediately with the data it already knows while it progressively loads the rest. Combine this with some animated sleight-of-hand and a reasonably fast data source, and the user won't even notice the bits that loaded progressively: your site will feel instantaneous.
2. It's crawlable.
The simplest way to achieve instant rendering is with a so-called Single Page Application. In this model, instead of serving an HTML page, the server returns a bunch of JavaScript that creates pages dynamically using DOM manipulation and the History API. When all your clients are modern web browsers that speak JavaScript and understand the DOM, Single Page Applications suffice. But, sites on the public web shouldn't make that assumption, because we have an ecosystem of search engines and archival tools crawling the web, and they often won't know what to make of a giant ball of JavaScript.
The architecture that enables all that instant-render goodness without sacrificing SEO is called isomorphic. It renders the first page on the server and subsequent pages on the client. To do so, the server needs to know precisely what data to load and to detect when that data's been loaded before it can render. Unfortunately, most Flux architectures don't start retrieving data until after the first render. When data retrieval is coupled to rendering, isomorphism is impossible.
By making Flux functionally-reactive, the solution becomes really simple: make a
readyToRender
store that composes the others:Conceptually,
readyToRender
is just another derived data source; it looks at the URL and the current state of the stores and returnstrue
when all the data that's needed to render that URL has been fetched. With a little sugar to make it more concise and declarative, it's a really nice way to solve the isomorphic problem.3. It's super expressive.
In the days before Flux, declaring a variable was a one-line endeavor:
Other Flux implementations add a bunch of boilerplate, but functionally-reactive Flux is just as simple:
To make isomorphism effective, the server needs to marshall the data it loaded to the client. As you might imagine, serializing all the stores and sending them alongside the rendered HTML can add significant weight to your responses.
In a functionally-reactive architecture, it's easy to tell what data came from the network and what can be derived internally. By serializing only the external data, we minimize our impact on the response size; everything else is automatically recreated on the client.
5. It can support some fantastic tooling.
One of the great things about functionally-reactive programming is that it's declarative - your inputs and outputs are all clearly defined in a way that's easy for tools to parse.
Because data isn't always coupled to UI, debugging is traditionally a tedious process that relies on breakpoints and
log
statements; finding the precise point where things started to go awry is a lot like finding a needle in a proverbial haystack. With a functionally reactive Flux architecture, we can add a tab to the Dev Tools that automatically visualizes your data as it flows through the system. When you can actually see what you're doing, mistakes are a lot easier to find.Overview of Existing Methodologies
Let's see how these concepts work in two of the leading Flux implementations, Facebook Flux and Reflux.
Facebook Flux
For as many words have been written about Flux, it's much easier to understand with a concrete example. For that, we turn to Ian Obermiller's NuclearMail. NuclearMail is an excellently-written sample application that demonstrates the techniques Ian's team used to port the Atlas ad server's web interface to React and Flux.
In NuclearMail, data is loaded by the DependentStateMixin: When a component is rendered, the DependentStateMixin uses its component's state and props to fetch data from the appropriate store. If the store can't fulfill the request, it tells the API adaptor to go fetch that data. When the API receives data, it publishes a message on the Dispatcher and tells the store. The store updates itself, which tells the DependentStateMixin to update the component's state.
In this implementation, the DependentStateMixin serves the same purpose as the selector in our proposal, but with a crucial difference: it lives in the component, outside the purview of the Flux architecture. This causes a few problems:
The DependentStateMixin is an elegant solution to derived data for Facebook Flux, but as these limitations show, there's still room for improvement.
Reflux
Reflux is the most popular alternative to Facebook Flux. It's also a bit simpler. In Reflux, stores listen to actions directly, with no dispatcher to intermediate. Stores can also listen to other stores. In this way, they are more composable than their Flux counterparts. For instance, here's the Reflux version of the
currentProduct
example:That's an awful lot more boilerplate than:
We had to manually keep track of
this.products
,this.productID
, andthis.state
; we also manually calledtryToUpdate()
every time a dependency changed. Bugs happen when you overlook those little details.Finally, Reflux doesn't enforce any constraints around what you
trigger
. When you callthis.trigger(newState)
, it ought to automatically store that value asthis.state
. Not only would that eliminate some of the boilerplate, but it would also give you a consistent place to look up the store's last value. This is important for isomorphism - when it's time to transition from the server to the client, serialize each store'sstate
and deserialize them on the client.These are not insurmountable problems - they could be fixed in a future version of Reflux, or with a wrapper library. But if we're going to take the time to create a wrapper library, maybe we should be teaching an FRP library about Flux rather than teaching a Flux library about FRP. That way, we'd get
map
,filter
,combine
, and all the other functional operations for free.Relay and Falcor are coming - why bother reinventing Flux?
Polishing Flux at this point might sound like building a better buggy whip in 1909. Facebook has publicly teased Relay as the successor to Flux. Netflix will be open-sourcing its data layer Falcor imminently. Why improve Flux now when these are "coming soon"?
Both Relay and Falcor expect to be paired with a server that implements its own corresponding API. For the foreseeable future, many web applications will need to be able to consume data from an arbitrary API. So long as that's true, there's room for a better Flux to coexist with Relay and Falcor.
How do we make this A Thing?
As I've hopefully articulated, there are some clear advantages to be achieved by bringing together functionally-reactive programming and Flux. However, this is not an announcement - it's merely a proposal. There are still plenty of open questions to be answered.
If this project sounds interesting to you: