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生成等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM