Skip to content

Commit

Permalink
Merge pull request #83 from wangyuheng/master
Browse files Browse the repository at this point in the history
支持合并多graphql schema
  • Loading branch information
wangyuheng authored Jan 26, 2021
2 parents f4772b2 + 6117f13 commit 4f5fd6f
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.errors.SchemaProblem;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.IOException;

Expand All @@ -22,13 +24,21 @@
* @author yuheng.wang
* @see GraphQL
*/
public class GraphQLProvider {
public class GraphQLProvider implements InitializingBean {

private static final Logger log = org.slf4j.LoggerFactory.getLogger(GraphQLProvider.class);
@Value("${arc.graphql.define:graphql/schema.graphqls}")
private ClassPathResource schema;
@Autowired(required = false)
private DataFetcherInterceptorRegistry dataFetcherInterceptorRegistry;
private String locationPattern;

private final PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private final TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();

private final DataFetcherInterceptorRegistry dataFetcherInterceptorRegistry;

public GraphQLProvider(DataFetcherInterceptorRegistry dataFetcherInterceptorRegistry) {
this.dataFetcherInterceptorRegistry = dataFetcherInterceptorRegistry;
}

private GraphQL graphQL;

public GraphQL getGraphQL() {
Expand All @@ -40,28 +50,38 @@ public GraphQL getGraphQL() {

private synchronized void refresh() {
if (null == graphQL) {
this.initGraphQL();
GraphQLSchema graphQLSchema = new SchemaGenerator()
.makeExecutableSchema(typeRegistry, RuntimeWiringRegistry.initRuntimeWiring(typeRegistry, dataFetcherInterceptorRegistry));
this.graphQL = GraphQL
.newGraphQL(graphQLSchema)
.queryExecutionStrategy(new AsyncExecutionStrategy(new CustomDataFetcherExceptionHandler()))
.mutationExecutionStrategy(new AsyncExecutionStrategy(new CustomDataFetcherExceptionHandler()))
.build()
;
}
}

private void initGraphQL() {
private void loadSchema() {
log.info("init graphql");
TypeDefinitionRegistry typeRegistry;
SchemaParser schemaParser = new SchemaParser();
try {
typeRegistry = new SchemaParser().parse(schema.getInputStream());
Resource[] resources = resourcePatternResolver.getResources(locationPattern);
for (Resource resource : resources) {
typeRegistry.merge(schemaParser.parse(resource.getInputStream()));
}
} catch (SchemaProblem schemaProblem) {
log.error("schema defined error! locationPattern:{}", locationPattern, schemaProblem);
throw schemaProblem;
} catch (IOException e) {
log.error("read graphql schema fail!", e);
throw new IllegalStateException("read graphql schema fail! path: " + schema.getPath());
log.warn("read graphql schema fail!", e);
}
if (typeRegistry.types().isEmpty()) {
throw new IllegalStateException("read graphql schema fail! locationPattern: " + locationPattern);
}

GraphQLSchema graphQLSchema = new SchemaGenerator()
.makeExecutableSchema(typeRegistry, RuntimeWiringRegistry.initRuntimeWiring(typeRegistry, dataFetcherInterceptorRegistry));
this.graphQL = GraphQL
.newGraphQL(graphQLSchema)
.queryExecutionStrategy(new AsyncExecutionStrategy(new CustomDataFetcherExceptionHandler()))
.mutationExecutionStrategy(new AsyncExecutionStrategy(new CustomDataFetcherExceptionHandler()))
.build()
;
}

@Override
public void afterPropertiesSet() throws Exception {
this.loadSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public DirectivePostProcessor directivePostProcessor() {
@Bean
@ConditionalOnMissingBean
public GraphQLProvider graphQLProvider() {
return new GraphQLProvider();
return new GraphQLProvider(dataFetcherInterceptorRegistry());
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.github.yituhealthcare.arc.graphql;

import com.github.yituhealthcare.arc.graphql.interceptor.DataFetcherInterceptorRegistry;
import graphql.GraphQL;
import graphql.schema.idl.errors.SchemaProblem;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
Expand All @@ -9,7 +12,6 @@
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.core.io.ClassPathResource;

import static org.junit.Assert.assertNotNull;

Expand All @@ -26,35 +28,45 @@ public class GraphQLProviderTest {
@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void graphQL_must_not_null() throws IllegalAccessException {
GraphQLProvider mock = PowerMockito.spy(new GraphQLProvider());
PowerMockito.field(GraphQLProvider.class, "schema").set(mock, new ClassPathResource("graphql/schema.graphqls"));
assertNotNull(mock.getGraphQL());
private GraphQLProvider graphQLProvider;

@Before
public void setUp() {
graphQLProvider = new GraphQLProvider(new DataFetcherInterceptorRegistry());
}

@Test
public void should_not_init_if_graphQL_existed() throws Exception {
GraphQLProvider mock = PowerMockito.spy(new GraphQLProvider());
PowerMockito.field(GraphQLProvider.class, "schema").set(mock, new ClassPathResource("graphql/schema.graphqls"));
PowerMockito.verifyPrivate(mock, new Times(0)).invoke("initGraphQL");
public void graphQL_must_not_null() throws Exception {
GraphQLProvider mock = PowerMockito.spy(graphQLProvider);
PowerMockito.field(GraphQLProvider.class, "locationPattern").set(mock, "graphql/s*.graphqls");
mock.afterPropertiesSet();
assertNotNull(mock.getGraphQL());
}

@Test
public void should_init_once_if_not_graphQL_not_existed() throws Exception {
GraphQLProvider mock = PowerMockito.spy(new GraphQLProvider());
PowerMockito.field(GraphQLProvider.class, "schema").set(mock, new ClassPathResource("graphql/schema.graphqls"));
public void should_init_once_if_schema_existed() throws Exception {
GraphQLProvider mock = PowerMockito.spy(graphQLProvider);
PowerMockito.field(GraphQLProvider.class, "locationPattern").set(mock, "graphql/schema*.graphqls");
mock.afterPropertiesSet();
GraphQL graphQL = mock.getGraphQL();
assertNotNull(graphQL);
PowerMockito.verifyPrivate(mock, new Times(1)).invoke("initGraphQL");
PowerMockito.verifyPrivate(mock, new Times(1)).invoke("loadSchema");
}

@Test
public void should_throw_illegal_state_if_schema_not_existed() throws Exception {
GraphQLProvider mock = new GraphQLProvider();
PowerMockito.field(GraphQLProvider.class, "schema").set(mock, new ClassPathResource("graphql/not_existed_schema.graphqls"));
GraphQLProvider mock = PowerMockito.spy(graphQLProvider);
PowerMockito.field(GraphQLProvider.class, "locationPattern").set(mock, "graphql/not_existed_schema.graphqls");
thrown.expect(IllegalStateException.class);
mock.getGraphQL();
mock.afterPropertiesSet();
}

@Test
public void should_throw_illegal_state_if_schema_type_repeat() throws Exception {
GraphQLProvider mock = PowerMockito.spy(graphQLProvider);
PowerMockito.field(GraphQLProvider.class, "locationPattern").set(mock, "graphql/*.graphqls");
thrown.expect(SchemaProblem.class);
mock.afterPropertiesSet();
}

}
8 changes: 8 additions & 0 deletions graphql/src/test/resources/graphql/repeat-schema.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type Mock{
id: String!
name: String!
}

input MockInput{
name: String!
}
8 changes: 8 additions & 0 deletions graphql/src/test/resources/graphql/schema-partA.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type Mock{
id: String!
name: String!
}

input MockInput{
name: String!
}
11 changes: 1 addition & 10 deletions graphql/src/test/resources/graphql/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,4 @@ type Mutation{
createMock(
payload: MockInput
): Mock
}

type Mock{
id: String!
name: String!
}

input MockInput{
name: String!
}
}
5 changes: 4 additions & 1 deletion sample/graphql-sample/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ spring:
# sampler:
# probability: 1
server:
port: 8401
port: 8401
arc:
graphql:
define: graphql/*.graphqls
54 changes: 54 additions & 0 deletions sample/graphql-sample/src/main/resources/graphql/project.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

"""
名称
为了达到某个产品迭代、产品模块开发、或者科研调研等目的所做的工作.
"""
type Project{
id: String!
name: String!
description: String!
createTime: DateTime!
milestones(
status: MilestoneStatus
): [Milestone]
}
"""
里程碑
表述一个Project的某个时间阶段及阶段性目标. 一个Project可以同时拥有多个处于相同或者不同阶段的Milestone.
"""
type Milestone{
id: String!
name: String!
description: String!
status: MilestoneStatus
creator: User
}

"""
里程碑状态
"""
enum MilestoneStatus{
"""
未开始
"""
NOT_STARTED,
"""
进行中
"""
DOING,
"""
发布
"""
RELEASE,
"""
关闭
"""
CLOSE
}

input ProjectInput{
name: String! @Size(min : 3, max : 100)
description: String!
dsl: String!
vendorBranches: [String!]!
}
58 changes: 0 additions & 58 deletions sample/graphql-sample/src/main/resources/graphql/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,3 @@ type Mutation{
payload: ProjectInput
): Project @Auth(role : "admin")
}

"""
名称
为了达到某个产品迭代、产品模块开发、或者科研调研等目的所做的工作.
"""
type Project{
id: String!
name: String!
description: String!
createTime: DateTime!
milestones(
status: MilestoneStatus
): [Milestone]
}
"""
里程碑
表述一个Project的某个时间阶段及阶段性目标. 一个Project可以同时拥有多个处于相同或者不同阶段的Milestone.
"""
type Milestone{
id: String!
name: String!
description: String!
status: MilestoneStatus
creator: User
}

type User {
name: String!
}

"""
里程碑状态
"""
enum MilestoneStatus{
"""
未开始
"""
NOT_STARTED,
"""
进行中
"""
DOING,
"""
发布
"""
RELEASE,
"""
关闭
"""
CLOSE
}

input ProjectInput{
name: String! @Size(min : 3, max : 100)
description: String!
dsl: String!
vendorBranches: [String!]!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
type User {
name: String!
}

0 comments on commit 4f5fd6f

Please sign in to comment.