GraphQL介绍

GraphQL协议

  1. schema即协议,通俗的来讲,可以类似rest openapi schema
  2. 客户端以及服务端根据schema通信,根据其定义进行校验约束
  3. 客户端在schema范围内可以自由获取(query)或者更新(mutation)需要的字段
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
32
type Hero {
id:ID!
name :String
friends:[People]
}

type Query{
heroes:[Hero]
heroById(id:ID):Hero
}

heroes{
name
}


data{
heroes: [{name:"A"},{name:"B"}]
}

type Mutation{
updateHero(new:Hero!):Hero
}

updateHero{
id:1
name:"C"
}

data{
updateHero: {id:1 , name:"C"}
}

需要关注的GraphQL Java概念

  • DataFetcher 定义具体数据获取方式,可以是db,memory
  • DataLoader 用于 1对n,数据关联查询,解决 n+1 查询问题,用key做了一些cache

对应SpringBoot以及Netflix DGS 封装映射

Fetcher

  • @Controller @MutationMapping @QueryMapping
  • @DgsComponent @DgsMutation @DgsQuery

Loader

  • @SchemaMapping @BatchMapping
  • @DgsData

原生GraphQL SDK demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private RuntimeWiring buildWiring(){
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("hero",starWarsWiring.heroDataFetcher)
.dataFetcher("human",starWarsWiring.humanDataFetcher)
.dataFetcher("droid",starWarsWiring.droidDataFetcher)
)
.type(newTypeWiring("Human")
.dataFetcher("friends",starWarsWiring.friendsDataFetcher)
)
.type(newTypeWiring("Droid")
.dataFetcher("friends",starWarsWiring.friendsDataFetcher)
)

.type(newTypeWiring("Character")
.typeResolver(starWarsWiring.characterTypeResolver)
)
.type(newTypeWiring("Episode")
.enumValues(starWarsWiring.episodeResolver)
)
.build();
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

@Component
public class StarWarsWiring {

private final DataLoaderRegistry dataLoaderRegistry;

public StarWarsWiring() {
this.dataLoaderRegistry = new DataLoaderRegistry();
dataLoaderRegistry.register("characters", newCharacterDataLoader());
}

@Bean
public DataLoaderRegistry getDataLoaderRegistry() {
return dataLoaderRegistry;
}


private List<Object> getCharacterDataViaBatchHTTPApi(List<String> keys) {
return keys.stream().map(StarWarsData::getCharacterData).collect(Collectors.toList());
}

// a batch loader function that will be called with N or more keys for batch loading
private BatchLoader<String, Object> characterBatchLoader = keys -> {

//
// we are using multi threading here. Imagine if getCharacterDataViaBatchHTTPApi was
// actually a HTTP call - its not here - but it could be done asynchronously as
// a batch API call say
//
//
// direct return of values
//CompletableFuture.completedFuture(getCharacterDataViaBatchHTTPApi(keys))
//
// or
//
// async supply of values
return CompletableFuture.supplyAsync(() -> getCharacterDataViaBatchHTTPApi(keys));
};

// a data loader for characters that points to the character batch loader
private DataLoader<String, Object> newCharacterDataLoader() {
return new DataLoader<>(characterBatchLoader);
}

// we define the normal StarWars data fetchers so we can point them at our data loader
DataFetcher humanDataFetcher = environment -> {
String id = environment.getArgument("id");
Context ctx = environment.getContext();
return ctx.getCharacterDataLoader().load(id);
};


DataFetcher droidDataFetcher = environment -> {
String id = environment.getArgument("id");
Context ctx = environment.getContext();
return ctx.getCharacterDataLoader().load(id);
};

DataFetcher heroDataFetcher = environment -> {
Context ctx = environment.getContext();
return ctx.getCharacterDataLoader().load("2001"); // R2D2
};

DataFetcher friendsDataFetcher = environment -> {
FilmCharacter character = environment.getSource();
List<String> friendIds = character.getFriends();
Context ctx = environment.getContext();
return ctx.getCharacterDataLoader().loadMany(friendIds);
};
}

Reference