您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
GraphQL实战(3)-构建开发框架
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
GraphQL实战(3)-构建开发框架
自猿其说Tech
2021-07-07
IP归属:未知
80520浏览
计算机编程
GraphQL实战系列前两篇是关于GraphQL的介绍和练手用的demo实现(本文最后提供前两篇阅读地址),GraphQL实战第三篇,我们来分享真正在实战中对GraphQL的应用。 ### 目的 利用graphql的特性改变现有开发形式,提升开发效率 在实际开发中,将graphql的部分沉淀出一个框架,便于新项目的敏捷开发 在此构建一个engine的项目,实现GraphQL的常用功能及问题解决方案,此项目即可直接应用于web项目的开发,也可以打成jar寄存于其他项目中。 接下来直接上手 首先还是基于Maven构建的Spring Boot项目 pom ```xml <!--graphql-java--> <dependency> <groupId>com.graphql-java</groupId> <artifactId>graphql-java</artifactId> <version>15.0</version> </dependency> ``` 接下来需要加载schema,并初始化grahql ```java @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); } } ``` ### 要点介绍 - file_path:schema的文件路径,这里通过配置来获取,并指定了默认值 - ctx:用于将bean注册到spring单例池 - queryCommonProvider 处理query类型的GraphQL策略,为graphql提供数据支持 - mutationCommonProvider 处理mutation类型的GraphQL策略,为graphql提供数据支持 - createGraphQL() 用于构建一个graphql,并将graphql注册到spring - initBean 将graphql注册到Spring - buildWiring 这里指定了graphql数据获取的策略,即queryCommonProvider,实现了动态解析graphql返回业务数据的功能 然后是需要在项目启动的时候加载GraphQL,即触发createGraphQL方法 ```java @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的实现 ```java @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的对应规则 定义一个接口IResolver标志此类是graphql的业务解析类 IQueryResolver和IMutationResolver继承与IResolver,用于区分query和Mutation的操作类型 所有在graphql中定义的方法,都需要在IResolver的实现类中有同名同参的java方法用于执行业务 按此思路,QueryCommonProvider类实现了以上的规则 ##### 要点解析:要点解析: queryResolverList 是系统中所有 IQueryResolver的实现类 get() 实现DataFetcher的get方法,这里通过遍历IQueryResolver的实现类和每个类的method,寻找名称匹配的method方法,用于执行业务 ### 接下来是IQueryResolver和IResolver ```java public interface IResolver { } ``` ```java public interface IQueryResolver extends IResolver { } ``` 到此,graphql用于实战开发的架子已经基本完成了,相比于之前的demo,这里实现了动态解析graphql的功能。 ### 接下来是测试效果 首先在项目中建一个schema.graphqls 的文件 ```bash #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应该是这样 ```java @Data @Builder public class Product { private Long id; private String sppu; private String name; private Long price; } ``` ```java @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访问入口 ```java @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(); } } ``` ### 启动测试 ```java http://127.0.0.1:8080/graphql/query?query={queryProductById(id:99562){id name sppu}} ``` 响应为 ```json { "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生成等。 ### 总结 GraphQL是一个新的接口的交互方式,规则也比较开放,所以很适合用来构建开发工具。 构建开发工具的方向可以有很多,这里的方向是构建一个后端半自动化的开放框架,如:通过制定一些规则,省掉很多基础的方法实现,可能是数据库的CURD,可能是标准的平台调用等。另外通过GraphQL可以展示一个服务的能力地图:清晰的看到一个服务有多少接口,每个接口相关的数据结构。 按此方向,之后一个服务的大部分逻辑就不再存在于大量的service中,而是存在于业务相关的schema中,通过不同的schema来定义各个接口的实现逻辑。 GraphQL应该不止存在于Rest接口中,如果想做,可硬应用于各种协议,如Dubbo或JSF。 - GraphQL实战(1)-GraphQL介绍:https://developer.jdcloud.com/article/2101 - GraphQL实战(2)-java和spring boot实现:https://developer.jdcloud.com/article/2106 ------------ ###### 自猿其说Tech-JDL京东物流技术发展部 ###### 作者:中台技术部 程路超
原创文章,需联系作者,授权转载
上一篇:单元测试与重构
下一篇:JAVA开发、测试问题排查利器-Arthas
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号