Please refer to my blog: GraphQL Interface Design
Graphql is a query language for apis. It provides a complete and easy-to-understand API data description, giving clients the power to precisely query the data they need without having to implement more code, making API development easier and more efficient.
Recently, I used Gatsby to develop a static blog, and I felt I couldn’t get along with this framework. Since the framework uses GraphQL technology, I spent some time learning about it and recorded the learning and thinking process.
This article explains how to understand GraphQL and how to design GraphQL based on a relational database. If you need to learn the basics of GraphQL, go aheadThe official documentation
This article contains a sample project for Nestjs + GraphQL: github.com/YES-Lee/nes…
Understand graphql
Graphql is a query language used to describe data and its relationships. The standard of GraphQL is described in the official documentation. The specific implementation relies on a third party.
Graphql doesn’t care how we get the data, we just need to provide a method to get the data (resolver) and how to assemble the data (schema). Similar to the interface design pattern in Java development, Graphql defines a set of standards by which we implement the interface. Let’s take the user-role model as an example.
The following code defines two data structures, similar to JSON, that should be easy to understand. It describes the name of each type and the attributes it contains. Attributes can be primitive types, arrays, and other reference types, creating all data models and diagrams for each other.
type Role {
name: String
note: String
}
type User {
id: Int
name: String
gender: Int
role: Role
}
Copy the code
The above code describes the Role and User data structures, so how do we use this thing? From the front end point of view, you can learn from the official documentation to use the basic front end. The data description in the request body is slightly different from the code that defines the type above.
query userInfo {
user(id: Int) {
id
name
gender
role {
name
note
}
}
}
Copy the code
As you can probably guess from the code above, if we don’t need to query the role data, we just need to remove it from the request
query userInfo {
user(id: Int) {
id
name
gender
}
}
Copy the code
When the request arrives at the server, GraphQL parses the request body. When the request is parsed to user, the logic we defined to get user data is executed. The same goes for role. So we need to define the logic to fetch data on the server side, which is called resolver in GraphQL.
The previous code only defined the structure of the data, so we need to create a resolver for it to fetch the corresponding data, similar to the following code
function userResolver (id) {
// ...
// return User
}
function roleResolver (userId) {
// ...
// return Role
}
Copy the code
We can perform SQL, HTTP requests, RPC communications, and any logic that fetches the required data in resolver. Ultimately, you just need to return the data in a predefined structure.
Schema
The code used to define different types of data structures is called Schema. It is a graphQL language for describing data structures and relationships. In the type definition of Schema, Int, Float, String, Boolean, ID and other Scalar Types defined by GraphQL can be used, or combined Types can be used. Refer to another type, etc., as in the above code User, which refers to Role.
Resolver
Resolver is the method used to fetch data in GraphQL. It doesn’t care about the concrete implementation of Resolver, we can get data from database, HTTP interface, server resources, etc.
Graphql executes the resolver based on the fields declared in the request to reduce the number of queries. In the following code, UserResolver is executed and RoleResolver continues after completion
{
User {
name
role
}
}
Copy the code
If we remove the Role field, the server will no longer execute RoleResolver
{
User {
name
}
}
Copy the code
When the UserResolver completes execution, the data is returned to the front end.
Graphql can perform data queries dynamically, reducing unnecessary resource consumption. However, everything has two sides. From another perspective, how can we control the granularity of Resolver? Assume that our Resolver is performing database queries. In restful apis, we usually use an associated query to retrieve both User and Role data. However, in GraphQL, we create a Resolver for each associated object. When we query a list of users and need to include roles related to users, we find that a request requires N+1 SQL queries: The UserResolver obtains N user lists once, and obtains the role of each user for N times. So that’s the N+1 problem that we’re going to do.
Problems with GraphQL
N + 1 problem
The N+1 problem is not unique to GraphQL, but we can avoid it when we write SQL.
To solve the N+1 problem, GraphQL officially provides a Dataloader solution. Dataloader uses caching technology and also merges the same (and similar) requests multiple times, merging the N queries described earlier into one. The basic principle is to perform the operation of the query list first, and then use the associated fields of each record as the parameter list. After obtaining all the associated data through a query, it is merged into the superior data. Dataloader is currently an effective solution to the N+1 problem without too much difficulty in use.
Second, we can reduce the number of queries by controlling the granularity of the Resolver. For example, in the previous example, instead of writing roleResolver, users and roles are obtained directly through an associative query using a single query. Of course, in this case, the service will perform associated query regardless of whether the role field is queried in the front end. The choice depends on the specific scenario.
HTTP Cache issues
Since the GraphQL interface request has only one unified endpoint, we could not use HTTP caching. Some current front-end implementations, such as Apollo, provide inMemeryCache solutions, but the experience is not very user-friendly.
Relational database + GraphQL interface design
Now that you know about GraphQL, let’s start designing the API for Mysql + GraphQL
Write a Schema
For schema design, the relationship between tables is ignored first, and the corresponding data table model is established first. For example, create the following schema for the User table and Role table
Type User {name: String gender: Int # password: String} Type Role {name: String note: String}Copy the code
Note that sensitive data should not appear in sensitive information (user passwords, etc.). Even if the Resolver result contains sensitive information, as long as the schema does not contain sensitive information, graphQL automatically filters these fields.
After you have established all the corresponding schemas of the tables, you can consider the relationships between the tables.
Table relationship processing
Graphql deals with relationships differently from Restful apis, where relational queries are created in interfaces where they are needed, and SQL optimizations are made for the interfaces to quickly query all the information needed in a SINGLE SQL.
However, in GraphQL, we should describe all table relationships as a graph structure, ensuring that all related tables (one-to-many or many-to-many) have their corresponding schemas together, so that we can reach any related node from a node when we request it.
This is also one of the charms of GraphQL in my opinion. When we build a complete graph, the front end can query and combine data freely. Theoretically speaking, the front end can be infinite recursively query a set of data, such as: Xiaoming -> Xiaoming’s friends -> Xiaoming’s friends of friends ->… We just have to pick a starting point and we can get anywhere.
One-to-many relationship
The establishment of one-to-many relationship is very simple, we just need to write the corresponding Resolver, and then add fields in the corresponding schema of the main table
type Role {
name: String
note: String
}
type User {
name: String
gender: Int
role: Role
}
Copy the code
function userResolver () {
// ...
// return User
}
function roleResolver (userId) {
// ...
// return Role
}
Copy the code
After we write roleResolver, we add role field to User. When this field is requested, GraphQL will execute roleResolver to get data.
Many-to-many relationships
Normally, in a Restful API, we would get many-to-many associated data through an SQL associative query, but in GraphQL, the use of only associative queries is obviously underutilized. Let’s look at the following example
# schema type User {name: String gender: Int role: role groups: [Group] # User Group userGroups: [UserGroups] # UserGroups} type Group {id: Int name: String note: String users: [User] UserGroups: [UserGroups]} type UserGroup {id: Int userId: Int groupId: Int note: String #Copy the code
For the above schema, you can see that groups and userGroups are included in User, and users and userGroups are also included in Group. UserGroup contains both User and Group, so we can perform the following query
{
user (id: 1) {
name
groups {
id
name
note
userGroups {
id
userId
groupId
note
user {
name
groups {
# ...
}
}
}
}
}
}
Copy the code
Some of you might say, well, this is an infinite loop. Yes, it does. It’s not a bug, it’s the connection I mentioned earlier. For different scenarios, we can query in different ways. For example, when I need to search for a user’s user group, I can add some parameters in groups
{
user (id: 1) {
name
groups (name: "admin") {
id
name
note
userGroups (userId: 1) {
id
note
}
}
}
}
Copy the code
The above query does not work if we only want to search for additional information in the UserGroup relational table. Then we can query in the other direction
{user (id: 1) {name userGroups (note: "new user ") {id userId groupId note group {id name note}}}}Copy the code
It can be found that after establishing the connected graph of the corresponding relationship, we can query from a table to any related table, and at the same time, we can nest the query indefinitely.
We don’t need to worry about infinite loops, because graphQL will execute the corresponding Resolver only after we specify the associated fields. To create an infinite loop, we need to write the query in an infinite loop, which is obviously impossible.
That’s what relationship management is all about. If you have a better idea, you’re welcome to harass.
conclusion
This article is my practice and thinking in learning and using GraphQL. If you have any mistakes or suggestions, please feel free to contact me for correction and discussion. In addition, it is important to consider whether graphQL should be used before practice, because the restful API can meet most of the requirements of the scene, blindly using GraphQL may bring some unexpected problems.