A cartoon guide to Facebook's Relay, part 4: How it all fits together
So far you've seen how:
- Relay lets you say what data you need with GraphQL in part 1
- Relay fetches data from the server in part 2
- Relay syncs changes back up to the server in part 3
Now let's see how all the different parts work together.
Caveat: this is only an approximation
In the cartoons up until now the scale of the diagrams has been 1:1… each arrow has (pretty much) represented a single function call.
For these diagrams, the scale is closer to 1:100. This is because Relay is doing so much for you.
Flux is a pattern. Relay is a framework. Flux just gives you guidelines on how to structure your application. Relay gives you concrete functionality. And lots of it.
This means that the number of different parts in the system has ballooned.
From the diagrams, it could look like implementing something with Relay requires a lot more work. The truth is, these extra characters take care of a lot of work for you. They are responsible for the magic.
You don't really need to know every detail of what they do. In fact, there's only a small number of parts that you even need to be aware of.
But cracking things open and seeing how they work is the fun part! So I'm going to try to give you a rough approximation of Relay's inner workings.
The chorus line of characters
Even leaving out some of the lesser cast members, there are still a bunch of them. In my head, I divide them into 4 groups.
The user interface
The user interface takes the data from the system and makes it understandable to the user. It also gives the user a way to change that data. It takes user input and turns it into something the system understands.
The parts of the user interface are:
The root container — Kind of like the root component in Redux, the root container is a CEO. It chooses the top level component to use for the page. It also tells the app where in the graph to start the query. But after that, it's pretty hands off.
Containers — The container is like a project manager for the component. It has a list of all of the data the component needs to do its job. The container is on top of getting the data from the rest of the system. This allows the component to focus on its job.
Components — A component in Relay is pretty much the same as it is in Flux. It is a presenter. It just knows how to format data into output that people understand (via HTML).
Relay Renderer — In some ways, the Relay Renderer is like the view layer binding from Redux. Like an IT department, it hooks up the container to the store.
Mutations —You saw mutations in part 3. They are like action creators in Flux, but they do more. They provide blueprints for the mutation system to follow for different user changes.
Data storage is handled by the store. Even though I'm only representing it as one character here, there are many different objects involved.
The UI talks to it whenever it wants data. It works with the query and mutation systems to make sure it has the data that the UI needs. The store also has a notification system. Each component gets custom-tailored updates when data it uses changes. The store handles all of the details of wiring up these notifications, so you don't have to think about it.
The query/mutation systems
Two characters sit on either side of the cloud: the server on one side and the network layer (which is on the client) on the other.
The server — As you saw in part 1, the GraphQL server is like a librarian. It isn't the library; it doesn't actually store the information. You tell the GraphQL server what information you need, and it figures out where to go in the stacks to get that information for you.
The network layer—The network layer manages all communication with the server. It is like an operator in The Matrix. It is responsible for getting requests into the cloud and making sure responses get out of the cloud. If the response doesn't come out of the cloud correctly, the operator will retry twice before pulling the plug. It also handles errors.
The first render
Let's see what happens when your app is first loaded.
Step 1: Starting the process
When the page is loaded, React starts rendering. The first thing it reaches is the root container. The root container passes off responsibility to Relay Renderer.
Relay Renderer is in charge of making sure the data is available to the UI. But because the app isn't mounted, it knows that none of this data is available. It tells the store to start priming the cache.
Step 2: Priming the cache
The store needs to know what data to get for the cache. Relay Renderer figures out what the starting point for the query is (the query root) and passes it to the store. It also gives the store a callback.
The store passes those off to the query system. Then the query system works with the store to figure out what data is already in the cache. We looked at this in part 2.
Step 3: Building the query
The query system builds the query.
I didn't show this in part 2, but the query system talks to the containers here. It asks them what data they need. Each container passes off a query fragment which specifies the data its component needs.
The query system stitches together the full query out of all of these fragments.
Step 4: Trimming down the query and queuing it up
As we saw in part 2, the query system removes pending requests from the query. Then it adds the query to the queue of requests for the network layer to send out (and also adds it to its own list of pending requests).
Note: things start to go a little asynchronous here. I don't have a good way to show this in the diagrams.
The query system is done with it's work for now. It queues up a message for the Relay Renderer with the ready state. But first, the network layer needs to do it's job…
Step 5: Sending the query to the GraphQL server
The network layer picks up the queue of requests that the query tracker left for it. It sends the requests off to the GraphQL server.
The network layer has nothing else to do ‘til the server responds. It lets the rest of the system do its thing while it waits.
The message that the query system put in the queue is sent. The Relay Renderer gets the ready state.
Step 6: Getting the data back and writing it to the cache
The network layer hears back from the GraphQL server. It transforms the response to JSON. Then the query system picks it up and passes it off to the store. The store adds it to the local cache.
Step 7: Notifying the renderer
Once the data is written to the cache, the query system is notified. It sends out the message to Relay Renderer with a new ready state, letting the Renderer know that the cache is primed and ready to go.
Relay Renderer will then prepare an object that the containers can use to get their data from the store. It calls setState(), passing this object down to the containers.
Step 8: Getting the data from the store to the UI
Using the object that Relay Renderer passed down, the container will set up a “resolver”. It will use the resolver to get its data from the store.
Note: This is handled automatically in the container prototype's componentWillMount() implementation.
During this process, the store also sets up some observers. These observers will notify the resolver, which will notify the container, whenever this data has changed.
Now that the container has the data, it calls setState() to pass it down to the component.
So that's how I think about all the different parts of Relay. Hope it helps!