前三篇是关于GraphQL的介绍和练手用的demo实现,从此篇开始,分享真正在实战中对GraphQL的应用。
目的
在此构建一个engine的项目,实现GraphQL的常用功能及问题解决方案,此项目即可直接应用于web项目的开发,也可以打成jar寄存于其他项目中。
接下来直接上手
首先还是基于Maven构建的Spring Boot项目 pom
<!--graphql-java-->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>15.0</version>
</dependency>
接下来需要加载schema,并初始化grahql
@Component
@Slf4j
public class GraphQLManagerProvider {
@Value("${engine.schema.file_path:classpath*:schema/*.graphql*}")
public String file_path;
@Autowired
private ApplicationContext ctx;
@Autowired
private QueryCommonProvider queryCommonProvider;
@Autowired
private MutationCommonProvider mutationCommonProvider;
private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
public GraphQL createGraphQL() throws Exception {
TypeDefinitionRegistry sdl = getSDLFormLocal();
if (sdl == null) {
log.error("没有要加载的schema文件");
return null;
}
//刷新bean
initBean(sdl);
return GraphQL.newGraphQL(buildSchema(sdl)).build();
}
public void initBean(TypeDefinitionRegistry sdl) {
//获取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
//创建bean信息.
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(GraphQL.class);
beanDefinitionBuilder.addConstructorArgValue(buildSchema(sdl));
//动态注册bean.
defaultListableBeanFactory.registerBeanDefinition("graphQL", beanDefinitionBuilder.getBeanDefinition());
//获取动态注册的bean
GraphQL graphQL = ctx.getBean(GraphQL.class);
log.info("graphql refresh :{}", graphQL);
}
private GraphQLSchema buildSchema(TypeDefinitionRegistry typeDefinitionRegistry) {
RuntimeWiring runtimeWiring = buildWiring(typeDefinitionRegistry);
return new SchemaGenerator().makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring(TypeDefinitionRegistry typeDefinitionRegistry) {
Optional<SchemaDefinition> optionalSchemaDefinition = typeDefinitionRegistry.schemaDefinition();
SchemaDefinition schemaDefinition = optionalSchemaDefinition.get();
Map<String, Type> typeNameMap = schemaDefinition.getOperationTypeDefinitions()
.stream()
.collect(Collectors.toMap(OperationTypeDefinition::getName, OperationTypeDefinition::getTypeName));
//动态加载schema中定义的query的typeName
TypeRuntimeWiring.Builder queryBuilder = TypeRuntimeWiring.newTypeWiring(((TypeName) typeNameMap.get("query")).getName());
//指定业务动态处理策略
queryBuilder.defaultDataFetcher(this.queryCommonProvider);
TypeRuntimeWiring.Builder mutationBuilder = TypeRuntimeWiring.newTypeWiring(((TypeName) typeNameMap.get("mutation")).getName());
mutationBuilder.defaultDataFetcher(this.mutationCommonProvider);
return RuntimeWiring.newRuntimeWiring()
.type(queryBuilder)
.type(mutationBuilder)
.build();
}
/**
* 加载Schema
*
* @return
* @throws Exception
*/
private TypeDefinitionRegistry getSDLFormLocal() throws Exception {
List<Resource> pathList = new ArrayList<>();
Resource[] resources = findResource();
if (resources != null && resources.length > 0) {
for (Resource resource : resources) {
if (resource.getFilename().indexOf("graphql") > 0) {
log.info("load schema file name: {}", resource.getFilename());
pathList.add(resource);
}
}
} else {
log.error("模型文件不存在");
return null;
}
return typeDefinitionRegistry(pathList);
}
/**
* 整合各个schema文件路径
*
* @param pathList
* @return
* @throws Exception
*/
public TypeDefinitionRegistry typeDefinitionRegistry(List<Resource> pathList) throws Exception {
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();
for (Resource resource : pathList) {
InputStream inputStream = resource.getInputStream();
String sdl = StringUtil.readStreamString(inputStream, "UTF-8");
typeRegistry.merge(schemaParser.parse(sdl));
}
return typeRegistry;
}
private Resource[] findResource() throws IOException {
return resourcePatternResolver.getResources(file_path);
}
}
要点介绍
然后是需要在项目启动的时候加载GraphQL,即触发createGraphQL方法
@Component
@Slf4j
public class GraphQLManager {
@Autowired
private GraphQLManagerProvider graphQLManagerProvider;
@PostConstruct
public void init() {
try {
log.info("graphql init");
graphQLManagerProvider.createGraphQL();
} catch (Exception e) {
log.error("GraphQLManager#init", e);
}
}
}
接下来是DataFetcher的实现
@Slf4j
@Component
public class QueryCommonProvider implements DataFetcher {
@Autowired
private List<IQueryResolver> queryResolverList;
@Autowired
private CommonQueryResolver commonQueryResolver;
/**
* graphql执行业务实现
*
* @param environment
* @return
* @throws Exception
*/
@Override
public Object get(DataFetchingEnvironment environment) throws Exception {
GraphQLFieldDefinition fieldDefinition = environment.getFieldDefinition();
String name = fieldDefinition.getName();
Object[] traslatedArgs = new Object[0];
Method currentMethord = null;
IQueryResolver curResolver = null;
for (IQueryResolver resolver : this.queryResolverList) {
Method[] methods = resolver.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(name)) {
currentMethord = method;
curResolver = resolver;
break;
}
}
}
if (currentMethord == null) {
return doExcute(name, traslatedArgs);
}
Method real = AopUtils.getMostSpecificMethod(currentMethord,
AopUtils.getTargetClass(curResolver));
try {
traslatedArgs = ValueTypeTranslator
.translateArgs(real, environment.getArguments(),
fieldDefinition.getArguments());
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return doExcute(name, traslatedArgs);
}
/**
* 遍历service和method寻找匹配的serviceMethod
*
* @param functionName
* @param args
* @return
*/
private Object doExcute(String functionName, Object[] args) {
for (IQueryResolver resolver : this.queryResolverList) {
Method[] methods = resolver.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(functionName)) {
try {
return method.invoke(resolver, args);
} catch (IllegalAccessException e) {
throw new EngineException(e.getMessage());
} catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause instanceof EngineException) {
throw (EngineException) cause;
}
throw new EngineException(e.getCause().getMessage());
}
}
}
}
//找不到 走common
this.commonQueryResolver.excute(functionName, args);
return null;
}
}
这里讲一下graphql和java的对应规则
按此思路,QueryCommonProvider类实现了以上的规则
要点解析:
接下来是IQueryResolver和IResolver
public interface IResolver {
}
public interface IQueryResolver extends IResolver {
}
到此,graphql用于实战开发的架子已经基本完成了,相比于之前的demo,这里实现了动态解析graphql的功能。
接下来是测试效果
首先在项目中建一个schema.graphqls 的文件
#as super schema
#三大根类型(root type: query mutation subscription)定义的时候本身或者继承其的子类必须有方法,否则就会报错
schema {
# 查询
query: Query
# 变更
mutation: Mutation
# 暂不使用subscription
# subscription: Subscription
}
# 查询
type Query{
queryProductById(id:ID!):Product
}
# 变更
type Mutation {
updateProduct:Int
}
type Product {
id:ID
name:String
sppu:String
price:Int
}
这里定义了一个queryProductById的查询方法
则对应的java应该是这样
@Data
@Builder
public class Product {
private Long id;
private String sppu;
private String name;
private Long price;
}
@Service
public class ProductService implements IQueryResolver {
public Product queryProductById(Long id) {
return Product.builder()
.id(id)
.name("product")
.price(592L)
.sppu("post")
.build();
}
}
这里实现IQueryResolver,表示这是一个query的实现
然后需要一个web访问入口
@RequestMapping("graphql")
@RestController
public class GraphQLController {
@Autowired
private GraphQLManager graphQLManager;
@Autowired
private GraphQL graphQL;
@RequestMapping("query")
@ResponseBody
public Object query(@RequestParam("query") String query) {
ExecutionResult result = this.graphQL.execute(query);
return result.toSpecification();
}
}
启动测试
http://127.0.0.1:8080/graphql/query?query={queryProductById(id:99562){id name sppu}}
响应为
{
"data": {
"queryProductById": {
"id": "99562",
"name": "product",
"sppu": "post"
}
}
}
源码地址:https://gitee.com/chengluchao/graphql-clc/tree/dev-20200928/graphql-engine/src/main/java/com/xlpan/engine
在后续还会做持续优化,如统一业务返回格式,request的传递,分页策略和sql生成等。