用Node.js和GraphQL创建BFF的故事

我是谁?

@qsona (Twitter, GitHub, Qiita)
Node.js developer
FiNC 2016/2 〜

Ruby, Rails / MySQL
love microservices!

Microservices Meetup 主催

昨日3/30に開催: vol.5 (API Gateway & BFF)


BFF 代表着什么?

    • Backends for Frontends の略

 

    • クライアントとバックエンドの中間にサーバを置き、フロントエンド寄りの処理を行う

 

    • Microservicesの文脈で語られることが多い

 

    昨日の会長のスライド step by step BFF

GraphQL 是什么?

    • クエリー型 Web API

RESTful API において問題になりがちな点をカバーしている

仕様として定められている

(RESTfulはあくまでAPI設計の指針)


我用Node.js和GraphQL制作了一个BFF。

    • Node.js v6.x

v7で –harmony オプションで async-await…!
は止められましたw
※ v7.6でオプションなしでも使えるようになった

koa v2.x


構成圖 – tú

Android    iOS   WebApp
   |        |      | GraphQL API
  Backends-for-Frontends (Node.js)
   |        |      | RESTful API
Backend1 Backend2 Backend3
 Rails    Rails     Node.js

※ 目前情况下,并非所有请求都通过BFF进行处理


小心翻译这个句子

    • 作り込み過ぎず、早めに試せることを重視した

価値検証を重視、ある程度失敗を許容する

役割をいきなり一手に引き受けすぎない

BFFがドメインロジックを持つようになると悲劇


我选择了作为BFF的角色。

    • 複数のAPI呼び出しをまとめる (batch request)

 

    • APIを順番に呼び出し、結果を合成する

例: ランキング
ランキングのサービスはuser_idを含む配列を返す
ユーザ情報を別のサービスにuser_idsで引く

ユーザ認証

Backendへのリクエストは user_id + サービスとしての認証トークン
user_idはHTTPヘッダに入れた


我没有选择作为BFF的角色

    • クライアント向けたレスポンスの整形

 

    • バックエンドへのqueryingの仕方を持つ

例: 一覧画面でのフィルターやソートのかけ方

更新系をまとめる処理

我想排除BFF中加入域逻辑的可能性。


为什么选择Node.js?

    • (我々の)BFFのメインの役割は、複数のBackend APIを呼び出して返すこと

当然、並列に呼び出したい。非同期処理が大半になる

フロントエンジニアに触りやすくしたかった

我々のクライアントサイドの言語: Swift, Java (Kotlin), JavaScript
(そもそもSwiftやJavaはここの選択肢に入りにくい)
JavaScriptはとっつきやすい(※個人の感想です)
重厚な言語(※個人の感想です)は選択肢に入れにくい


为什么选择Node.js?

    • qsonaがもともとNode畑だったので

手伝ってくれた人もJavaScript畑

ReactのSSRを見越す


为什么选择使用GraphQL?


GraphQL的特点 (1)

可以同时获取多个资源

query {
  user(id: 10) {
    name
    age
  }
  groups(filter: "awesome") {
    name
    leader_name
  }
}
{
  "data": {
    "user": { "name": "qsona", "age": 17 },
    "groups": [
      { "name": "Node学園", "leader_name": "yosuke_furukawa" },
      { "name": "FiNC", "leader_name": "Yuji Mizoguchi" }
    ]  
  }
}

这次称为批量请求。


GraphQL的特点(2个)

只能取得特定key所需的资源。

query {
  user(id: 10) {
    name
    // ageがない
  }
}
{
  "data": {
    "user": { "name": "qsona" }
  }
}

这次我们称之为过滤查询。


GraphQL的特点(3)

1. 增量加载:使用GraphQL,客户端可以通过一个请求来获取特定数据的特定字段,而不需要多次请求多个接口来获取不同数据。这大大减少了网络请求的次数,提高了效率。

2. 精确获取:GraphQL允许客户端请求特定字段,从而减少了不必要的数据传输。这意味着客户端可以准确获取其需要的数据,而不会过度获取或浪费带宽。

3. 强大的类型系统:GraphQL有一个严格的类型系统,能够帮助开发者在编写查询时提供自动补全和错误检测。这大大降低了出现数据类型错误的可能性,并提高了开发效率。

可以决定在嵌套资源中提取到哪个层级。

query {
  user(id: 10) {
    name
    age
    children {
      name
      age
    }
  }
}
{
  "data": {
    "user": {
      "name": "qsona",
      "age": 17,
      "children": [
        { "name": "junki", "age": 4 },
        { "name": "sumire", "age": 2 }
      ]
    }
  }
}

本次称为preload query。


GraphQL的特点(4)

具有强大的类型系统的 (GraphQLType)。

這個因此獲得的東西

    • リクエストのバリデーション

 

    • レスポンスのバリデーション

 

    • ドキュメントの自動生成

 

    • cli (GraphiQL) の自動生成

とても使いやすい


BFF導入前存在的問題點(1)

智能界面的API

    • 画面に必要なものを全て返すようなAPI

 

    • 画面が変わるたびに、1つのレスポンスにキーが追加される

キーが増えすぎる
何が今でも使われているのか不透明

メンテが大変


对于问题点(1)的应对措施

    • 前提: Backendをリソース指向に寄せていく

 

    • それでも画面に必要なものを同時に取得できるように

batch request
preload query


在引入BFF之前存在的问题点(2)。

响应的JSON格式不稳定,客户端感到困扰。

    • レスポンスのドキュメントがない

DSLでリクエストパラメータを記述していた (grape)
ここからドキュメント(swagger)を自動生成できる
しかし、レスポンスのドキュメントはない

リソースが定まってない

ふわっとしたJSONシリアライザ ( rabl )
複数のAPI間で、jsonの形が微妙に違う
Android/iOS間でのドメインの解釈も定まっていなかった


应对问题点(2)。

    • 前提: Backendをリソース指向に寄せていく

 

    GraphQLType

在引入BFF之前存在的问题(3个)。

    • メインのサービスが、他のサービスのデータもまとめて返していた

オーケストレーション

凝集度の低下

他サービスの変更時、メインのサービスまで変更しなければならない
(メインのサービスは規模が大きく、変更・デプロイが大変)


对于问题点(3)的处理方法

    • 前提: Backendはリソース指向に寄せていく

 

    • batch requestを利用

複数APIの集約はBFFの責務とする


(图片)


改良了


随后


BFF引入后的问题点(1)查询的管理

在GraphQL的第二个特点中介绍的“过滤查询”并没有特别解决任何问题。

    • あるリソースのうちの一部分だけ取得したい、というユースケースがほとんどなかった

クライアントが、部分的なエンティティをわざわざ作らなければならない
部分で取得すると、キャッシュもヒットできなくなる


BFF引入后的问题点:(1) 查询的管理

    • バックエンドにキーを追加するたびに、変更する必要がある

 

    • iOSとAndroidでの共通化

本来は「iOSとAndroidで別々な方法でリソースを取れる」というメリットがある
しかし、そのようなユースケースは今の所なかった
クエリを共通管理することに


BFF引入后的问题 (2) 改进的竞争

    • Backendサーバ (Rails) 側も問題を改善してきた

JSON Hyper Schemaの導入
使うシリアライザの変更 (rabl => active_model_serializers)
API設計レビュー、設計力の向上、設計の標準化

クライアントも、ドメインモデルの手前にJSONをマップする層を持っている

腐敗防止層 (参考: @yanzm さんの記事 )


BFF导入后的问题点(2) 改进的竞争

    • 役割が結構かぶる

Server: JSON Schema / Serializer
BFF: GraphQLType
Client: 腐敗防止層

必要かどうかは、チームの距離感の問題でもある

結合度
そこまで遠くする必要はない


拾取落下的穗子


(1) 拣落穗更新了吗?

    • ひとまず、更新系はBFFに持っていない

 

    • 更新系のオーケストレーションは、今後もしない

 

    • やるとしたら「このボタン(アクション)に対応するエンドポイントをBFFで持つ」

GraphQLではちょっと難しい


摘落蓂蕭 (2) 錯誤處理

    • GraphQLでは、レスポンスは errors または data のキーを返す

 

    • 複数リソースを同時に取得しようとして、1つが失敗した場合、errorsだけ返すのが仕様

 

    • BFFでは、成功したリソースは返しつつ、失敗したリソースはその理由を返す必要がある

つまり errors は基本使えない


错误处理

{
  "data": {
    "user": {
      "isError": false,
      "data": {
        "name": "qsona"
      }
    },
    "groups": {
      "isError": true,
      "reason": "Server is broken"
    }
  }
}

拣集落下的稻谷 (3)

    • GraphQLInt型は32bitまでしか対応していない

 

    オーバーフローして障害が発生

追溯

    • GraphQLをBFFに採用するのは、少なくとも銀の弾丸ではない

資産が使えるので、やりたいことにマッチすればメリットがある
自由度が効きにくい面もある


回顾

也许采取混合策略可能会更好。

    • BFF置くならやはり全部のリクエストを通したい。

 

    • 初めはただのProxyでいい。 (step by step BFF 参照)

 

    • GraphQLは、1つのエンドポイント (例えば POST /v1/graphql) を提供するだけ

同じサーバで他のエンドポイントを提供しても問題ない

エンドポイントは画面ごとに提供して、BFF内でGraphQLを完結させることもできる

(それをやりたいかはともかく…)


谢谢你!

我们正在招聘!

    • Microservices

hot: Asynchronus architecture

Node.js Server
React, Redux
GraphDB (TitanDB)
Fulltext Search
Ruby on Rails

v5 / api mode

Docker, Amazon ECS
iOS Swift / Android Java, Kotlin
Machine Learning

广告
将在 10 秒后关闭
bannerAds