00:00:00.000
Our next speaker is the CTO at Dext, and he has been doing software development since he was a child. Professionally, he has over 20 years of experience. Over the years, he has done a lot of things, including teaching at the university here in Sofia, which is amazing because he has supported the Ruby community.
00:00:06.400
He also has a podcast that he hasn't posted to in about six years. I couldn't find it on Spotify, and he recently started streaming on Twitch, so you can check that out.
00:00:14.160
And yes, he's my boss, so please be kind to him.
00:00:26.680
Thank you. Can you hear me? Let's set the record straight: I'm not Gin's boss; I'm the boss of Gin's boss.
00:00:33.760
But I’m not here to talk about reporting lines; I’m here to talk about GraphQL. What better way to start than with a bit of audience participation? Can you raise your hands if you've ever looked in the general direction of GraphQL? Keep your hand up if you've actually used it in some way.
00:01:01.280
Now, keep your hand up if you’ve actually built or maintained a GraphQL API. Continue keeping it up if you think you’re very good at doing that.
00:01:14.000
Okay, I work with him. I know that's not correct! It's understandable because GraphQL is like an onion, and bear with me, I've thought hard about this; it's a very good metaphor. In fact, it's a three-fold metaphor. The first way GraphQL is like an onion is that it can be really enjoyable. If you’re into onions, there’s quite a lot of good Indian cuisine that uses it, and it can be quite tasty.
00:01:57.640
Also, it has layers. I don't know how many times I thought I understood GraphQL, only to peel back a layer and discover more complexity. Finally, and most notoriously, it can make you cry. So, my goal today is to give you a taste of the 'French onion soup' of GraphQL and some tips on how to make it nice.
00:02:56.879
By the way, Yaroslav, is he here? I made this image with ChatGPT. Oh wow, my job is safe! So, I'm Stefan, and here’s how I look on the internet and in real life.
00:03:21.280
Now, there’s a bit of good news and a bit of bad news. The bad news is that everything here is in Bulgarian. The good news is that I haven't posted on any of those things for ages, so you’re not missing out on anything. In fact, speaking of YouTube, the last video I tried to make was on Haskell, but I was too lazy to finish it.
00:03:38.840
You're not supposed to laugh or clap because if you didn’t like that joke, you’re really going to hate the next one.
00:03:57.440
Anyway, I have been using GraphQL since 2016, pretty much when it first came out. I want to start with my angle on GraphQL and how I’m using it because there are many different ways in which one can use it.
00:04:05.599
First, I’m working on an information system with a complicated model, showing a lot of data and letting the user edit a lot of it. There are way too many tables involved. Some of those tables are small log tables, but it's still too many tables, and that's important because if you’re doing something much simpler, your experience is going to be different.
00:04:53.320
We’re also fetching all the data through PostgreSQL, compared to using GraphQL to pull data from other APIs and services in the same request. That's a different problem with a different set of interesting challenges. But it’s not the problem I've got.
00:05:30.720
We also added GraphQL much later over a bunch of legacy code. That’s important because if you start a new project today with GraphQL, you can write Ruby in a way to avoid some of the pitfalls. But if you have a lot of Ruby code, you need to figure out how to make it work.
00:06:11.360
Finally, we have a React client that uses this API, an iOS client, and an Android client. Those are three separate codebases doing slightly different things.
00:06:18.320
If you’re not in that situation, why are you using GraphQL in the first place? You could use Hotwire, which I really like, or even use React Server Components, which I also like. But anyway, speaking of GraphQL, the story is very different on the client side versus the server.
00:06:55.439
I could give an entire talk on GraphQL on the client, but usually, you’re going to use Swift, JavaScript, or Kotlin on the client, while I’m going to focus on the server.
00:07:03.639
For those who haven't seen GraphQL, let me make a super quick introduction: it is a way to create APIs, and you do this instead of using REST, but it’s still over HTTP, and the responses are still in JSON.
00:07:45.880
Now, I know you don't need to do REST with JSON, but like everybody does REST in JSON, so in that way, they are similar. You also don’t need to use GraphQL over HTTP, but everyone does it, so close enough. What makes it different is that when you make a request, you use a custom language with its own grammar. You expose it by having only a single endpoint and only post to that endpoint.
00:08:48.400
You specify what you want to get back, and you only get the stuff you specify. The structure of the API is somewhat strongly typed, which means that while you can consume it from JavaScript and deal with JSON, if you think in TypeScript or Swift, you could generate types for your API responses.
00:09:29.840
GraphQL consists essentially of three components: queries, where all the fancy stuff is—where you fetch data—mutations, which are essentially remote procedure calls, and subscriptions, which are server-sent events. However, you can also perform a query on the result. The most interesting aspect of GraphQL is the queries where the complex part lies, and that’s what I’m going to focus on.
00:10:16.880
I need to show you some examples, and I will use the GitHub API. It may feel a bit GitHub-heavy because their API is quite robust. Every time I wonder how to do something, I look at their API and usually adopt how they’ve done it.
00:10:57.760
If I showed you a real GraphQL request to GitHub, it would introduce complexity that we don’t need for this narrative, so I’ll keep it GitHub-ish. Here’s a GraphQL request similar to the first one I ever wrote: we're fetching a pull request.
00:11:46.560
We know the pull request ID is one, and we want to get the ID, the title, all the comments of that pull request. For each comment, we want to get the body and some information about the user who made it, as well as the repository name. When you fire this request to the GraphQL API, you get back a JSON structure that closely mirrors the request you made.
00:12:36.160
You can see that it contains the pull request, the ID, the title, an array of comments, and the users in those comments. If you don’t select all those fields, for example, if you omit some fields, you will only receive a smaller response.
00:12:51.400
Now, how do we implement something like that in Rails? First, we start with an Active Record model representing a user, a comment, a pull request, and a repository. There are a few associations, and we know what they mean if you’ve used GitHub.
00:13:42.359
Next, we need to add the GraphQL gem, which helps you build an API. I have a lot to say about that gem: it was really cumbersome seven years ago, but it’s actually quite good today, though still not as smooth as Rails. It has massively improved and makes life a lot easier.
00:14:35.400
The first thing you’ll do is define your query type, which is the entry point where you can start your request. Here, we define the field pull request, indicating it will take an ID, and will return a pull request type which we’ll look into shortly.
00:15:15.640
When the user selects this field, we want to execute Ruby code to fetch the data. We call these resolvers, which is basically what you need to implement this part of the query.
00:15:52.000
Now, moving to the pull request type—this will give part of the query. It will get instantiated with the Active Record object returned in the previous step. Here we have the fields ID and title. They are just methods on Active Record, so we don’t need to do anything special about them. There are also associations, where we don’t need to do much either, but we can write resolvers.
00:16:43.200
When we want to fetch all the comments, we should select those comments linked to this pull request, possibly order them for stability. We want to select the repository as well.
00:17:36.000
The story of the comment type is similar. We filter the comments for the user using a user find operation on the user ID, and here we get the body from the comment.
00:18:25.599
Finally, for completeness, here's the user type from which you get the pull requests. All of this is typed, so you will have a GraphQL schema that describes all the types of your entry points, which is the query, and every other type you can obtain from the query. You don’t need to manually write that; GraphQL Ruby will generate this for you from your Ruby code.
00:19:25.440
At this stage, you might wonder why GraphQL is called GraphQL. If we draw the schema in this way, we can actually see it is a graph. I know it sounds very computer science-y, but all the types are the nodes, and all the resolvers are the edges between those nodes.
00:20:02.000
So at this point, you are probably wondering, where are the tears? I promised you tears. Let’s see how one of those things gets resolved. Here’s the query, and let’s see what happens when we run it.
00:20:57.440
Execution will start here. We need to make a query to the database. That query will retrieve the pull request by ID, and then we’ll select all comments with that pull request. Then we will query each comment for its user, resulting in an additional query for each user, leading to multiple queries and causing the N+1 problem.
00:21:36.000
Now, obviously, we don’t want to do that. Instead, we need to do what we’ve done in Rails for ages. I know you're going to say we could just use includes or preload, which I think is a bit new. The question is, where do we put this? Returning to this comment type, we don’t have an array of comments; we just have a single comment where the user is already loaded.
00:22:38.480
We need to resolve this field before we move on to the next one. This presents a challenge because at this stage, we need to specify what to do to find this user without knowing other users are involved, which leads us back to the N+1 problem.
00:23:20.080
If you think about it, we’ve got a complicated problem. Some might say it’s quite scientific, and yes, we could tackle it using Haskell. I know it sounds like a lazy approach, but allow me to elaborate.
00:24:02.239
With Haskell, we can design our code to handle lazy loading of requests. Here’s how it goes: instead of returning a result right away, we could just indicate that we need to fetch this user. We can accumulate all the required IDs across queries and, afterward, we resolve them all together in one pass.
00:24:53.440
There are ways to do this using GraphQL's built-in lazy load mechanism. However, this requires additional consideration around promise-based code. I do not advocate for GraphQL batch processing and will instead focus on a proper implementation.
00:25:45.840
Now, moving on to the challenges we face when implementing associations. Ideally, we'd still use the concepts from the Rails framework. Rails offers various tools to manage associations effectively.
00:26:41.280
Another key point is GraphQL’s introduction of a data loader. This method allows you to batch your network requests to fetch your responses from a stored GraphQL API. Recently, they added more methods to enhance efficiency, enabling you to avoid unnecessary requests.
00:27:36.960
In essence, you can effectively use the data loader to reduce overhead by loading data asynchronously. All you must do is call it upon your model, which will help you achieve lazy loading without the inconvenience of managing complex promises in JavaScript style.
00:28:37.440
Now that we’re equipped with data loaders, let’s recap how to maintain consistency while implementing nested queries. The libraries we choose often determine success in avoiding the N+1 problem, especially with GraphQL.
00:29:22.640
The big question remains: when should you use GraphQL? The boring answer is: it depends on your project. It can be highly beneficial in cases like ours but also overwhelming for simpler projects.
00:30:00.080
Ultimately, engaging with complex libraries requires a willingness to dive into intricate systems, akin to enjoying onion soup. In the long run, if you appreciate the challenges, it will provide joy; if not, you might want to find someone who does.
00:30:31.760
In summary, I’ve presented various aspects of how to work with GraphQL, alongside the challenges you might face. I encourage anyone dealing with these issues to reach out; perhaps together, we can overcome some of these hurdles.
00:31:04.000
Thank you for your attention, and I look forward to any questions you may have!