GraphQL before GraphQL — Dan Schafer @ GraphQLConf 2019

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
but I'd like to talk about today is what graph QL was before graph QL because I feel like at this point the story of graph QL we've told it a bunch of times you know we've told that story of the initial prototype in February of 2012 and you know hacking in the corner and trying to figure out what this was going to be and eventually shipping it in the iOS app in August the open sourcing in July of 2015 and then of course where we are today but the story that we haven't told quite as much in the story that I think is really important to understand graph QL and why it exists and a lot of the decisions we made is that prehistory it's the you know probably four plus years leading up to 2012 because a lot of the architectural decisions we made there really made graph QL possible and if you look at graph QL today you can see the effects of those decisions on the technology and on the way it operates and so before I dive into this I do want to caution this is not the one true way of using graph QL this is not the way to do it and if you're not doing it that's crazy this just happens to be the Facebook server architecture in 2012 but the reason I think it's worth talking about is this was a Facebook server architecture in 2012 graph uol was built for this architecture and more importantly I think there are some good ideas in here some ideas that I think we're good in 2012 some ideas that I think are good today and some ideas that I think really sort of transcend just building a graph QL server at Facebook in 2012 to building things in general so that was a little bit abstract oh these are going to be some good ideas if you're working with graph field today why does this matter at all like what sort of things in graph QL does this inform well I'm gonna go back a little bit to philosophy because I actually think this philosophy really resonates with me and I think it sort of explains some of how graph QL operates you know there's a quote from Doug McIlroy this is the UNIX philosophy right programs that do one thing and do it well right programs to work together and when I think about the way that graph QL operates at Facebook and when I think about the way that almost the spec is designed there are a lot of questions that it very specifically does not answer it does not have a canonical way of doing authorization or efficiency or caching or rate-limiting which are all really really important questions and it's not that it ignores these it just doesn't address them and the reason why it doesn't address them is because these were already answered by Facebook server architecture in 2012 and so these concerns were not specific to graph QL graph guilded was not designed to solve these graph Yeol was designed to compose with a solution for these that we anticipate would either already exist or that you could bring into existence and so that's a little bit why I want to talk about this era because it does help answer all of those questions and explains how we were able to answer them without graph QL and therefore how graph QL was able to evolve as you know a rather small piece of technology in the stack one that's critical but one that doesn't necessarily try and answer every question it tries to answer one question and one question well and when I think about this era at Facebook I really think of three key architectural developments three key principles that both informed how we built things and that we emerged with from building things and those are these three this idea that data requirements form a tree the idea that you need to design around a synchronicity and our desire to strive for single sources of truth I'm going to walk through all three of these and give an example of how this emerged in Facebook and really build up that stack in a way that hopefully by the end you'll be looking and saying oh I can see how graph QL emerge from this so let's start with the first one this idea that data requirements form a tree and throughout this whole example or the whole presentation I want to have an example I want to make sure we're building something concrete so this is a very very basic sample app it is a sample app that has a user and it has a best friend and it has a list of friends and their best friends and I don't think I'm giving anything away here I'll have a lead to a Polish pass on at some point but this is the initial working design it's at least functional and if we were gonna try and build this you know how would we do it well we might just say let's throw it onto a sequel table and we'll query the data and then we'll build a view from there and it would look something like this you know we're gonna fetch the user's name we'll fetch their friends we'll fetch their best friend we'll join the friends on that and then we'll go and fetch their name and their friends name this would work we could build this by the way I'm also not a sequel engineer that almost certainly isn't that sequel it's fine the concept is there so why wouldn't we do this we're done right we've built it end of presentation well there are a couple of things that about this that just like don't quite click with me the first one is a little bit these joints because this assumes that we can efficiently run the joins across all of this and if this is all in one sequel database that might work but you can imagine as this grows too as a hypothetical social network billions of users maybe we're not just running one giant sequel database maybe we're gonna need to shard this across multiple DBS and suddenly these joins aren't looking quite as trivial but that's a very technical answer for why we shouldn't build it this way I actually think there's a conceptual reason why we wouldn't build it this way ought to which is that sort of sequence of joins that sequence of tables and how they connected isn't how I think about this app when I describe this app to you if I were gonna describe it I would say we're going to fetch the logged in user that's me and then we're going to go and we're going to fetch that person's friends and we're gonna fetch that person's best friend and then for each of their friends we're going to fetch their name and then for each of those friends we're gonna fetch their best friend this is how I start to think about the app this is sort of what gets created and it translates fairly reasonably into an underlying query I'll use Redis here as an example also not a Redis engineer but you can imagine the get going out again and arrange a bunch of more gets and a bunch of more gets and we can see if we were building it this way what would emerge what are the actual like Redis hits that we're going to do and one of the nice things that emerges from here is we realize that our data requirements for match free when we think about our data requirements when we describe our data requirements that's the shape that they take and I don't think that's a coincidence because when we think about our apps when we think about our views when we think about our interactions they very often also form a tree and that tree doesn't just describe the shape of our app that tree also describes our data fetching strategy and if I go back to this there are actually some really nice optimizations that we can do here you can imagine these five friends the ones that were fetching all at the same time there's actually no reason why we would do five round to a DB in order to fetch them we have all five of those ideas at once so it'd be really nice if we did that in a one round trip and we would use something like an M get in Redis and then similarly with these five friends we can do the same thing and we can optimize the heck out of this thing and if you're familiar at all with data loader this is exactly what data loader does in J s and the reason data loader exists is because there was a corresponding abstraction within Facebook called loader and we were talking with folks about graph you own they're like well how do you solve this problem and her answer was what do you mean how do you solve this problem this mom's already solved there's an underlying abstraction there's a thing that graph you all can compose with in order to do this well and there are bunch more interesting optimizations that we can do here and one of the talks I'm really excited about for later in the next two days is Andy's talk because he's gonna dive really deep into this in great detail including how the graphical spec addresses it but one really critical thing before we even died that deep because I'm just touching the surface here is this layer of the tree because this layer the tree were trying to fetch the friends and we're trying to fetch the best friend and we don't need to block on one before the other we don't need to wait for the friends to come back to go to the best friend but these are also two different queries this isn't really a data loader style problem how exactly are we gonna solve it and in particular how are we gonna solve it in actually writing the code because that's one thing I've completely elided so far we haven't had actually written code to do this data fetching and the realization that we had repeatedly is that we have to design our servers around a synchronicity that is just critical to making sure that our servers are going to be efficient and that we're going to be able to build the app that I described using the efficient queries that I described one challenge we ran into this is 2008 I think is where I started that timeline so we were using PHP 5 PHP 5 and 2008 did not have good asynchronous primitives and so we may do so let's imagine this is you know this is the query pattern we're trying to get this is sort of the thing we want to see in our Redis logs if we got everything right what are we actually going to write in 2008 era PHP well our initial step would just be this just get the data and this of course will do 13 round trips because every time we call it get we're actually going to go and hit this get is a synchronous Hall so what could we do instead without asynchronous primitives well one thing we could do is we could say get doesn't actually issue the roundtrip get queues up the roundtrip and then I'll add a magic function call it dispatch and that will actually do the round-trip and so you queue everything up you call dispatch and that actually goes and hits Redis this seems kind of concocted and seems kind of fake this is pretty much what our code in 2008 would have looked like this true that it's at the end that says hey that get call that was supposed to be synchronous don't make it synchronous like make it a Singh instead and that additional parameter that third parameter is a reference variable that when it actually gets fulfilled will get filled in and if this seems confusing don't worry it gets worse but I want to really strongly emphasize this was not hypothetical this is actually what we did because asynchronous II was that important to us we knew we had to do this in order to make the app run efficiently and so we did uh it wasn't good so we iterated and we improved and we came up with something that we called prepare able and app repairable basically said okay instead of having a single magic function that says dispatch everything all at once the parable says I'm a function that has multiple stages of fetching so I'll put the first stage in case zero of a switch statement in a magic function I'll put the second stage that requires the first stage to be done in stage one and again you eventually get used to this but not really but this was something that we used very very heavily throughout the app I mean it's something we talked about on Quora it's up there and it's something that we gave talks about this is a slide from a talk that we gave on prepare Bowls in 2011 and when I say we here I'm using you know a little bit the Royal we and a little bit we as Facebook but I also can use it as we the creators of graph q well I guess because this talk was given by Nick Jacques in 2011 talking about preparing and talking about really a lot of the things that I'm covering here and I'm starting to hint at you know the evolution of graph QL what created it didn't emerge from sort of this primordial void it emerged from these problems that we were solving in facebook server architecture problems like a synchronicity that we were solving with prepare ball so going back to prepare ball unless we worked at Facebook this probably looks rather unfamiliar with one exception if you ever played with regenerator which allowed you to use sort of function stars in JavaScript and it would effectively compile it into something that didn't use function stars this is what the output looks like looks a lot like prepare a ball and that's not by coincidence really all prepare ball was was a way of implementing generators which you can sort of simulate async functions with when you didn't actually have generators in the language so in conclusion we were taking our functions we were turning them into objects the state of those objects was literally what we now consider the output of a compile step because who would write this manually and this was the state of the art in 2010 or 2011 and I want to further emphasize this is exactly what we did we did this because asynchronously was still that important we had to do it in order to make our server run the way we wanted to we spent so much time iterating on these dacing primitives and the good news is you don't have to because all we were doing was building async await and async await exists now and virtually every language that I would consider using for a server has async await and if that sounds like a slight dig at Python or Ruby ok and why do I like async await so much why do I think it's so good well this code that we wrote that's pretty much what we wanted to write it's pretty good I can read that code I mean this is a hypothetical app with no designer and that I just explained 10 minutes ago you can read that code and go yeah I know what that does and that's a really important principle and it's one that certainly the memcache dispatching the prepare Abel did not have well with async await you still have that you're just fetching the data you're bundling the things you want to do in one roundtrip you can compose these things as you'd like it's just pretty it's really nice I like it and I'm gonna violate the laws of sort of time here because I'm supposed to be talking about 2008 to 2012 and I might be dating myself but I'm gonna do like a season 3 Lost thing where we just like flash-forward and we're going all the way from 2008 2012 - last month because I said any single weights really good async/await is really good I like the code a lot but this line never quite sat it feels kind of awkward and it's not awkward in a slide format because you can usually simplify it down and this makes sense but you know this is the equivalent of what it would look like in PHP it's not that ugly it's just dollar signs but ultimately your app grows and you do more and more things in this function and the function ends up looking a little bit more like this than you would like and this is ugly this is twice as many lines of code as it needs to be you can't really follow what's going on and I'm also doing that trick that speakers always do where they show a bug on a slide and then see how many people spotted it while they were talking show of hands it's there I messed this up I assigned the additional data to extra and I assigned the extra data to additional I don't know if I would have caught that in code review I hope I would have caught that in unit tests but I probably would have caught that in production right this isn't really what we want to be coding and the good news there is we are trying to solve this in the hack language in hvm with a new keyword concurrent that basically takes this code which has a very obvious a very subtle bug in it and you would write this instead and you write these five things and they would all run concurrently and at least in my mind the bug is very easy to spot now it's like extra equals additional it's like yeah that's wrong I messed this up so this is really cool why am I talking about this I want to make it very clear I had nothing to do with building this I just think it's super cool and a particular I've been saying a synchronicity it was that important it was that important in that era it is still that important that in 2019 we are still iterating on our server architecture to make sure that we get it right more often than we get it wrong and so this is a lesson that I took away from that you really want to design your server abstractions around a sink rim ative and one of the things that I've learned the hard way is anytime you design it around sink Rimma tips and someone decides they need to make it a sink in the future of which they will that's really hard it's a lot easier to call synchronous code form asynchronous code than vice-versa and so this is one lesson that I've taken away over and over again is if it can ever be a sink make it a sink and if it can't you're probably wrong and it should be a sink anyway and what I say you should design your server abstractions around a sink primitives I'm talking a little bit from experience here because graph QL did so if we all turn your hymnals to the book of graph QL chapter 6 verse 4.2 will read responsibly this is the value resolution section it's small type but we all that memorized so it's fine in case you don't here it is yeah that's small type I'm gonna need to go over here to read it this one I'm gonna read out loud cause it's important it is common for resolver to be asynchronous due to relying on reading an underlying database or network service to produce to value this necessitates the rest of a graphical executor to handle an asynchronous execution flow the graphical executors got to be asynchronous because your server abstractions are going to be asynchronous they need to be design it around a synchronicity the final lesson that we learned was to strive for single sources of truth whenever possible and I want to flashback to these four questions because these are four questions that I said I was going to answer it that's the start of the talk I was gonna explain how Facebook server architecture handled these but I actually want to sort of look at these four questions and think of something different because I didn't really answer them but when we go to answer them when we decide ok it's time we're gonna solve these these questions are really important authorization authentication you gotta get it right you're looking at caching if you don't get caching right is that gonna take down one of your Dedes right you're trying to detect people who are spamming you've got rate limiting you don't want to get that right in 90% of the time you want to get it right all of the time and if we always want to get things right we only want to write them once right if we wrote it twice one of like three things happens in my experience either the first implementation is wrong the second one's wrong or they're both wrong very rarely are they both you wrote it to what you wrote it twice and you happen to get it exactly right both times and so the solution that we had at Facebook here these single sources of truth are what I would call smart data objects if I were speaking in generalities if you're ever talking to a Facebooker and they sound super obsessed with Lord of the Rings because they're constantly talking about ents they might be obsessed with Lord of the Rings they're almost certainly referencing these objects at Facebook we know them as ents it's short for entity and there's a lot to that abstraction but it actually kind of fits on a slide a lot of the critical pieces because this is really what one of those smart data objects would look like it's just a class in this case it's in JavaScript but you know we have an in PHP or in hack lang and that class has this static gen method and this gen method returns a promise a synchronicity is important of a nullable smart user it's saying hey if you tell me who you are give me your viewer and you give me an idea of a user I will go and asynchronously get that user and I will return it to you if I can and this is important because that returned it to you if I can I'm fetching the user this is where we can put a lot of those hard questions I mentioned earlier we're trying to figure out where to put authorization you put it here because if this is the only way of getting a smart user if you're in a language that lets you actually hide the constructor then everyone who has a smart user object has to go through this I mentioned caching if you're gonna integrate data loader into your stack where would you put it you can put it here rate limiting how do you detect any time someone's fetching a user well they're always going through this method this is our single source of truth for fetching a user and then this object you can also sort of augment with additional capabilities you can have these methods that basically say hey a user has a best friend you should be able to fetch that thing again an asynchronous promise that returns a noble user they might have a profile photo same thing and notably these smart data objects all of their methods stay in that universe they stay in the smart date object universe and they use these gen methods as well and so gen best friend is that going through the single source of truth yes definitionally because the only way to get a user is to call that gen method so this net that has to be implemented using it so I know when I call this method because it returns a smart user object because I'm getting this I know it's gone through the single source of truth so this has two benefits one of them is it avoids over fetching I included that profile photo example my mock was parable he didn't have any photos in it I don't need to fetch that data for the mock and if I'm using these objects I don't because if I never called you in profile photo I never run that asynchronous method I never hit the DB I've avoided over fetching and secondly and I think this is something that is subtle but really important we have built-in preconditions imagine you don't have a single source of truth and you're writing a method you're like okay I want to get past in a user but you need to have already done a rate limiting check on that user before I can use it because I'm not gonna do it in my method it doesn't make sense for whatever reason so you would write that in a doc block and all of your callers would read your doc lock diligently and go oh I better do the rate-limiting check or I'll something's gonna go wrong that's definitely what would happen in every code face I've seen now like you can write your preconditions in doc blocks but you'll got to write some of the time which isn't really what you want from a precondition the nice things about these objects are you basically write your preconditions in the type signature of the method if you give me a smart user object I know you've rate limited it and I know that because you went through the single source of truth to fetch it and that thing did the rate-limiting for me and so it gives you a little bit of peace of mind where you're like hey because I'm operating at this layer I know that all the things I needed to do all the things that needed to happen have happened because the single source of truth guaranteed it so this is where things might start to be looking a little bit eerily familiar because if I look at this class and I squint a little bit it looks really familiar it's kind of a graphical object type right like this class and these these methods that I described that asynchronously maybe return another one of these types those are just graph QL fields that return objects and that Jen method that's a route field right you put that on query it's gonna give you a user given a viewer and an ID it'll take an idea as a parameter and this isn't by coincidence the reason why this looks really familiar and why the concepts in graphical correspond so nicely to this is because that's what existed at Facebook in January of 2012 when we were building graph QL we had these smart data objects and so we built graph QL is a wrapper around them and so a one question that all occasionally here is okay we have these resolvers our resolvers what should contain your business logic and I generally advise no resolver shouldn't contain your business logic resolvers should map to your business logic if you look at the resolvers at Facebook they are generally very very thin and or non-existent as you'll hear later today because we want the business logic to be in a single place it's a thin API layer and I think that's an important concept the reason why graph Google doesn't answer any of these four questions is not by omission it's by design so this has been graph QL before graph QL sort of that initial four years and it really goes even before that but leading up to February 2012 that put us in a place where you know with three people sitting at a pot of desks trying to build this thing out in two weeks we had the underlying infrastructure we had a lot of the underlying concepts that would allow us to build that initial prototype that mckisco okay there might be something here and those principles those principles that existed the idea that our data requirements form a tree the design around a synchronicity and the striving for single sources of truth that's very clear in the graph QL spec and in the design of graphical implementations you know the data requires form a tree I don't even know how to expound on that that is just so tautological in graph QL it's you know that input query is a tree the design around a synchronicity the idea that in the executor yeah everything could be asynchronous and we have to handle that this idea of striving for a single source of truth which you know the resolvers allow us to very cleanly map and say every time I'm fetching users name it goes through this method and I know that because I wrote it right there last year not on this stage on the previous stage the one that only fit 400 instead of 800 mix rock talked about graph QL native servers and the need to design our servers around graph QL and I think these three principles really sort of define in my mind what a graphical native server would look like they have to answer all these because if they don't graph Gil is not going to click it's not going to fit in because graph QL is designed to work with a server that thinks like this and what the shape of that server is is going to completely vary you know I will happily expound if you find me in the room over there on the benefits of a monolith also talk about that all day I love them but it doesn't have to be a monolith you can easily hit all three of these principles with a non monolith and plenty of people have and so I think that to me is really key is when we talk about this graphical native design we want to make sure that we're hitting the underlying principles one thing I'm really excited about today is the fact that you know I spent all this time talking about the creation in 2012 era Facebook server architecture there have been seven years of evolution since then on that original graphical native server and so the fact that we've got two talks from people who are working on a Facebook server during this conference gets me really excited and I'm also excited for our next talk because in addition talking about graphical native servers we can talk a little bit about the clients we can talk a little bit about the original graph Galyen native client the Facebook iOS app and the Facebook games right up on so I'm excited to hand it over to Matt Mahoney who's going to be talking about how to scale them thank you [Applause]
Info
Channel: Prisma
Views: 2,194
Rating: 5 out of 5
Keywords:
Id: gb1R-fWP1Yw
Channel Id: undefined
Length: 25min 27sec (1527 seconds)
Published: Thu Jul 11 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.