RubyConf AU 2019
Building APIs You Want To Hug With GraphQL
Summarized using AI

Building APIs You Want To Hug With GraphQL

by Tom Ridge

In the talk titled "Building APIs You Want To Hug With GraphQL," Tom Ridge explores how to design effective and evolvable GraphQL APIs, emphasizing the advantages of GraphQL over traditional RESTful APIs. He begins by expressing his excitement about the subject, particularly its transformative impact on how developers can handle data requests. Ridge introduces GraphQL as a query language specification that allows clients to issue declarative queries against a single endpoint, providing precisely the data they need without excess.

Key points discussed include:

- GraphQL Fundamentals: GraphQL enables the retrieval of specific data through structured queries, promoting efficiency and clarity in API responses.

- Schema Design: The importance of a well-designed schema is highlighted, as it clearly represents the application's domain and facilitates long-term success by making the API easy to understand and use.
- Real-World Example: Ridge illustrates the implementation of GraphQL through a Dungeons & Dragons character creation scenario. He shows how to model data, such as character names and races, and the evolution from handling multiple RESTful endpoint requests to a singular, streamlined GraphQL query.
- Avoiding Over-Engineering: A key lesson is the need to expose only necessary fields in the API. Ridge points out the risks of adding superfluous data which may complicate the schema.
- Enum Types and Ability Scores: He discusses the creation of enum types for class names and how to model ability scores, emphasizing encapsulation of logic within the server rather than the client.
- Collaboration with Domain Experts: Ridge stresses that effective API design should involve collaboration with domain experts and continual revisitation of the schema to ensure it aligns with the domain's needs.

In conclusion, Ridge advocates for thinking in terms of use cases rather than merely focusing on data or views. He emphasizes the critical role of domain understanding to craft well-designed GraphQL schemas that serve the users' needs effectively. The talk provides insights, tips, and encouragement for developers looking to harness the power of GraphQL for their applications.

00:00:00.030 I am really excited to introduce Tom Ridge. Tom is currently a team lead and tech lead at Culture. Before that, he worked at Two Red Kites, where I started my career. When I began working there as a brand new intern with no development experience, Tom was my number one mentor, ally, and cheerleader. He was a fantastic person to be around and has now become one of my best friends. Apart from being a fantastic human who is extremely supportive of junior developers and everyone he meets, Tom is a co-organizer for his local Ruby group. He has been a member of Ruby Australia and was once the vice president. Tom has a wonderful wife named Josie and two adorable twin daughters, Allie and Chloe. He's really into tabletop games, including Dungeons & Dragons, which may be mentioned later in this talk. I heard the best description of him the other night at the opening party: he is ruthlessly positive, and it is my great honor to introduce Tom Ridge.
00:01:10.530 It's very hard to follow such an introduction, but I just want to take a quick moment to applaud all the organizers and everyone involved in setting up this event. It's been an awesome experience. My name is Tom Ridge, and I am here today to talk to you about building APIs you want to hug with GraphQL. In 2018, I was introduced to a completely new way of seeing the world, which changed my perspective, opened my eyes to amazing possibilities, and introduced a bit of magic into my life. It was absolutely 100% Dungeons & Dragons-related. Some news that I am sure will come as a surprise to my colleagues and attendees—I have decided to quit my job and join my friends at a Dungeons & Dragons startup called The Geek's Dream. I will be building the character sheet feature, and, as luck would have it, the first designs came in today. It's a bit threadbare, but there's enough here to work with.
00:01:36.580 All I need to do is get my character's name and race from their API. In this case, 'race' could mean something like 'elf', 'dwarf', or 'human'—stuff you'd encounter every day. We start communicating with their RESTful API, and all of a sudden, I need to sift through all this data just to get my character's name. Even though, despite being sleep-deprived with twins, I can assure you that we need to get the race as well. So, we grab the race data and make a second request. More data to sift through... As we discuss, we think to ourselves that this is only going to get more complicated as we build our app.
00:02:12.020 We speak to our friend, and they are kind enough to create another endpoint for us—a custom one. This data is more in line with what we need, but we have to consider authorization on each new endpoint. We know that we have many more features down the line that aren't as simple as character sheets. We can't just develop an endpoint for every single feature, and we also have clients to consider. We want our app to be available on phones, desktops, and of course, who doesn't want to access their character sheet while on their way to get snacks from their internet-powered fridges? All these platforms have different data needs that won't be serviced by a custom endpoint. If only there were an alternative solution that could be served up in a digestible format.
00:02:51.300 So, we're here to talk about GraphQL. I am more excited than ever to discuss how we can build amazing APIs with it. First, let's cover some fundamentals: GraphQL is a query language specification for APIs. It empowers clients to issue declarative queries against a singular endpoint. Through these queries, we can traverse the graph representation of our application domain, pulling out discrete trees of data from a schema—no more or less than exactly the information we requested. With a strong type system and introspection, we can be certain about the shape of data being returned. In a nutshell, with GraphQL, you can have an API where you ask for just what you need and get exactly that in the shape you expect.
00:03:32.050 This brief introduction is really all you need to get an understanding of what GraphQL can do. The trouble is that it's very easy to design an API that isn't huggable—it can end up being quite the opposite. So how do we go about building something that's a joy to work with, easier to understand, and actually equips your engineers across all disciplines for long-term success? How do we build huggable APIs? It turns out this is a bit of an insider tip—APIs aren't tangibly hug-able objects, but today we will tackle the facets of GraphQL that will help you build evolvable GraphQL APIs that are at least pretty lovable, and that's through good schema design.
00:04:07.700 Unsurprisingly, this is harder than it sounds, but a good schema clearly expresses the language of your domain and accurately models it through a graph. Today, we're going to explore some simple techniques and rules of thumb that you can apply in your approaches to building your own schemas. We will avoid the pitfalls and mistakes that I have made along the way. To illustrate this, we're going to explore a well-known domain that is currently popular in the fifth edition of Dungeons & Dragons. Dungeons & Dragons has been around since 1974, and within its tidy 316 pages, it contains all the answers about how to name things in this domain, leaving us to focus on just modeling.
00:05:32.520 This is my first-ever player character, McGee, a wood elf—and a testament to the fact that naming characters can be quite challenging. He will help us form a data picture to accompany our designs. First, though, we need to get our app ready to go. We will install the GraphQL Ruby gem in your preferred Ruby web framework and build a schema that will probably look something like this. You will immediately notice that we have three route types: query, mutation, and subscription. For today, it is enough to understand that mutations are all about writes, and subscriptions allow us to listen for updates we care about. However, what we really want to focus on is querying because reading data forms a significant part of what we do.
00:06:04.440 Remembering our designs from earlier, we start thinking about how we can add our first real field to the query, attaching our character data to our designs. We begin with the character's name in a game of Dungeons & Dragons. A name is a property of a player character. With a quick glance at the rulebook, we can create our first node on our domain graph. When we introduce a player character node, we reference McGee as a player character according to the rules. Now, if we go back to our query, our first field will be a player character name that represents the language of the domain.
00:06:51.389 When we make a query against our API requesting a player character, it will resolve to this field. However, this field will be an object type, which means it will support additional fields so that we can return our character's name, race, and just about anything else. We've begun to model our player character type with various fields and additional types, but we've fallen into a bit of a trap. The design only called for two properties to be represented, and we've gone ahead and modeled the world. All we needed was to represent our player character's name and race—anything more than that at this point is overkill. This example is extremely contrived, but in the real world, it is far easier to add fields than to remove them.
00:07:18.890 Ensuring that you're only exposing the data your clients have an immediate need for not only prevents you from issues around field deprecation but also helps you be more deliberate about the structure of your API. With that in mind, let's reduce our player character down to just the necessary fields, along with a handy description of each type. We'll help clients introspecting our API to gain a better understanding of what they are receiving. Each field, including an ID field, signifies something unique. At this point, all three of our fields are scalar types, including booleans, floats, date/time, and integers.
00:07:41.770 Since these types often represent the leaves of the query—the data being returned—it is essential to set them as non-nullable. This approach helps avoid situations where boolean types might return nil instead of simply false. However, something still doesn't feel quite right. I've been thinking in RESTful terms again. If we remember our original complaint about a RESTful API, we often required an additional request to retrieve my character's race name. With GraphQL, we can and should retrieve this information within a single query.
00:08:15.570 The critical question is where to include this information in our graph. What we're really asking in a GraphQL context is that we want our API to return something like race ID. We want a new node on our graph, which means our GraphQL APIs are not—or should not—necessarily mirror our database design one-to-one. Our queries return discrete trees of data from that graph, so we can easily request details about a player character’s race as long as the linkage is established.
00:08:38.620 Since our goal is to get the name of our character's race, we must create that linkage. Let's add a race object type complete with ID and name, then incorporate it as a field back alongside our player character. To visualize how we resolve this, we can implement some methods that will be called when we resolve our queries. As simple as this may seem, it is a remarkably effective way of prototyping your APIs. It allows engineers across both front-end and back-end disciplines to validate their assumptions about a domain before wiring in persistent storage.
00:09:12.428 This schema-driven design approach is a fantastic way to start building out your APIs with GraphQL, enabling us to formulate queries that are immediately expressive and provide a clear picture of the shape of our domain. Let's recap what we've learned so far: adding fields is inexpensive, but it's essential to only include what you need when you need it. This practice will help you be more considerate and deliberate about the additional details you introduce to the graph while avoiding unnecessary complexity.
00:09:45.240 While we are dealing with performance considerations or dependencies from clients, this approach leads to greater flexibility within the schema, ensuring that it remains evolvable as we address each field in turn. At the same time, it's important to recognize when a new field could be an object type waiting to be uncovered. This could manifest as simply exposing an ID on a new node, or if you notice multiple similarly-named fields—like date something or address home—that typically indicates the need for encapsulation within its own object type.
00:10:16.961 Finally, it's unnecessary to have a persistent layer established to begin wiring things up; this strategy empowers you to validate your domain design while equipping your front-end and back-end engineers to collaborate effectively toward a shared contract rather than letting one side dictate terms. Some of us are prone to making the mistake of wiring things up without first considering the domain, which can hinder the API's evolution over time.
00:10:55.440 As conferences continue to happen, it appears we have a new design in front of us requiring the addition of classes and levels to our character sheet features. Classes in Dungeons & Dragons enable players to indulge in some classic power fantasies; for McGee, being a druid, this means he can transform into a chicken at a moment's notice. When playing a character, players will level up in their chosen class. With this knowledge in mind, let’s contemplate how we might apply this domain knowledge to our graph.
00:11:24.520 Since player characters choose their class, we recognize the need to add a player class type as a node on that graph, establishing a connection to the player character. Now we must add the field to our player character type, and we want our field names to stay close to the domain language. We'll name the field 'class' and define it as a new player class type, which we will now implement. We have created a type containing just the fields we needed and are feeling satisfied with our progress.
00:12:00.400 However, because classes in D&D are typically a fixed list, a class name field would just be a string. Luckily, GraphQL offers us a solution in the form of enum types, which lets us restrict the allowable values for our class names through queries and mutations. This functionality allows us to inform clients about the field in question while ensuring that only valid values can be returned from our queries.
00:12:31.859 This means that we can certify the range of class names returned by our queries. For fields that allow fixed lists of values, consider using enum types to bolster certainty around those inputs. The advantage of this approach is that while it represents a fixed list, you can still add any additional values in the future, allowing your APIs to remain adaptable over time.
00:12:55.800 It seems our API isn't the only thing evolving; there’s a new design that we need to implement supporting ability scores. Thinking back to our character, McGee has an impressive wisdom score but it's counterbalanced by a less-than-friendly charisma score. We need to understand how we might model these scores. Each ability score provides a modifier to dice rolls, which determines success when attempting tasks like spotting hidden details in a room or successfully negotiating for a cheaper horse.
00:13:21.750 In D&D, the minimum value of an ability score ranges between 1 and 20, and depending on where we fall within that range, the modifier can vary from negative 5 to plus 5. For example, a score of 16 would yield a plus 3, whereas a score of 12 results in a plus 1, and a score of 8 leads to a minus 1. As we analyze the value of an ability score, we realize it isn’t just a single number; this base score is accompanied by bonuses acquired from character race and class. These adjustments are essential in forming the total ability score.
00:14:03.810 How do we model this in GraphQL? Our collaboration allows us to create a new type on our domain graph responsible for managing these bonuses. With some reflection, we can derive comprehensive queries that encapsulate our total ability score. Yet, while retrieving the necessary information to compute the score, we run into a concern: we risk forcing business logic onto the client, making it responsible for aggregating data and performing calculations.
00:14:33.739 Our queries should encapsulate an implicit domain concept. As we describe our GraphQL schemas, it's vital to think in terms of use cases. Once we identify data related to generating a real score from various sources, we recognize it as a fundamental domain concept that warrants its own type. When modeling our domain graph, there is a temptation to only represent data or overly couple to views. Instead, we should focus on reflecting the business use case.
00:15:09.470 With that in mind, we'll add an ability score node before implementing it as its own type, also enhancing it with arguments to facilitate more effective queries. Reviewing the graph once more, we notice that ability scores align nicely with linking back to player characters. We use similar reasoning for race and player class, forming parts of our query type as well. This permits multiple entry points for exploring our application domain and helps us visualize the expansive possibilities we can achieve over time.
00:15:45.160 Thus, our teams aren't simply creating APIs for features; they’re exploring new ways to utilize the available data while constructing features. Now we’re equipped to build expressive queries that yield discrete trees of data. Queries are declarative and easy to interpret, like the one demonstrated here that pulls out player characters who have selected the two-player races, allowing us to extract precisely the information needed in the expected format.
00:16:21.100 From the start, we've revisited this graph, identifying it as it expands with new types and fields while observing the space evolve. We've been thinking in graphs, focusing on schema-first design, and recognizing our approaches need to differ from the traditional RESTful API method. We have constructed a small domain graph using a well-established domain language, reflecting on the composition of nodes, their relationships, and how that informs types and queries.
00:16:54.679 But what if we were exploring a domain that isn't as easily referenced? How can we model our domains? Once we start with the schema, it is helpful to take a step back and consider your domain from a purely graph perspective, devoid of the burden of considering specific fields you might have on those nodes. This perspective can set you on a successful path toward building out your APIs.
00:17:24.090 In my own team, we recently undertook steps to address schema misalignment with our domain needs. We collaborated with domain experts to draw a graph that accurately represents the data. From these sessions, I've come away with helpful tips: keep the scope small—if you have an existing domain model or a small feature to roll out, GraphQL can be adopted incrementally. Although you shouldn't couple your API to a single feature, focusing on mapping your domain helps prevent you from feeling overwhelmed.
00:18:08.800 It facilitates discussions that are restricted to the data and use cases at hand. Remember that with GraphQL, you’re building an API that represents your domain—not merely for features. Continuously revisit your mapped graph before implementation to verify it accurately represents a domain concept not encapsulated by other nodes. Ask yourselves every time you see a design, 'What is this data a property of, and where does it fit?'
00:18:38.900 Surprising as it may sound, engineers are not always the domain experts we believe we are. Modeling from the graph offers opportunities to collaborate with actual domain experts who help us consider the data that should be represented. Building a graph collaboratively as your first step is a deliberate and effective way to ensure your schemas start off on the right foot, fostering relationships and collaboration across your organization.
00:19:11.550 Now, let’s conclude with this thought: think in terms of use cases rather than merely modeling data or views. Consider emerging domain concepts and ensure your business logic resides on the server instead of being offloaded to the client. Evaluate the architecture of your application as a graph. This perspective makes it clear that there isn’t necessarily a one-to-one relationship with your database model and you should be mindful of how nodes are related and interconnected.
00:19:59.540 Engaging with graph modeling exercises within your team and in collaboration with business representatives is a terrific way to solidify that domain understanding. Remember, it's called GraphQL for a reason. Your ability to think in graphs is crucial for building well-designed schemas. Ultimately, success with GraphQL schema relies on both having GraphQL experts and domain experts, which are often not the same individuals. Proactively seeking domain expertise for modeling is vital and exploring how that fits within your graph will better prepare you for crafting schemas that empower your engineers for the future ahead. Thank you very much! If you're seeking resources or tips on schema design, there's a gist available, and I'll be uploading the slides for this talk. Additionally, I have a spare copy of a Dungeons & Dragons book, which I will give to the first player to tweet at me, TJ Reads, with the name of my character that’s present at the conference. I hope you’ve enjoyed this, especially if you're into Dungeons & Dragons. Thank you so much!
00:22:03.250 Thank you, Tom! That was so good; it actually tricked me into being interested in D&D for 20 minutes and that’s a win in my book. Thank you!
Explore all talks recorded at RubyConf AU 2019
+10