使用 Spring Cloud Gateway 和 GraphQL Mesh,可以轻松构建现有微服务的 BFF (REST+GraphQL)

KINTO Technologies 2021 年 Qiita 的「圣诞快乐」系列的第 24 篇文章。

首先

本文介绍了在考虑实现微服务的BFF(前端后端)时,我们在PoC中进行的GraphQL Mesh构建示例。

我想在这篇文章中传达的是

如果我们使用基于REST接口的微服务架构,再加上Spring Cloud Gateway和GraphQL Mesh,就可以很容易地构建一个BFF,可以同时支持现有的REST接口和GraphQL接口。希望能够在这方面进行分享。

系统概要图

API_GATEWAY概要図6.png

为什么选择Spring Cloud Gateway?

    Spring Boot

目前,我们的微服务后端使用Spring Boot,并计划使用Spring生态系统来构建BFF,因此我们决定使用Spring Cloud系列框架。

    マルチクラウド問題

作为BFF的角色,我们考虑了使用AWS的管理服务。然而,由于我们需要在服务的海外扩展中支持多云环境,我们决定采用基于容器的方式来构建API Gateway。

    認証・認可のカスタマイズ

计划使用BFF实施认证和授权,并根据海外扩展需求进行系统定制化,希望构建一种标准且灵活的解决方案。我认为能够利用Spring Security等生态系统也是一个优势。

为什么选择GraphQL Mesh?

    API Composition問題

系统正在转变为微服务,并且预计存在将各个API响应进行合并的使用情景,我们考虑了API组合策略,但在现有的REST API中,需要为每个使用情景创建新的API。因此,我们决定考虑GraphQL接口,它可以灵活地处理请求和响应,当然也包括API组合,我们决定使用GraphQL Mesh来实现该目标。

    構築が一番簡単

我认为有很多方法可以使用GraphQL接口包装现有的微服务,但在这些方法中,使用GraphQL Mesh仅通过openapi配置来构建是最简单的。

实施

微服务

本文描述了使用OpenAPI Generator从API定义的YAML文件中输出源代码的微服务。在本次验证中,我们决定只实现订阅(Subscription)和车辆(Vehicle)这两个微服务的GET方法。
请注意,由于本文主要讨论BFF的实现,因此省略了微服务的详细实现内容。

    OpenAPI設定
订阅微服务subscription.yml
openapi: 3.0.3
info:
title: 订阅API
description: 这是一个用于测试的订阅API。
version: 1.0.0
servers:
– url: ‘http://localhost:8091/’
description: 本地开发环境
tags:
– name: 订阅
description: 测试订阅API

paths:
/subscriptions:
get:
tags:
– 订阅
summary: 获取订阅列表
description: |

  • 按创建时间排序返回订阅表中的订阅列表。
    operationId: listSubscription
    parameters:
    – in: query
    name: count
    description: 最大数量
    required: true
    schema:
    type: integer
    – in: query
    name: customerId
    description: 客户ID
    required: false
    schema:
    type: string
    responses:
    ‘200’:
    description: 成功
    content:
    application/json:
    schema:
    type: array
    items:
    $ref: ‘#/components/schemas/订阅响应’
    ‘400’:
    $ref: ‘#/components/responses/错误请求’
    default:
    $ref: ‘#/components/responses/默认错误’
    security:
    – BearerAuth: []components:
    responses:
    错误请求:
    description: 错误请求
    content:
    application/json:
    schema:
    $ref: ‘#/components/schemas/Api错误’
    默认错误:
    description: 非预期错误
    content:
    application/json:
    schema:
    $ref: ‘#/components/schemas/Api错误’
    schemas:
    Api错误:
    type: object
    properties:
    errors:
    type: array
    items:
    type: object
    properties:
    code:
    type: string
    description: 错误代码
    message:
    type: string
    description: 错误消息
    订阅响应:
    type: object
    properties:
    id:
    type: string
    customerId:
    type: string
    mailAddress:
    type: string
    name:
    type: string
    createdAt:
    type: string
    format: date-time
    createdBy:
    type: string
    modifiedAt:
    type: string
    format: date-time
    modifiedBy:
    type: string
    version:
    type: integer
    format: int32

    securitySchemes:
    BearerAuth:
    type: http
    scheme: bearer

车辆微服务vehicle.yml
openapi: 3.0.3
info:
title: 车辆API
description: 这是用于测试的车辆API。
version: 1.0.0
servers:
– url: ‘http://localhost:8092/’
description: 本地开发环境
tags:
– name: 车辆
description: 测试车辆API

paths:
/vehicles:
get:
tags:
– 车辆
summary: 获取车辆列表
description: |

  • 按创建日期排序返回车辆列表。
    operationId: 获取车辆列表
    parameters:
    – in: query
    name: count
    description: 最大数量
    required: true
    schema:
    type: integer
    – in: query
    name: customerId
    description: 客户ID
    required: false
    schema:
    type: string
    responses:
    ‘200’:
    description: 请求成功
    content:
    application/json:
    schema:
    type: array
    items:
    $ref: ‘#/components/schemas/车辆响应’
    ‘400’:
    $ref: ‘#/components/responses/请求错误’
    default:
    $ref: ‘#/components/responses/默认响应’
    security:
    – BearerAuth: []components:
    responses:
    请求错误:
    description: 请求错误
    content:
    application/json:
    schema:
    $ref: ‘#/components/schemas/Api错误’
    默认响应:
    description: 意外错误
    content:
    application/json:
    schema:
    $ref: ‘#/components/schemas/Api错误’
    schemas:
    Api错误:
    type: object
    properties:
    errors:
    type: array
    items:
    type: object
    properties:
    code:
    type: string
    description: 错误代码
    message:
    type: string
    description: 错误消息
    车辆响应:
    type: object
    properties:
    id:
    type: string
    customerId:
    type: string
    model:
    type: string
    color:
    type: string
    createdAt:
    type: string
    format: date-time
    createdBy:
    type: string
    modifiedAt:
    type: string
    format: date-time
    modifiedBy:
    type: string
    version:
    type: integer
    format: int32

    securitySchemes:
    BearerAuth:
    type: http
    scheme: bearer

春季云网关

在Spring Initializr的默认源代码基础上,只需更改以下设置。

    設定
dependencies {
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway:3.0.5'
}
spring:
  cloud:
    gateway:
      routes:
        - id: graphql
          uri: http://localhost:4000
          predicates:
            - Path=/graphql
          filters:
            - SetPath=/graphql
        - id: subscription
          uri: http://localhost:8091
          predicates:
            - Path=/subscriptions
          filters:
            - SetPath=/subscriptions
        - id: vehicle
          uri: http://localhost:8092
          predicates:
            - Path=/vehicles
          filters:
            - SetPath=/vehicles

GraphQL Mesh 可以进行本地中文求情重新解释为:图灵网格

GraphQL Mesh的Docker镜像是基于以下内容创建的。

    GraphQL Mesh設定
sources:
  - name: Fake API
    handler:
      openapi:
        source: ./openapi.yml
        baseUrl: http://host.docker.internal:8080

serve:
  hostname: 0.0.0.0 

※GraphQL Mesh运行在本地PC的Docker上,而Spring Cloud Gate运行在本地PC上,因此将host设置为host.docker.internal。

    Dockerfile
FROM hiroyukiosaki/graphql-mesh:latest-all-alpine
COPY .meshrc.yaml ./.meshrc.yaml
COPY openapi.yml ./openapi.yml
    OpenAPI設定
微服务APIopenapi.yml
openapi:3.0.3
info:
title:微服务API
description:这是一个用于测试的微服务API。
version:1.0.0
servers:
– url:’http://localhost:8080/’
description:本地开发环境
tags:
– name:订阅
description:测试订阅API
– name:车辆
description:测试车辆API

路径:
/subscriptions:
get:
tags:
– 订阅
summary:获取订阅列表
description:|

  • 按创建日期对订阅表进行排序。
    operationId:listSubscription
    parameters:
    – in:query
    name:count
    description:最大数量
    required:true
    schema:
    type:integer
    – in:query
    name:customerId
    description:客户ID
    required:false
    schema:
    type:string
    responses:
    ‘200’:
    description:成功
    content:
    application/json:
    schema:
    type:array
    items:
    $ref:’#/components/schemas/SubscriptionResponse’
    ‘400’:
    $ref:’#/components/responses/BadRequest’
    default:
    $ref:’#/components/responses/Default’
    security:
    – BearerAuth:[]/vehicles:
    get:
    tags:
    – 车辆
    summary:获取车辆列表
    description:|
  • 按创建日期对车辆表进行排序。
    operationId:listVehicles
    parameters:
    – in:query
    name:count
    description:最大数量
    required:true
    schema:
    type:integer
    – in:query
    name:customerId
    description:客户ID
    required:false
    schema:
    type:string
    responses:
    ‘200’:
    description:成功
    content:
    application/json:
    schema:
    type:array
    items:
    $ref:’#/components/schemas/VehicleResponse’
    ‘400’:
    $ref:’#/components/responses/BadRequest’
    default:
    $ref:’#/components/responses/Default’
    security:
    – BearerAuth:[]components:
    responses:
    BadRequest:
    description:错误请求
    content:
    application/json:
    schema:
    $ref:’#/components/schemas/ApiError’
    Default:
    description:意外错误
    content:
    application/json:
    schema:
    $ref:’#/components/schemas/ApiError’
    schemas:
    ApiError:
    type:object
    properties:
    errors:
    type:array
    items:
    type:object
    properties:
    code:
    type:string
    description:错误代码
    message:
    type:string
    description:错误信息
    SubscriptionResponse:
    type:object
    properties:
    id:
    type:string
    customerId:
    type:string
    mailAddress:
    type:string
    name:
    type:string
    createdAt:
    type:string
    format:date-time
    createdBy:
    type:string
    modifiedAt:
    type:string
    format:date-time
    modifiedBy:
    type:string
    version:
    type:integer
    format:int32
    VehicleResponse:
    type:object
    properties:
    id:
    type:string
    customerId:
    type:string
    model:
    type:string
    color:
    type:string
    createdAt:
    type:string
    format:date-time
    createdBy:
    type:string
    modifiedAt:
    type:string
    format:date-time
    modifiedBy:
    type:string
    version:
    type:integer
    format:int32
    securitySchemes:
    BearerAuth:
    type:http
    scheme:bearer

※openapi.yml是将微服务的subscription.yml和vehicle.yml合并而成的文件。

    Docker image作成
PS C:\dooboo\test\custom-images\graphql-mesh> docker build . --tag graphql-mesh
[+] Building 0.2s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                                                                                               0.0s
 => => transferring dockerfile: 156B                                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                    0.0s
 => [internal] load metadata for docker.io/hiroyukiosaki/graphql-mesh:latest-all-alpine                                                            0.0s
 => [1/3] FROM docker.io/hiroyukiosaki/graphql-mesh:latest-all-alpine                                                                              0.0s
 => [internal] load build context                                                                                                                  0.0s
 => => transferring context: 225B                                                                                                                  0.0s
 => CACHED [2/3] COPY .meshrc.yaml ./.meshrc.yaml                                                                                                  0.0s
 => [3/3] COPY openapi.yml ./openapi.yml                                                                                                           0.0s
 => exporting to image                                                                                                                             0.1s
 => => exporting layers                                                                                                                            0.0s
 => => writing image sha256:b9e2f32d6c20cb5340de6cafa9e2f72b663fc95150809002fe1b08bd463a2acf                                                       0.0s
 => => naming to docker.io/library/graphql-mesh                                                                                                    0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
    GraphQL Mesh 実行
PS C:\dooboo\test\custom-images\graphql-mesh> docker run --name graphql-mesh -p 4000:4000 graphql-mesh
yarn run v1.22.5
$ mesh dev
?️ - Server: Generating Mesh schema...
?️ - Server: Serving GraphQL Mesh: http://0.0.0.0:4000
    GraphQL 起動確認
image.png

最终成果

执行环境

アプリケーション実行環境ポートSpring Cloud GatewayローカルPCのIDE8080GraphQL MeshDocker4000:4000Subscription microserviceローカルPCのIDE8091Vehicle microserviceローカルPCのIDE8092

休息

在swagger editor的网站上,通过对上述openapi.yml定义进行REST(Representational State Transfer)验证,直接运行了基于http://localhost:8080的Spring Cloud Gateway。

image.png

GraphQL是一种用于API的查询语言。

image.png

我确认也能成功执行这个。

结束时。

使用Spring Cloud Gateway和GraphQL Mesh,通过为现有的微服务添加BFF(Backend For Frontend)来实现对REST接口和GraphQL接口的双重支持。实际上,如果要运营BFF,请假设在BFF端执行认证和授权,并且可以使用Spring Security + Spring Session进行支持,这样可以实现通用的用法。

我们公司正在进行丰田汽车的订阅服务计划和开发工作,目前正在招聘工程师。请访问KINTO Technologies公司官方网站。

广告
将在 10 秒后关闭
bannerAds