深入理解GraphQL查询语言,掌握API设计最佳实践
GraphQL实践指南
本文将深入介绍GraphQL的最佳实践和性能优化技巧,帮助你设计高效的GraphQL API。
Schema设计
类型定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type User {
id : ID !
username : String !
email : String !
profile : Profile
posts : [ Post !]!
}
type Profile {
id : ID !
avatar : String
bio : String
user : User !
}
type Post {
id : ID !
title : String !
content : String !
author : User !
comments : [ Comment !]!
createdAt : DateTime !
}
type Comment {
id : ID !
content : String !
author : User !
post : Post !
createdAt : DateTime !
}
查询和变更
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Query {
user ( id : ID !): User
users ( first : Int , after : String ): UserConnection !
post ( id : ID !): Post
posts ( first : Int , after : String ): PostConnection !
}
type Mutation {
createUser ( input : CreateUserInput !): CreateUserPayload !
updateUser ( input : UpdateUserInput !): UpdateUserPayload !
deleteUser ( input : DeleteUserInput !): DeleteUserPayload !
}
type UserConnection {
edges : [ UserEdge !]!
pageInfo : PageInfo !
}
type UserEdge {
node : User !
cursor : String !
}
type PageInfo {
hasNextPage : Boolean !
endCursor : String
}
解析器实现
基础解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const resolvers = {
Query : {
user : async ( _ , { id }, { dataSources }) => {
return dataSources . userAPI . getUser ( id );
},
posts : async ( _ , { first , after }, { dataSources }) => {
return dataSources . postAPI . getPosts ({ first , after });
}
},
User : {
posts : async ( parent , args , { dataSources }) => {
return dataSources . postAPI . getPostsByUser ( parent . id );
}
}
};
数据加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const DataLoader = require ( 'dataloader' );
class UserAPI extends DataSource {
constructor () {
super ();
this . userLoader = new DataLoader ( ids =>
Promise . all ( ids . map ( id => this . getUser ( id )))
);
}
async getUsersByIds ( ids ) {
return this . userLoader . loadMany ( ids );
}
}
性能优化
查询复杂度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const complexityRule = {
Query : {
users : {
complexity : ( args ) => args . first || 10
},
posts : {
complexity : ( args ) => args . first || 10
}
}
};
const validateComplexity = ( schema , query ) => {
const complexity = getComplexity ({
schema ,
query ,
rules : complexityRule
});
if ( complexity > 1000 ) {
throw new Error ( 'Query too complex' );
}
};
缓存策略
1
2
3
4
5
6
7
8
9
10
const cache = new InMemoryLRUCache ();
const server = new ApolloServer ({
schema ,
cache ,
cacheControl : {
defaultMaxAge : 5 ,
calculateHttpHeaders : true
}
});
错误处理
错误格式化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const formatError = ( error ) => {
console . error ( error );
return {
message : error . message ,
code : error . extensions ? . code || 'INTERNAL_SERVER_ERROR' ,
locations : error . locations ,
path : error . path
};
};
const server = new ApolloServer ({
schema ,
formatError
});
自定义错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ValidationError extends ApolloError {
constructor ( message ) {
super ( message , 'VALIDATION_ERROR' );
Object . defineProperty ( this , 'name' , { value : 'ValidationError' });
}
}
const resolvers = {
Mutation : {
createUser : ( _ , { input }) => {
if ( ! isValidEmail ( input . email )) {
throw new ValidationError ( 'Invalid email format' );
}
// 处理创建用户
}
}
};
安全性
认证授权
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const resolvers = {
Query : {
me : ( _ , __ , { user }) => {
if ( ! user ) {
throw new AuthenticationError ( 'Must be logged in' );
}
return user ;
}
},
Mutation : {
updatePost : async ( _ , { input }, { user }) => {
const post = await Post . findById ( input . id );
if ( post . authorId !== user . id ) {
throw new ForbiddenError ( 'Not authorized' );
}
// 处理更新
}
}
};
输入验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const typeDefs = gql `
input CreateUserInput {
username: String! @constraint(minLength: 3, maxLength: 20)
email: String! @constraint(format: "email")
password: String! @constraint(minLength: 8)
}
` ;
const schemaWithValidation = makeExecutableSchema ({
typeDefs ,
resolvers ,
schemaDirectives : {
constraint : ConstraintDirective
}
});
测试
单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe ( 'User Resolver' , () => {
it ( 'should return user by id' , async () => {
const user = { id : '1' , username : 'test' };
const dataSources = {
userAPI : { getUser : jest . fn (). mockResolvedValue ( user ) }
};
const result = await resolvers . Query . user (
null ,
{ id : '1' },
{ dataSources }
);
expect ( result ). toEqual ( user );
});
});
集成测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { createTestClient } = require ( 'apollo-server-testing' );
describe ( 'GraphQL API' , () => {
it ( 'should query user' , async () => {
const { query } = createTestClient ( server );
const GET_USER = gql `
query GetUser($id: ID!) {
user(id: $id) {
id
username
}
}
` ;
const res = await query ({
query : GET_USER ,
variables : { id : '1' }
});
expect ( res . data . user ). toBeDefined ();
});
});
最佳实践
Schema设计原则
使用描述性命名
避免过深嵌套
合理使用接口和联合类型
实现分页
性能优化建议
使用数据加载器
实现缓存策略
控制查询复杂度
优化N+1问题
掌握这些GraphQL最佳实践,将帮助你构建高效、可维护的GraphQL API。
Licensed under CC BY-NC-SA 4.0