172
120

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Node.js + GraphQLでBFFを作った話

Last updated at Posted at 2017-03-31
1 / 37

whoami

  • @qsona (Twitter, GitHub, Qiita)
  • Node.js developer
  • FiNC 2016/2 〜
    • Ruby, Rails / MySQL
    • love microservices!
  • Microservices Meetup 主催

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

構成図

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にドメインロジックが入る可能性を排除したかった


Why Node.js?

  • (我々の)BFFのメインの役割は、複数のBackend APIを呼び出して返すこと
    • 当然、並列に呼び出したい。非同期処理が大半になる
  • フロントエンジニアに触りやすくしたかった
    • 我々のクライアントサイドの言語: Swift, Java (Kotlin), JavaScript
    • (そもそもSwiftやJavaはここの選択肢に入りにくい)
    • JavaScriptはとっつきやすい(※個人の感想です)
    • 重厚な言語(※個人の感想です)は選択肢に入れにくい

Why Node.js?

  • qsonaがもともとNode畑だったので
    • 手伝ってくれた人もJavaScript畑
  • ReactのSSRを見越す

Why 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" }
    ]  
  }
}

今回は batch requestと呼びます。


GraphQLの特徴 (2)

あるリソースのうち、必要なkeyのみを取得できる

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

今回はfiltering queryと呼びます。


GraphQLの特徴 (3)

ネストされたリソースの、どの階層まで取るかを決められる

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)

Smart UIな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の責務とする

(画像)


改善した :muscle:


その後


BFF導入後の問題点 (1) クエリの管理

GraphQLの特徴 (2) で紹介した "filtering query" は特に何も解決していない

  • あるリソースのうちの一部分だけ取得したい、というユースケースがほとんどなかった
    • クライアントが、部分的なエンティティをわざわざ作らなければならない
    • 部分で取得すると、キャッシュもヒットできなくなる

BFF導入後の問題点 (1) クエリの管理

  • バックエンドにキーを追加するたびに、変更する必要がある
  • iOSとAndroidでの共通化
    • 本来は「iOSとAndroidで別々な方法でリソースを取れる」というメリットがある
    • しかし、そのようなユースケースは今の所なかった
    • クエリを共通管理することに

BFF導入後の問題点 (2) 改善の競合

  • Backendサーバ (Rails) 側も問題を改善してきた
    • JSON Hyper Schemaの導入
    • 使うシリアライザの変更 (rabl => active_model_serializers)
    • API設計レビュー、設計力の向上、設計の標準化
  • クライアントも、ドメインモデルの手前にJSONをマップする層を持っている

BFF導入後の問題点 (2) 改善の競合

  • 役割が結構かぶる
    • Server: JSON Schema / Serializer
    • BFF: GraphQLType
    • Client: 腐敗防止層
  • 必要かどうかは、チームの距離感の問題でもある
    • 結合度
    • そこまで遠くする必要はない

落穂拾い


落穂拾い (1) 更新系はどうしている?

  • ひとまず、更新系はBFFに持っていない
  • 更新系のオーケストレーションは、今後もしない
  • やるとしたら「このボタン(アクション)に対応するエンドポイントをBFFで持つ」
    • GraphQLではちょっと難しい

落穂拾い (2) エラー処理

  • GraphQLでは、レスポンスは errors または data のキーを返す
  • 複数リソースを同時に取得しようとして、1つが失敗した場合、errorsだけ返すのが仕様
  • BFFでは、成功したリソースは返しつつ、失敗したリソースはその理由を返す必要がある
    • つまり errors は基本使えない

落穂拾い (2) エラー処理

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

落穂拾い (3)

  • GraphQLInt型は32bitまでしか対応していない
  • オーバーフローして障害が発生 :cry:

振り返り

  • GraphQLをBFFに採用するのは、少なくとも銀の弾丸ではない
    • 資産が使えるので、やりたいことにマッチすればメリットがある
    • 自由度が効きにくい面もある

振り返り

ハイブリッド策を取るとよかったかも

  • BFF置くならやはり全部のリクエストを通したい。
  • 初めはただのProxyでいい。 (step by step BFF 参照)
  • GraphQLは、1つのエンドポイント (例えば POST /v1/graphql) を提供するだけ
    • 同じサーバで他のエンドポイントを提供しても問題ない
  • エンドポイントは画面ごとに提供して、BFF内でGraphQLを完結させることもできる
    • (それをやりたいかはともかく...)

Thank you!

We are hiring!!

  • 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
172
120
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
172
120

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?