Skip to content

Instantly share code, notes, and snippets.

@mfunkie
Created May 27, 2015 18:44
Show Gist options
  • Save mfunkie/65e94f42fee3962b1703 to your computer and use it in GitHub Desktop.
Save mfunkie/65e94f42fee3962b1703 to your computer and use it in GitHub Desktop.
Relay Talk Transcription by NPM
>> I work on products infrastructure team at
Facebook, and back in January at react day two of my
teammates Dan safer gave us introducing relay, which is
a data stream work. So today in this talk, going to
start with a description of relay for anyone who didn't
see this or who needs an infrastructure, and then I'm
going to dive into specific cards of the relay.
So let's start by thinking about how we at
Facebook were dining inclines a year ago. At that point
we had developed react, and which also buys a pattern
for one way flow flew application. And when we use both
inflow and react, we found we can move faster through
the process. But there was one big problem that neither
react nor flux really addressed. And that almost every
signs has faced at one time or another, which was what's
the best way to fetch data from the server and to
organize that data.
So let's look at how data fetching might work
in a application built with reaction. And I'm going to
use this example of a news feed story that I wrote. And
in particular it's likes and comments actually on the
bottom. So this like and comment box is -- was one of
the first parts of Facebook that we built with reaction
flux. It's a really big part of Facebook where you get
people from a bunch of different peoples coming in which
changing it frequently, so it's kind of a hot part of
Facebook.
So let's consider the example of introducing
stickers and comments, this is something that we
actually did last fall. Let's say that -- and this part
of the application our component tree consists of the
comment Bobs, which contains a comment list, which
contains a series of comment items.
So how did each of these components get the
data that it needs? The data comes from the server, and
then it will get passed down through the component tree.
So comment box is going to get some set of data and then
it will take a subset of that and pass it into comment
list. And then comment list will take Senate Bill you
sets of comment data and base past it into each item.
In this model, the most important thing to
notice is each is going to need to be aware so that it
can pass the data down. And then that server needs to
have the data required by every single component.
So, in other words, the implementations detail
of each component, the detail of what each component
needs are leaked up to the parents and for the
[Indiscernible]. Why does this matter?
Well, it means if you want to come along and
introduce sticker comments, you're also going to need to
change the comment list, the comment Bobs, and that
server end point. This is not cool when you need to
change all those files, especially when you have
different people coming in all the time making changes
and wenting to modify all the time. It turns into a
slower comment process.
And it can get pretty confusing when you have
the data fetch on the server. So let's say that you
look at your server end point, and you see that you're
effecting the birthdays. It's really hard to know at a
advance whether and where this birthday data is being
used in your application, on the client. So maybe
someone ran a test a few months ago and they wanted to
see what would happen if they wanted to add birthdays
for commentators, it didn't work that well, so they
removed that.
And now we have this over fetching situation
where we're loading that birthday, sending it to the
client, and then we're not doing anything with it, so
it's just wasteful. So maybe we're going to try to do a
good deal deed and clean up the server, remove that
birthday code without realizing there is a corner of the
application where we're still using the birthdays, and
now we have an under fetching bug, so we're going to
such the birthday and the things won't be there.
It's pretty easy to put these data fetching
pieces to get out of things like this and it will often
cause you over fetching or under fetching.
So how would this work in a perfect world?
Ideal if you wanted to introduce sticker comments, we
would just that need component. And more logically, it
would reside in just one place. The same place where we
do our rendering. This is the idea of the forward
relay. That's to make the development easier, we should
keep our data fetching and rendering in the same place.
Namely within the react component.
So rather than just continue logic render
itself like a transitional reaction component, a relay
component also contains a declaration of data that it
needs in the form of the data query. This way when
someone needs to make a small change, they can just find
the developing component, change the data theory, and
then they're done. They don't need to change the
components or touch the server. With the data acquirey
and the refreshing in one place witness it's easier to
know when you're finding the data that you didn't fetch.
So you're less likely to get that over fetching or under
fetching.
In order to achieve all these benefits of
putting the data queer into the component, you need a
common way for the components to declare their data
requirements, and this is where the FQL comes in. For
the last couple of years, our applications at Facebook
have been using it to set their needs. Let's look at an
example to see what it might look look like.
So let's look at the data that we might need
to render a comment off of here.
So we need the ID of the person, the person's
name, and then some data we need to render the profile
picture. So let's see what happens if we ahighlight the
field here and remove the values.
As you can see the query here not only
expressing the data that it needs, but also the precise
infrastructure that we want the response to that query
to have. The SQL is designed to be a thin layer so that
clients can get it without the whole server needing to
be rewritten. Another important feature of SQL is that
you can compose the theory, so, basically, one is built
up of others.
So this mean that if you take a component like
this application. On the one hand you can look at it to
build a view for the whole application. So each one
will render. In the same way, you can take the
component tree and look at it as a guide for how to big
a stroke for the application. Each parent component for
the SQL query is composed from the subsequently.
Each component contains a theory and a render
method. It's going to take the component if each method
and send that to the server. The server is going to
respond, and then relay puts our response data into a
single store, and then it will use that data to
construct top and then send it out to the corresponds.
At it's core, this is a flux application, the
only difference is it has this one single store to use
generic logic. Having a single store has a number of
nice benefits. So for one it reduces the need for a lot
of the flux boilerplate. It also helps the data
consistency across different parts of the application.
And finally it let's us build certain comment patterns.
Something that you guys have probably done at least
once, right into relay so that people don't need to
build them every time.
So now that we see on you relay works, let's
go back to this sticker sample and see how much easier
relay is going to make our lives. We just change
comment item. And more specifically let's say this is
part of our SQL theory, right now we're just doing the
test of the comment, and now we can also ask the
sticker, use that in our render message, and we're done.
We can build other stuff in the time had a we would have
spent changing those other files.
So relay resolves this big problem on how to
fetch data in a way that we found nicely for
applications being built with a big engineering team.
So the when you have those in the component, it's easy
for a lot of people to work on a lot of different parts
of an application at once.
So I can make my changes, my teammates can
make their changes, people on other teams can make their
changes, and we don't need to worry whether or not we're
stepping on owe each other's toes and what they're doing
at that exact time.
So far we've talked about the read passing
relay. But it would have limited usefulness. It give
users away to take action. Like, make making a story,
or liking my own story. So I have the term mutation so
far these actions are in a application.
In Facebook this would be something, like,
sharing a link, or poking a friend. And for the rest of
the talk, I'll focus on homotaxes energetic real life.
So to start, I'm going to go back and talk
about how we at Facebook are building these before
relay. So I joined Facebook a little over three years
ago, and I was working on the new product hear her. I
was working on the home page of the website. And I got
really familiar with writing mutations like this. I
would write some custom JavaScript and a custom server
end point. I would have the JavaScript call that end
point. I would have the end point return data and
basically whatever for mat I chose and enI would have
the JavaScript make sense of that data and up date
accordingly, usually by manually updating.
And then maybe I wanted to use that same owned
point from a different part of the client, I would need
to shove some logic in there to make sure that the
server was returning the data that the client needed,
regardless of where it was being called from.
And the key word here is custom. Basically
every time my teammates or I wanted to entered relevant
enter a new mutation, we had to start from scratch. I
personally really dislike this pattern of writing
mutations custom, it's all really representative. And
in my mind, this pattern becamesy no, ma'am, mouse,
because I had just started a Facebook, this is what I
did every time I would write JavaScript, so to me this
was JavaScript, so I came to myself as someone who
didn't like JavaScript and didn't to want write it. It
wasn't until I was introduced to flux that I don't just
like JavaScript, I just like this gross pattern.
So I would have have guessed three years ago
that I would now be on a team that I'm writing
JavaScript full-time or here with you guys at a
JavaScript conference but here I am and I'm excited
about it.
So back to Hughtation. We took a step in the
right direction when we introduced a more structured
API. So this was nice because it gave the client a
standardized way of what mutation they wanted to do and
also the input in the structured way. But what about
the data that the server runs? Each end point in the
graph is being used by a bunch of different clients,
there's not really a great way to make sure that the
server is going to return the data that the client needs
to update itself.
So what we would do instead is writing these
pretty minimal responses, just an ID to see if the
mutation was writing a comment, we would just get an ID
have the comment or maybe just saying this mutation is
[Indiscernible].
Is and so at that point there's two main
appearances for how you update the client. You can
guess, you can say okay. I've got this, this is how it
works, so this is how I think it is. Or you can do a
second round trip, go back to the server and get the
data that you needed to make sure that it's right.
The first option has issues potentially, the
second has a few with deficiency. So either one of
those is ideal.
Ideally the response from the server would
contain exactly the information that the client needs to
operate itself. So let's talk about liking a story.
Let's say we have this mobile client, and it shows the
number of people who liked the story. Then we would.
The server to [Indiscernible] so that we could update
the story correctly.
And in our web client, instead of just showing
the number of likes, we show this thing thatta "we" call
a like sentence, and it's an international string that
we generate on the server about who else, who of your
friends has liked the story. So for this, we would want
the server to have new like components so that we can do
the upgrade.
Okay. But then what if we wanted to change
the mobile client to also show the profile pictures?
Then we would want to make sure to update the server so
that it also retains the new liked picture so that we
can show that when someone likes it. And then maybe if
we eventually took out those pictures, we would need to
cleanup that server end point and get rid of the serve
he, and in this equation insinuation it would start to
look familiar to you. In that situation where the
server end point needs to be aware of the details of the
client logic.
When we change the client rendering, when with
you can change when we return from the end point of the
server, and it gets time-consuming to keep those two
instant.
Just like before, relay can help us solve the
problem. It turns out that it doesn't just
[Indiscernible]. So how would this worktook? You just
want to write a query and get a response. Mutations are
a little bit different. So you need to provide three
pieces of information. The type of mutation that you're
trying to do, any input that will usually just be an
idea to, and then the query for the data that you wanted
after that mutation has been performed.
So for liking a story, what would this be?
The type is a story like. The inputs that you need to
give are just the ID of the story that you're liking,
and then the query can be anything, but in this case
let's say we want to know if the viewer liked the story
so that you can know if you can make that that thumb
blue and then the number of people that like it.
So when we send this information to SQL, it
performs a write, and then it runs that query, and then
it will send this back to payload, which we can use to
update the client. Relay uses graph mutations for all
of its data rights, which performs a standardized way to
perform the rights and then update the client afterward.
So let's look back at these three pieces of
information. One of the more interesting problems is
that we face when developing this new stream work for
relay was how to decide what this query. So so our goal
here was to get the client data consistent with the new
post mutation state of the world. So we want this query
to be for anything that we had in the store for the
relay that could have changed as a result of mutation.
So one option, the easiest option is just to
have the developer write these manually. So in this
example, they would write this query. But then let's
say somebody came and added profile pictures to it. You
would send this information to the server, the right
would happen, we would get this response back, and we
wouldn't have the new profile picture in the right size
and this was the bug. To avoid that, the person who
made the profile picture would need to make sure that
it's in the query and add the profile picture. And more
generally every time someone came some rendering lodge
being I, they would need to go find all the potentially
relevant mutation queries and change them. This wasn't
a great option for us because with relay you make a
change somewhere and then make a change in a whole budge
of other areas Pennsylvania.
So instead we put the mutation into relay
itself.
So it's a set of data that can change as a
result of that mutation. So this is an independent of
what any client rendered, it's a property of the
mutation itself. So of story life or comment.
So here's a set of things that can Chang when
we do a story life. And if we always queried for
everything that can change every time we move in a
mutation, we would clearly end up in a consistent state,
but we might be manifestly over fetching since we never
rendered any of that data that we don't care about.
So we have relay for of the set of data that
the client has retrieved for that and ID put into a
store. So if we had rendered my story, maybe this would
be the shot of the data that we had shot from my story.
And then when you do a mutation on some story or some
ID, he re: lay builds the mutation query by enter sect
this set of things that can possibly change with the set
of things that it cares about.
This incurs in the exact amount of field that
needs to be updated.
So in this case you would end up with this
query here. And the really nice part of this is that if
someone something come along and let's say they replace
that three likes with the like sentence. Then relay,
what we've stored will know that we've now fetched the
like sentence for that story and so when we do it in a
section, the enter secretary of state theory will be the
server for the new liked sentence rather than the liked
[Indiscernible]
So those are the basic of how relay mutations
work. The user takes the action, we send the name of
the mutation into relay, and relay does that
intersection to figure on what the query should be, it
will send that over to the server, the server will
respond, we'll put that response in the store, and then
know the reviews by sending them to the top.
And you'll notice that this diagram is similar
to the one that I showed you before. In particular the
second part is identical in the two flows. The SQL data
store and then the [Indiscernible].
Between the secreted right flows just like
flux, relay is first class by using the same code to
both handle read and write.
So if you've used mutations before, when you
have the one working and the right on the server and the
client getting updated, there's often a lot more work to
be done. So maybe things, like, making the app feel
more responsive by having the client kind of doing
updates stainless. Or errors or resize. Because relay
has this have had framework, we're able to take a lot of
these common concerns and put them into relay themselves
so that they can get them for free rather than do them
again and again.
So let's start with this process example. The
way that I've described so far and then someone uses
this app and hit the like button, they noticed a
significant delay between when they hit like and when
the app actually changes because it's sitting there
waiting for the server to respond back.
It would be nice if we didn't have to deal
with this delay, and lucky for us relay provides the
supporter where we immediately under which date the view
to the effective post rate state being optimistic that
everything will work out nicely on the server.
Essentially to make this happen, you can
provide a payload mimicking the server response, and
then the view will change instantly based on that
payload. So that optimistic payload doesn't have to
include everything in that server update, it will
include everything you need to make feel right, so this
is we want it to feel good in between the server relay.
So maybe you can do something like this. That
would cause the thumb to turn blue, so maybe it's kind
of weird because [Indiscernible] instead of optimistic
where now you're also pulling the account by one and you
get the three likes and the blue thumb.
If I wanted to do an optimistic update like
this, I would to need add a bunch of code to manually
update.
or I could [Indiscernible] but in relay, I
just provide this optimistic field and then everything
else happened on that framework with automatically
updated.
So I want to describe now how these optimistic
mutations happen behind the scene. Even though the view
is changing immediately with optimistic update, relay
isn't immediately over writing the data. So instead we
maintain a Q of inflight mew takings, mutations that
we've done optimistic updates but the server response
doesn't come back. And when we read data from the
store, we read through that. So what does that look
like? Let's say this is the data in the store from that
stories.
So the UI is going to reflect behalf we have
in the store. And then let's say I do a like. So
you'll notice that the UI block immediately changed
blue, but the store, you haven't changed anything in the
store yet.
And now I do a comment. Again, because of the
optimistic update, the UI changes instantly, but the
store is still untouched. So now we give the server
payload, let's say the like succeeded. This is when
we'll actually remove the like from the queue and update
the story.
So now what if we get an error? So if we had
immediately written that comment into the store as soon
as I did it, this is when we'll be in a little bit of a
sticky situation because we need to roll back our
changes and makes that we left the store in the exact
same state as before. But instead of doing it with the
queue, all need to do is remove it from the queue and it
goes back.
The mutations queue also make it simple to
deal with retries. So let's say I'm trying to comment
and say thanks to my friends here. So I hit post and
the optimistic update happens immediately and then added
to the queue and then an error comes back. So in the
example I just showed you, it said there was an error,
take it out of the queue. But we can also keep it in
the queue but mark it with an error state. And then the
queue can pick up on this error state and show this
message, unable to post comment, try again. And then if
I do hit try again, it's easy to form that refry try to
if I have that sitting in the queue and it contains all
the data to send itself to the server again.
So I hit retry, and this time it worked and
voila.
So it provides a [Indiscernible] when someone
is performing a quick sequencing of mutations. So let's
say that I quickly like and unlike my story a bunch of
times in a row. There's a pretty high chance here that
something is going to go wrong if we just didn't have
any special mutation.
So there's a race condition for my mutations
in the server. If he ended up on a unlike, but the like
is the last one in the server, then we're going to have
the wrong data state on the server. And even if all
that works out, we have another payload for those coming
back. That if the last one coming back is a like, then
we're going to have a mutation state on the client. In
relay, we have a way to detect if these are independent
and guarantee that only one of them is in flight at a
time.
So the first one goes off and only when that
payload comes back do we send off the second one and so
on. So all the optimistic rates don't happen
immediately so the person not using the application
doesn't we're doing anything different behind the
scenes, but we do this to ensure.
So all of what I have described so far, relay
and mutation framework are already being used in
Facebook including or mobile app, both using relay and
react relay. I'm going to spend the last few minutes
here discussing a part of relay that we're still working
on that's not yet in production.
So this is a diagram that I showed earlier to
explain mutation. As I explained, replay lei can take
that mutation payload, store it, and then it send it.
So here the reaction is using the application. So that
doesn't have to be the case. So imagine if Joe comments
on my story from his phobe. You can actually use this
same pass once it goes through the cloud to send his
mutation to the relay, and then show his views. So we
call this a suband I want, basically, if I'm looking at
a new store, I can see all the comments on that store.
And it will provide a query saying here's the query for
what data I want for every new comment.
And then using a system in the back end, we
can ensure that all these mutations payloads are
delivered whenever someone comments and then relay will
automatically update.
So along with meteor or fire base, this
provides a simple way to build dynamic updates that feel
alive, and this is something we're excited about
innovating.
So I'm going to close with just a few last
points. Before the idea of relay, to just emphasize
that is that we should keep our data logic together with
our rendering logic because we found this approach to
really scale well to a big application being built by a
big team.
One of our main goals when we designed relay
was to identify these problematic Nantz people are
facing again and again and that are slowing them down
and to have the complexity of those patterns into relay.
So we saw a few of these examples today with relay,
thisth way we do that is the mutation query through the
way we have optimistic updates in the queue.
And in each case, someone using relay can have
this problem solve for free. So I'm going to close with
one last sticker comment. Thank you, all, for
listening.
[Clapping]
>> And if you guys have questions, I'll be
around the rest of the week. We'll be at our table out
there a lot of the time if you want to come talk to us,
we're excited to hear what you guys think. Thanks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment