用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