GraphQL(四):GraphQL工程化实践

Demo到产品化还有很长的路要走,尤其是要在尽量小的影响当前框架的前提下引入新的方法。

GraphiQL

GraphiQL是整个GraphQL优势的重要一环,然而默认的GraphiQL不允许配置graphql服务的地址(就是点击GraphiQL上的运行按钮去请求数据的地址),要弄明白这一点很容易,找到graphiql-spring-boot-autoconfigure包,里面有graphiql.html文件,请求数据的endpoint是硬编码的:

function graphQLFetcher(graphQLParams) {
    // This example expects a GraphQL server at the path /graphql.
    // Change this to point wherever you host your GraphQL server.
    return fetch('/graphql', {
        method: 'post',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(graphQLParams),
        credentials: 'include',
    }).then(function (response) {
        return response.text();
    }).then(function (responseBody) {
        try {
            return JSON.parse(responseBody);
        } catch (error) {
            return responseBody;
        }
    });
}

显然这不能满足项目工程化的要求,解决这个问题有两种比较简单的方式:

  1. 把graphiql.html文件复制到项目中,用一个Controller提供GraphiQL服务,这样就可以去掉GraphiQL的相关依赖了
  2. 利用官方的GraphiQL的React组件自己搭建GraphiQL页面,这样定制化更方便

GraphQL Voyager

强大的实体关系图生成工具

image

GitHub已经提供了GraphQL的接口,实体关系图可以在GraphQL Voyager里查看,Custom Schema允许提供自己的实体关系数据生成实体关系图,简直不能更好用。

身份认证和权限控制

GraphQL(三):GraphQL集成SpringBoot原理中提到GraphQL本身不带身份认证和权限控制(这也确实不是它该做的事儿),但是它对查询提供了回调方法(Instrumentation接口),在GraphQL.java文件中可以看到这部分逻辑:

 public ExecutionResult execute(String requestString, String operationName, Object context, Map<String, Object> arguments) {
        //执行回调
        InstrumentationContext<ExecutionResult> executionCtx = instrumentation.beginExecution(new ExecutionParameters(requestString, operationName, context, arguments));

        assertNotNull(arguments, "arguments can't be null");
        log.debug("Executing request. operation name: {}. Request: {} ", operationName, requestString);

        //解析回调
        InstrumentationContext<Document> parseCtx = instrumentation.beginParse(new ExecutionParameters(requestString, operationName, context, arguments));
        Parser parser = new Parser();
        Document document;
        try {
            document = parser.parseDocument(requestString);
            parseCtx.onEnd(document);
        } catch (ParseCancellationException e) {
            RecognitionException recognitionException = (RecognitionException) e.getCause();
            SourceLocation sourceLocation = new SourceLocation(recognitionException.getOffendingToken().getLine(), recognitionException.getOffendingToken().getCharPositionInLine());
            InvalidSyntaxError invalidSyntaxError = new InvalidSyntaxError(sourceLocation);
            return new ExecutionResultImpl(Collections.singletonList(invalidSyntaxError));
        }

        //验证回调
        InstrumentationContext<List<ValidationError>> validationCtx = instrumentation.beginValidation(new ValidationParameters(requestString,operationName,context,arguments,document));

        Validator validator = new Validator();
        List<ValidationError> validationErrors = validator.validateDocument(graphQLSchema, document);

        validationCtx.onEnd(validationErrors);

        if (validationErrors.size() > 0) {
            return new ExecutionResultImpl(validationErrors);
        }
        ExecutionId executionId = idProvider.provide(requestString, operationName, context);

        Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation);
        ExecutionResult result = execution.execute(executionId, graphQLSchema, context, document, operationName, arguments);

        executionCtx.onEnd(result);

        return result;
    }

通过对每次执行GraphQL查询进行拦截可以实现类似SpringMVC拦截器的效果,但是需要自己实现SpringMVC中 @RequiredRole 注解的功能。

GraphQL(二):GraphQL服务搭建中提到有两种搭建GraphQL服务的方式,当时并没有考虑身份认证和权限控制的问题,假如要在那两种方法的基础上加入身份认证和权限控制,有哪些成本呢?

  • graphql-java + graphql-java-spring
    此方案使用了SpringMVC的DispatcherServlet,所以完全可以复用原有的拦截器机制,权限认证需要基于Instrumentation自己实现,工作量大
  • graphql-spring-boot-starter + graphql-java-tools
    此方案用了自己的GraphQLServlet,没有包含拦截器机制,要加上身份认证可以自己重写GraphQLServlet加入拦截器机制,或者在Instrumentation接口上做文章,两种方案都需要较大改动,且后期维护难度大,权限控制也需要基于Instrumentation自己实现

综合来看:


image

ok,再想想我们的需求是什么。

  1. 对项目的现有流程改动小(最大化复用现有逻辑)
  2. 支持权限控制
  3. 自动解析schema
  4. 不用硬编码、不要底层细节

这么一看的话可以得出这样的方案:


image

可以同时使用SpringMVC的拦截器和graphql-java-tools的优势。

似乎这种方案能满足我们的需求,但是有一个潜在的风险:

“A用户允许访问ApiA,ApiA能够访问到实体A,但是A用户没有权限访问实体A”

这时工程上就难以控制了,如果非要控制需要对实体进行权限,能做到,但是需要另外一套设计,工作量也不小。

风险

工程化实践时风险是必须要考虑的问题,GraphQL强大的自省功能(查询整个实体图的结构)能方便开发,也带来了相应的风险,同时嵌套循环查询、sql注入等问题也是需要防范的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容