Interfacing with GraphQL in Swift
Sommer Panage at Swift Summit San Francisco, 2016
Sommer: Alright. Hello, everyone. I'm super excited to be here and to be speaking about Swift and how it plays with GraphQL. Before I jump in, I'm just going to give you a tiny bit of info about me. My name is Sommer Panage, @Sommer on Twitter, definitely please feel free to tweet at me if you have questions. I currently work at a tiny fitness startup called Chorus. Before that, I headed up mobile accessibility, that team over at Twitter. Way before that, I worked at Apple.
Anyway. That's enough about me. Thank you all so much for having me, and let's talk about GraphQL and Swift.
Many of your first questions may well be, what is GraphQL? GraphQL is a query language for your APIs. It acts as a middle layer between multiple front end clients and multiple back end services. Generally, with a GraphQL layer, you have exactly one endpoint for all of your queries. The query is parsed and rooted to your backend APIs and a response is returned. It's a whole lot of flexibility when you think about it. One endpoint with infinite queries. This means, your backend doesn't have to change if your client's data requirements change, and that's pretty cool.
GraphQL servers define a schema. That schema, this is essentially a type system that describes exactly what data is available to its clients. The type information is super important in GraphQL. The client can make the assumption that the server will either return the expected types in the schema or an error, but nothing else. It's essentially a client server contract. You can see here, we have a very simple schema example, and you can see, it's very similar to Swift. We've established a type called User, and that user has a name, an id. Those are required fields. You'll see the exclamation point, it's a bit the opposite of Swift, in that things are optional by default and the exclamation mark says no, this is actually required. I'm able to specify whether something is required in my schema.
Once I have a schema, I can write queries against it. I write queries against a particular schema. This allows for powerful developer tools, things like auto completion and validation. Let's take a look and let's compare GraphQL with a typical REST environment.
If I wanted to query a user in REST, I would hit my back end endpoint, I'd say something like, "Hey, API, give me user id 1." Of course, that's a standard thing we're all used to. Let's see if instead, I wanted to query a GraphQL endpoint for my user id 1. I would write a query in the GraphQL language, and you can see, the GraphQL language looks a bit like JSON, not quite JSON, but looks like it. I'll send this query off to my server and it'll get fulfilled, and it's going to return some JSON to me.
Regardless of whether I hit my first endpoint or my GraphQL one, this is what I would get back. Notice something really interesting here. In the GraphQL query, I actually specified the fields that I wanted. I said, I want name, email and Twitter. If I had not said name, I wouldn't have gotten name. That gives me a little more power than I had with my REST endpoint.
In a world where not everyone has the latest LTE, where network speeds are very varying across the world, it can be fantastic to have this level of control, to be able to reduce the size of our network requests. This brings us to my next big point, which is, why is GraphQL such a big deal for mobile?
In order to illustrate this better, I'm going to use a slightly modified version of the Star Wars API. If you aren't familiar with the Star Wars API, it's a great little open source API that provides a bunch of info about Star Wars characters, vehicles, star ships, etc. The reason I've chosen this is because it's a REST API, but the folks over at Facebook have written a wrapper for it in GraphQL. It's a great way to compare and contrast REST and GraphQL.
Let's start our case study. I wrote a very simple app. In this app, I wanted to list out all of my Star Wars characters and their home planet, and then I wanted to be able to tap in and see some details. As you can see, I failed at auto layout miserably on Luke Skywalker's name. This is my app, very simple, and that's what I want to be able to do. Let's start with the detail view, and we'll work backwards.
In the REST world, what will I do with my Star Wars API? To do the detail view, I'm going to have to hit my Star Wars API endpoint and I'll ask for Luke's id, which is 1. We're going to get back everything about Luke from the Star Wars API. Big old JSON thing. Looks good so far. You'll notice though, in my app, I need to get his starships. Let's see how I can do that. In my JSON response, the starships are there, but it's just the URLs. Pretty typical REST pattern. I need to go and make a couple more API requests.
I'm going to hit the Star Wars endpoint for ship 12 and ship 22. I'm going to get back some more JSON, and some more JSON. Okay. I just made three calls to populate one little view controller. Most of the information I got back, I actually didn't need. It would've been nice if I could've just populated that view controller in one request, but that's not the way the backend services were set up for me, so that's not the way I could do it. Let's populate this exact same view controller using GraphQL in our backend.
Now, in GraphQL, we would write the following query. I would specify that I want to hit the person query, pass in my id just like I did with REST, but now I would specify exactly the fields about Luke Skywalker or about my character that I want. I want his name, his height, mass, etc, etc. I would also specify that I want his connections to the starships. That field happens to be called starshipConnection, and I would specify that I only want the name, nothing else.
Now, when I make that query, I'm going to get back JSON, obviously. This time, I get back exactly one chunk with exactly what I need. Notice, we only have the data we want, and we only had to make one API call to get everything, starships included.
Also remember, I didn't change the Star Wars API backend. That data is the same. It's the exact same data that backed the REST query. The only thing that changed is how I'm interfacing with it.
I hope that starts to answer the question, why is GraphQL such a big deal for mobile? First of all, you specify exactly the data you want, you get it back. No more, no less. This means you can coalesce your requests into generally one per view controller, and this reduces network round trips. That's great. Stemming from that, when you build modern app UIs directly against REST, you often have to write a lot of imperative code. Your client often has to filter or join or transform the incoming data to make it actually fit what you're trying to do, what you're trying to show. You can get away from all that because now I can just request what I want.
Now, if you're like me, the first time you heard about this, you're like, "Whoa, that's really cool." This is great, this is exactly what mobile needs. Then you start to think about it, and you start to think about Swift and how you write your apps, and you're like, "Okay, I think this is great," but you start to feel the magic breaking down a little. Let's go into a little bit of that.
I'm sure we're all used to parsing JSON, but if you think about it, because I can arbitrarily specify what my model's going to give my back, all of the sudden, I don't have a concrete sense of what the model is. Now, we're parsing much more arbitrary JSON dictionaries of potentially untyped values. That seems weird in a strongly typed language like Swift. How are we going to deal with all this?
Let's take a look at our app again. In my little Star Wars app, I probably have something like a character model. In a typical way I would write it, my character model looks pretty much like what the backend model of the character looks like. We probably have a struct like this, it basically mirrors the backend table. Perhaps we lazily fetch the starships and vehicles or perhaps like we see here, I was eagerly fetching them. Either way, we're essentially reflecting our backend model in our Swift side model. It's not actually our view based needs. We create a separate view model for that later.
Here's what happens. Uh oh. You can probably see the issue here. Because I can ask for any field I want, every field now is technically optional. That doesn't feel so good. Our GraphQL queries are fantastically flexible, but this is seeming like a negative right now. One of the great things that we get from Swift are optionals. To be precise, it's not optionals, but it's the choice for something to not be optional. In Objective-C, anything can pretty much be null. This talk would be a lot shorter if it were about Objective-C.
In Swift, we're ushered into the beautiful world of non nullability. We can say this thing will never be null, and we can mean it. It would be tragic to give that up just for playing with something new like GraphQL. Fortunately, we don't have to.
Let's rethink how we're going to do this. We're going to have one view controller, my detail view controller for now, and I'm going to have one query, we've gotten that far, and for that query, I'm going to have one data model. The data model will be reflective of the query and the view controller, not of my backend.
Let's see how this paradigm works now. Back to my app. Just a quick review, there it is. Let's focus on that, we'll start with our first screen. What do we need here? We just need the name of the character and we need their home planet. I can query my AllCharacters from GraphQL, and I can say for each character, I want their id, which I'll need later, I'll need their name, and I will need that character's home world's name. There's my query for our first view controller.
I want to have one model to reflect that query in Swift. I'm just going to mirror that into a struct. You'll notice now, all my optionals are gone because I know what my query is. There's no question of, am I going to ask for a name or not? No, that question's gone because my model is now reflective of my query. It's a query based model. Again, my AllCharactersData, just the list of people, what is a person, a person is just an id, name and homeworld, and a homeworld has just a name.
We have a matching Swift struct for our query. We can do the exact same thing for the second view controller. Again, this time our query takes a parameter, that id that we got from the first one. We can query now for exactly the fields we need on our character, including the starshipConnection. We'll mirror that into our Swift struct. Here, we have a CharacterData, that also has a substruct called Person, but this time, Person has more information, has exactly the information we need. You'll see, the only thing that's optional is that image URL, because we don't always get that back.
By doing this, by having query or view-data based models, we no longer need optionals everywhere, and in fact, we no longer need that old style model at all. This is great news. We can use the benefits of Swift's optionals and type system with GraphQL.
Our parsing code still looks like this. Whether or not you're using some fancy parsing library, somewhere in your code, you're having to provide those hard coded stings and you're having to do a bunch of casting. That's unfortunately just how parsing JSON works. This leads us to the next step. We actually already have the information we need to not have to manually write this code. We have a query, which tells us the fields we're going to be getting back exactly, and we have a GraphQL schema that's our contract between our server and our client, and that schema tells us exactly what type each field in our query is.
We have all the info we need, we shouldn't have to write this code. In fact, that's true, you're right, we don't have to, because code generation is where it can actually happen with all that information. The fun thing with GraphQL is, this is actually what's been going on inside of Facebook for quite a while. Inside Facebook, they've been using GraphQL for many years, and on their Swift client, and I believe their Android one as well, they're using code generation to generate all these query based models, so you're not having to write any of this code.
Even though those tools aren’t necessarily available, this awesome company called Apollo has started their own GraphQL/Swift code generation tool. It's awesome. How does this work? The code generation system uses the statically written queries and it uses the schema to infer the types, and then it generates your Swift structs so you don't have to. Let's take a look at this in action.
Here's my app. This is before I added the home planets. You can see I just have the list of the names and now I want to go ahead and I want to modify my query and add the information about the home planets to my list. What I do, I modify my query in Xcode. I say, "Hey, give me that home planet," just the name though, and indentation not so much. There it is. Build. Oh no, I wrote the wrong thing. Xcode can actually check that thanks to the integration with the schema. Type the right name, build again. Now it worked. Okay, great.
Because I built, now I automatically have access to that new field by the exact same name. A person, name, I already got that, now I'm getting person, home world, and the name of the home world. Those fields were automatically created for me. I build and run. That's it. Didn't have to modify any of my model code, pretty much had to do nothing, modify the query.
Now, if you were watching that example really closely, you were like, "Wait a minute, we just got rid of the optionals and now you're typing in exclamation marks everywhere. What's going on here?" This is because the GraphQL wrapper that's currently wrapped around the Star Wars API, a lot of the non null fields are not marked as such. What this is showing us is that it's very important that our schema is correct. This is one of the key things that becomes important to the client when you're dealing with GraphQL, is that schema really does need to reflect your data precisely.
What happened here is that because the schema had not marked all of the non nulls as non null, I'm having to do that on the client side. We can see a very clear example of how important good schema definition becomes to you as the client programmer when you're dealing with GraphQL.
As you can see, this was Apollo's iOS GraphQL client for Swift that I just showed. The types and the structs are generated based on queries and schemas. Now, what this gave us, it gave us a lot of power. Xcode can now check your GraphQL queries for you against the schema to be sure they're valid. While there are many libraries out there that can wrap GraphQL as a language, and allow you to do dynamic query generation, what this does is it goes much further and says, "We don't need that, we'll just write our query statically but then check them so we're not worried about any string typing issues."
I highly recommend that if you are thinking of integrating GraphQL into your development, or you want to start playing with it, go check out the work that Apollo's doing. This whole project that you just saw is open source, so you can contribute to it. They're very excited to have people working on this and looking at this, so I highly recommend taking a look.
What about other things? There's a lot more to interfacing with your backend than just making queries and displaying responses, often. What about things like API versioning, things like caching? Let's talk about versioning first. GraphQL is marketed as version-less, according to Facebook. When you're adding new features, you're simply going to add new fields to your server and your schema. Your existing clients are unaffected. When you're sun setting old features, the corresponding server fields can be deprecated but can continue to function. You're not forced to leave any extra endpoints running, because there's only one GraphQL endpoint anyway.
This allows for a very gradual backwards compatible process. It removes the need for incrementing version numbers and again, helps you have less crufty version dealing with code on the client side.
What about testing? Testing for backwards compatibility is great with GraphQL. What do I do? I've got my Xcode project, I've got my separate query files that I have all stored as .GraphQL or whatever I want to store them as, and what do I do? Whenever the schema changes, I just take all my query files from all my previous releases of the app which I can just export, and I can validate those against the new schema. That's it. If I can validate those against the new schema and they're shown as valid queries, then everything should work fine.
Of course, this doesn't fix things like bad programming. If I'm force unwrapping things that actually are optional, that's not going to save me. What it does, it means backwards compatibility testing becomes very easy.
The next thing is, what about caching? This is very different than the way the standard REST world works. RESTful APIs, how do we usually cache? Usually, the way they work is, they have a dictionary-like nature. I had an endpoint, endpoint gives me data. Sounds like a map to me. We can do a very similar thing with GraphQL. We can use the query and we can use the response, and that's it. Query is my key, as you see, response is my value.
This is the simplest way to do GraphQL caching on the client side, and it works pretty well. As we've seen, GraphQL is different in the way that it is very common for results of multiple queries to overlap, because now we have that power right in our hands. This can lead to data consistency issues if I go with the simple approach. A more advanced caching scheme, and one that I believe is currently used at Facebook, is to flatten the responses into id-record pairs. I flatten my responses and I take whatever information is associated to the fields, and that becomes the value. Responses can link to other record pairs, and you essentially end up caching the graph itself.
Of course, populating and reading the cache is going to require query traversal. We're going to need a layer of code to handle this type of caching logic. However, this method is going to be overall a lot more efficient than response based caching. If you're interested in this approach, I've linked to some documentation on Facebook's Relay, and this is the approach that Relay uses as well. At the end of the talk, you can check out the links and I definitely encourage you to read more about record based caching if you want to go ahead and use GraphQL on your Swift apps.
This is a question I get a lot: Sommer, GraphQL's really great, but aren't you supposed to use it with React Native? It's a valid question. I think a lot of people think that the two technologies go together, and they certainly, they work really well together, no question. GraphQL shines on mobile, not just because of React Native. GraphQL shines on mobile for all the things we've seen here. We can reduce API calls, we can reduce the amount of data we're receiving. None of those features require React Native to do it.
The thing that makes React Native extra special is that we can have these component based systems where each component of our view hierarchy can declare exactly what fragment of a query it needs, and those then get synthesized and sent off as a query. The benefits of Relay are relatively separate from the benefits of GraphQL itself.
You come up against those issues. My answer to that question is, they're both fantastic and fascinating new technologies, but to get the benefits of GraphQL, I don't need to use React Native. We chose to stick with the type safety of Swift and go ahead and do Swift with GraphQL.
Wrapping this whole thing together, bringing it all back, GraphQL + Swift. What do we have here? We have; fewer requests for data. We have; getting exactly the data you want. We have; code generation and the potential for even more advanced features because of our strictly typed GraphQL schema. We have; no janky parsing code also because of the code generation features that we're starting to see develop in the real world. We have; models that are specifically reflective of our views, which means we can potentially do away with our own view models and our own model code, in many cases. And we have; a strongly typed backend schema, which is great for lots of things like backwards compatibility checking.
Of course, it's only fair, there are some cons. Not a lot of tooling yet. Just not a ton. The GraphQL client that I demonstrated earlier from Apollo is awesome, but it still has a long way to go. Best practices like with any technology, like with Swift even, are still emerging. A poorly defined schema can become a big client problem. As we saw with the Star Wars example in the video, if I don't correctly define my schema or if I come into, starting a Swift project on top of a poorly defined schema, I'm not going to get the benefits of the Swift language. I need to really, as a client, develop or pay attention to that schema and be aware of any changes that are happening.
It's not necessarily as good for endpoints requiring heavy logic. As you can see, the queries are relatively simple. I specify the data I want, I get it back. If there's some fancy things that need to happen based on the inputs, GraphQL may not necessarily be the right choice for you.
Nonetheless, I do think it's worth it. After having worked with this technology now for a few months, and have talked a lot to the Apollo team, I'm very, very excited about it. I do think it's worth giving a try, especially because if you want to see your app in other countries, if you want to see people with not necessarily the best networks able to use your app to the best of their abilities, I think this is one of the great things we can do to allow for that to happen.
As promised, I have a lot of links at the end of this talk that will get posted when these slides go up, some blogs that I recommend, etc, etc. That is everything. Thank you all a ton. As you can see, I'll be outside in the fireside area later, you can ask me questions about GraphQL, about this talk, but also, I wanted to mention, you can also ask me about Accessibility. That's my other specialty. Even though I didn't give a talk about it today, if you want to talk about it, I'd love to chat.