• GraphQL实战-第四篇-构建开发框架


    GraphQL实战-第四篇-构建开发框架

    https://blog.csdn.net/xplan5/article/details/108846012

    前三篇是关于GraphQL的介绍和练手用的demo实现,从此篇开始,分享真正在实战中对GraphQL的应用。

    目的

    • 利用graphql的特性改变现有开发形式,提升开发效率
    • 在实际开发中,将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);
        }
    
    }
    

    要点介绍

    • 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方法

    @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的对应规则

    1. 定义一个接口IResolver标志此类是graphql的业务解析类
    2. IQueryResolver和IMutationResolver继承与IResolver,用于区分query和Mutation的操作类型
    3. 所有在graphql中定义的方法,都需要在IResolver的实现类中有同名同参的java方法用于执行业务

    按此思路,QueryCommonProvider类实现了以上的规则

    要点解析:

    • queryResolverList 是系统中所有 IQueryResolver的实现类
    • get() 实现DataFetcher的get方法,这里通过遍历IQueryResolver的实现类和每个类的method,寻找名称匹配的method方法,用于执行业务

    接下来是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生成等。

  • 相关阅读:
    Table Groups [AX 2012]
    Extended Data Type Properties [AX 2012]
    Base Enum Properties [AX 2012]
    Dynamics AX 2012 R2 Business Connector Error
    [转]System.Reflection.AssemblySignatureKeyAttribute
    为博客启用MetaWeBlog API
    How to create UrlSlug in Asp.Net MVC
    Serialize Documents with the C# Driver
    C# Driver LINQ Tutorial
    Getting Started with the C# Driver
  • 原文地址:https://www.cnblogs.com/chenglc/p/13743793.html
Copyright © 2020-2023  润新知