Scale By The Bay 2018: Michael Solomon, Transpiling GraphQL instead of writing customized....
you hello Hey so I'm Mike Solomon and we're gonna be talking about transpiling graph QL instead of writing customized server code Before we jump in I just want to clear up the meaning of transpiling transpiling just means that we're going to be taking some source code in our case it'll be a graph QL query and we're gonna translate that into some different source code and today that'll be JavaScript flavored pseudo code a little quick background about me I work on a team at Twitter that builds a data access layer as a service that we use to power our graph QL implementation and you can follow me at M saw on Twitter if you want first a quick agenda just so you can know what to expect today we'll recap some properties of graph QL that are relevant for transpiling and introduce an example we'll talk about how graph QL implementations typically work and which parts a developer is expected to implement and lastly we'll talk about how we can skip doing pretty much all of that in favor of letting the computer do it for us all right so now that you're trapped here my real purpose is to get some engagement on my tweets so if you go to this URL then you'll see something like this there's a series of tweets each one's a vain attempt at humor and they all belong to a user in this case that's me and because this is a timeline of tweets that all belong to a user we call it the user timeline very creative and you can see it's made up of tweets so tweets in a timeline called a user timeline all belong to a user hopefully everyone's tracking so far so we're gonna jump to a little bit of graph QL and I don't know how familiar all of you are with that but hopefully it won't be so bad let's look at how we would model this in graph QL by looking at a simplified schema definition so you may or may not be familiar how graph QL schemas look but it's basically all of the types that are available in your API so this is a very simple API that only lets us query for one thing so you can see here we have this type query this is going to contain all the things that you can query for so today that's just gonna be a user so we see we have this user and it takes an ID a user ID and you're gonna get back a user and then we can see we have this user type you can get that same ID back and you can also get a time line that's going to be our user time line which we saw in the last slide and may be that the type the way the types written here might not be familiar to you but that basically just says that it's a list of tweet objects and we can see there on the right the type of tweet is just an ID and maybe a text field that contains the text of the tweet of course real tweets have a whole lot more than that but we're just gonna keep it simple today okay so let's look at a query demonstrating how we might actually get data for this in graph QL so I'm graph QL your queries start with query and then we can give it a name here we'll call it user timeline again staying very creative and we'll pass in a user ID so if you're gonna look up my user timeline you'd pass in my user ID then we want to actually query something on that user object we want to look up the user so we'll take that user ID and pass it to the user and then for that user we want to look up their timeline and then for every item in that timeline because remember it's a it's a list of tweets we want to get back just the text field for each of those tweets so for the user timeline we're going to look up a user the timeline associated with that user and then the text associated with each tweet so even if you're not familiar with hopefully this is enough to kind of like get you started and you'll notice here that there is lots of what so there's the user there's the timeline there's the text but nothing here says how to do it which is good in an API it shouldn't be telling you how how you're fetching things but someone does have to eventually write something that will actually go and get this data so let's belabor this point a little bit more with a diagram and we can focus on where the data comes from so we start with a user ID and we're gonna use that to get the user from some database maybe it's from a service but I'm just gonna say database and not distinguish because it's actually not that important for what we're talking about once we have that user we can then look up the timeline associated with that user and for that every entry in that timeline there's gonna be a list of probably tweet IDs or something we can go and look up the actual tweets and then on each of those tweets there will probably be a text field that actually contains the text of the tweet so there we don't need to go and talk to a service we can probably jit or a database we can just get it off of that objects directly this is kind of like where our data comes from and this is probably there's probably a lot more data available here but this is just a part of the graph that we're concerned about for this one query so let's talk a little bit about how graph QL implementations typically work so if you have a graph QL service the odds are good that you wrote some code to actually go and fetch some data so let's go over what functions we might implement for each to get data for each field in our query these are usually called resolvers so we'll look at examples and something that resembles JavaScript and we're going to assume that we're working in some graph QL framework the details of which aren't actually important so we'll start by writing a resolver for the user field in our graph QL is top level query object so you can see here that this takes three arguments there's the parent value which is query Q that's actually not that useful also the field arguments which here is called args and also request context this is usually just a place where there's things like so it gives you access to things like the logged in user maybe access to databases and things like that so let's look at the implementation of this function it'll probably look something like this so we'll take you can see on the right there we pull out the ID argument that was passed in and we're gonna take that and pass it to some database accessing function that's on our context so we'll take the user ID and we'll use it to look up a user by ID nothing very shattering here okay we also have a couple more of these that are gonna do very similar things you can see that for timelines we use the user ID to look up a timeline in a different database this one that contains the timeline and tweets and in this last function for the text field we just pull it off with a parent object which is a tweet so you can see that first there's a different name for the first argument in each of those that kind of like represents what the parent object is and you can access data on that so sometimes we're looking things up in databases sometimes we're just pulling things off of objects so so where does the data come from so we've kind of seen how you might implement resolvers by hand so here's that same code again from the last slide but how would we actually know where the data comes from so here it comes from database and here it comes from either a different database or maybe a different table or a different service or something like that and here it just comes from a field on the tweet object so some of this knowledge is baked into these resolvers each of these functions are called a resolver but it doesn't have to be because in graph QL you also have a schema which tells you the types of all the data that's available in your API so it'll tell you for example whether something is a union or a list or an object so a tweets an object whereas a timeline is a list of tweets the schema actually tells us more than that even it also tells us about graph connections so for example we know that a user has their own time line that's actually a graph connection as we saw a couple of slides ago so these graph connections are always by type an ID so for example we might have a user ID you can look up data associated with users that have that ID not too surprising and if we need some extra information maybe just the user ID isn't enough then we can use field arguments to pass in extra information that's just kind of a side note also we can enrich the schema with extra information so of course it's going to contain all of the type information and stuff we saw when we looked at this game and earlier as well as information about graph connections but we can also add extra information if we if we want so one example is we might add information about how to call into a database or service for a particular entry in the schema and the source of that information might be something like an existing data access layer maybe I'm projecting here because I work on such a service at Twitter but these sorts of things do exist okay so now that we've seen how graph QL implementations typically work let's see if there's anything that we can improve upon so we're using graph QL to make life easier for developers so we want it to be trivial to add new data to our graph QL the API so that seems like a pretty good goal we've seen here that the resolver code seems kind of repetitive so maybe that's a hint that we can avoid writing it at all maybe there's an opportunity to just skip that step entirely another goal is no business logic in the API layer so we found that teams can have more independence if business logic doesn't live in the API layer resolvers can be a really tempting place to put that logic because you're actually writing code and you know maybe it wouldn't be so bad if we just verified a couple of things right here in the API layer and then soon maybe we pull in some extra data and then we can you know verify that those things are working together as expected and just having that place to write logic makes it really tempting to start adding business logic in your API layer another thing we might be interested in is API updates without deploys so updates typically require deploys and that's not always a big deal but they do represent an organizational dependency between the team that owns and operates the graph QL service and a team that might own and operate the access to the underlying data so maybe we can give teams even more independence if we don't require the graph QL service to deploy whenever they add new data obviously if we're writing resolvers probably need a new deploy so that we can pick up that code and lastly we want to make sure that things like error handling and access control and any sort of improvement that we might dream up in the future gets applied everywhere without users doing any extra work if we can apply things automatically then users can't forget to do them or sorry developers on the graph QL API can't forget to do them so that leads us to this idea maybe we can generate most of our implementations instead of writing them so instead of writing resolvers for each field maybe we can take every query that we get and transform that query into a specialized function with enough logic to to fulfill that query and just that query so if that sounds a little crazy it's worth remembering that most queries are reused many times so doing this translation is maybe an acceptable to pay this would mean we would have no handwritten resolvers and we would be automating a lot of this so it'd be ideally very little work to add new data maybe we just put enough information in the schema to actually get the data and know what type it is but then you don't have to write code for it and this would also make it easy to apply our good ideas everywhere because we have a place where everything's being generated it's nice and centralized we can add air handling access control things like that that might apply everywhere okay so let's go back to our query so here's the same query that we saw before for getting the user time line so we're gonna go through this line by line and see how we can generate code for it as if we were the transpiler so the code that will generate will look something like pseudo JavaScript and remember that everything here after isn't code that will be writing by hand we're gonna see how we can automatically transpile these graphs dwell queries into functions without further human involvement okay so let's jump in at the very top so here we have a query named user time line that takes a user ID argument and it has a body that we're ignoring at the moment that's what that 3 dot ellipsis is for so this is gonna become a function so let's be very creative and we can give it a name like get user timeline and it's gonna take one argument so I think this will be easier to follow if we can actually see this side-by-side so let's put our graph QL query on one side and our generated code on the other side so here we have our query the user time line query and this is going to generate our get user time line function so we also still have the rest of the query still to fill in there on the left and we have some corresponding code to generate as well as a return value that we haven't generated yet over on the right so this is just a basic shell we're taking a query and we're gonna turn it into a function this is something we can do automatically okay so moving on objects that are looked up by ID so here you can see we're looking up a user by the user ID we're passing in just that one argument in this case is gonna be translated into a call out to a database so here we might do something like database user by ID and passing that user ID so let's look at this back in context we're over here on the Left we're asking for the user by ID and on the right in the function we're actually getting the user from the database and we still aren't worrying about the rest of the query over on the left and on the right we can actually start building out the value that we're going to return we know that we have to return a user so we can name a user and here you can see it's kind of like a JSON desc return value but it could also be some richer type if we're using some other language okay so now things are gonna get a little bit more interesting so graph connections like time line and if you remember from our diagram users each have a user time line so this actually is a graph connection well in order to generate this line or in order to generate code for this we have to know that users have time lines and we actually do know that because of how the scheme is structured we know about that graph connection so here we can generate a line of code like this where we look up the time line using that user ID from the parent object so we're not actually done with time lines yet because time lines are lists of tweets and we need to deal with that aspect of it as well so for lists when we look up time line we know from the schema that timeline is a list of tweets we need to hit rate over the results and do something for every item in that list so we can generate some code like this where we map over the time line and do something for every tweet inside it alright let's put this back in context so we can actually maybe understand what's going on so we're focused on the timeline and we can use this scheme as graph information to know that we need to look up the user's timeline in the database and then we can use the type information from the cyma to know that we need to iterate over every item in that time line and that's actually all we want to focus on for now in the query but now we know that the value we're returning has a time line nested inside the user and that the value is the result of iterating over that time line so we've actually filled out most of our final return value there already we know that we're returning a user that contains a time line that's made up of tweets okay and the last one is pretty easy so for field access on objects which looks something like this they're just looking up the text field on each tweet that's gonna remain field access on objects so our code is just going to pull out the text field from the tweet object so now we can add the text field there on the left and now we can actually see that this is the whole graph QL query we're trying to transpile in the first place and in context it's easy to see that we can just pull out the text field from the tweets so that we can return it and we're still returning the same overall value from before so we now have our original query on the left and our generated function on the right this function is only used for this query not for any others and nobody had to write this code instead we generated it using type information and a little bit of extra information in the schema to tell us how to access like where the data lives for looking at things in databases and you can see that it at least somewhat resembles the that you might write by hand here if you were going to if you're going to write a function to serve just this query it might actually look something like this we have user timeline text all on roughly the same order we see them in the query so in some sense the query on the left is the decorative version of the procedural implementation on the right that we generated all right so now we've seen how we can automatically generate implementations for queries so let's go back and see how we did on our goals let's see what this enables for us so now we've made it possible for us to build a system with easy opt-in to graph QL for data sets we haven't built that whole system here we just did the transpiling piece but we've kind of opened the door because we eliminated the need for developers to make changes in the API layer building a system like that will be a lot easier remember that we can use data access layer that tells us where the graph data lives so it's easy to augment the schema and wire up new connections like this dynamically there's also no business logic in the API layer here and because this is totally generated there's just no place to even do that and this is open the door to adding new data to graph QL without deploys because we just need to update our schema with new information we don't actually need to write new code that needs to be deployed all right we're also able to generically apply some things such as error handling we can generate error handling logic and the queries so it's easy to see how it works and we can't forget how to do it we skipped over that here just to keep it simple we can also generate access control another thing we can't forget about and it's not obvious from these examples but we can actually also generate code that handles batches of requests so in this example if you wanted to look up a whole list of tweets we can actually ask for all the tweets in a single request instead of many lastly it's also easy to add new operational improvements generically this model maybe we could add some retries or something like that okay so so what next what else does this open the door to in the future so because each query becomes a specialized function we have lots of information about what a query is gonna do which makes it a good candidate for optimization also this approach extends naturally to mutation and subscription supports so we only looked at the read side today but if you want to do writes or if you want to do stream extreme information then a lot of this would still apply alright and that definitely glossed over a lot of details but and there's probably some other ways you could approach some of these problems so if you'd like to talk to me about that then I'd love to talk to you after this again I'm Mike Solomon and you can follow me if you like tweets I'm in saw on Twitter and thanks for listening I think we have time for a time for a couple of questions one are you [Music] okay so the question is what changes when the mapping between the graph of where the data actually lives isn't quite so simple do you have an example of it in mind okay so if they're if the data is spread out across many different places and we need to fetch it independently from each other and all of the composition is done by the graph QL service so kind of its job is to go out and actually fetch data from all these various services and put it together into a final result which is kind of like what the code we looked at generated so I guess we haven't come across too many examples where we can't do something like this most of the time - you actually just going out and fetching data from either either for a list of data or for single items and you can actually just map pretty much your query exactly on to actual data dependency graph can you indicate the journey because okay so how do we actually do this because we didn't actually look at the code that that did any of this work so it was not just string manipulation we actually have our target and in our case we're actually targeting an internal language the query language that we have at Twitter so we already had some tools around manipulating that and then we basically walk over the AST of the graph QL query and then do some intermediate transformations and then eventually spit out our target language I know that's really hand wavy but sorry I don't have any examples with me okay so how do we inject a test database if we don't want to run this against our like real production data I think you could generally do that in your context so each one of these resolvers outweigh if you're writing it by hand in the resolver you would connect that to a test database instead of your real database I think that same basic idea would still apply you could just connect it to a database you could just change what database that's connected to as long as it has the same you know schema of how it does this so you want to know how how this transformation actually happens or what happens thereafter yeah okay so the left-hand side actually isn't an ast it's actually the original graph QL query but this is executed within a schema that has we also have like type information about all these different pieces so using the type information plus plus this query is enough information to generate everything else right then it's an answer your question quick all right I think we're out of time for questions thanks for coming everyone [Applause]