Upgrade to Pro — share decks privately, control downloads, hide ads and more …

既存のRESTful な RailsプロジェクトにGraphQL導入を検討した話

18c4748985323ef0e69f3436f89fbdb8?s=47 hiroya iizuka
September 24, 2021
430

既存のRESTful な RailsプロジェクトにGraphQL導入を検討した話

18c4748985323ef0e69f3436f89fbdb8?s=128

hiroya iizuka

September 24, 2021
Tweet

Transcript

  1. 既存のRESTful な Rails プロジェクトに GraphQL 導⼊を検討した話 株式会社BeatFit CTO 飯塚 浩也

  2. 医師 循環器内科、総合診療医と して、⼤学 - 地⽅の病院を 8 年間勤務。 IT は⼤の苦⼿。 パソコン教室のアビバに

    通っていた。 エンジニア 株式会社BeatFit にエンジニ アとして⼊社。 [ ⽇課] Codewars, LeetCode Hack The Box Type Challenge
  3. None
  4. 今⽇のお話 ⼩規模エンジニア組織の課題 課題解決を⽬指した技術選定と現実とのギャップ 最終的にどんな教訓がえられたか? ⼩規模なエンジニア組織での 技術選定に参考になれば 🙏

  5. うおおおお 〜! Twitter でインターン研修の連絡

  6. 内定のご連絡

  7. 翌⽇

  8. レガシーな技術選定のせい・・・? Rxjs, Redux, REST API...

  9. 弊社エンジニアチームの歴史 2018 年創業 外部エンジニア2 名(frontend, backend) で開発スタート その後 React Native

    エンジニア4 名 Rails エンジニア2 名 2020 年、資⾦繰りが⼀時悪化し 正社員エンジニア1 名、業務委託エンジニア2 名に・・・
  10. 技術スタック

  11. Frontend

  12. 課題1. Redux による状態管理 Text Technology Radar

  13. import { ajax } from 'rxjs/ajax'; const fetchUserEpic = (action$,

    state$) => action$.pipe( ofType('FETCH_USER'), mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( map(response => ({ type: 'FETCH_USER_FULFILLED', payload: response })) ) ); 1 2 3 4 5 6 7 8 9 10 11 ・コードが複雑で、Rxjs の学習コストが⾼い ・API response に型がつかない 課題2. Redux Observable
  14. import { ajax } from 'rxjs/ajax'; const fetchUserEpic = (action$,

    state$) => action$.pipe( ofType('FETCH_USER'), mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( map(response => ({ type: 'FETCH_USER_FULFILLED', payload: response })) ) ); 1 2 3 4 5 6 7 8 9 10 11 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 5 map(response => ({ 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 ・コードが複雑で、Rxjs の学習コストが⾼い ・API response に型がつかない 課題2. Redux Observable
  15. import { ajax } from 'rxjs/ajax'; const fetchUserEpic = (action$,

    state$) => action$.pipe( ofType('FETCH_USER'), mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( map(response => ({ type: 'FETCH_USER_FULFILLED', payload: response })) ) ); 1 2 3 4 5 6 7 8 9 10 11 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 5 map(response => ({ 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 map(response => ({ import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( 5 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 ・コードが複雑で、Rxjs の学習コストが⾼い ・API response に型がつかない 課題2. Redux Observable
  16. import { ajax } from 'rxjs/ajax'; const fetchUserEpic = (action$,

    state$) => action$.pipe( ofType('FETCH_USER'), mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( map(response => ({ type: 'FETCH_USER_FULFILLED', payload: response })) ) ); 1 2 3 4 5 6 7 8 9 10 11 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 5 map(response => ({ 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 map(response => ({ import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( 5 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 payload: response import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( 5 map(response => ({ 6 type: 'FETCH_USER_FULFILLED', 7 8 })) 9 ) 10 ); 11 ・コードが複雑で、Rxjs の学習コストが⾼い ・API response に型がつかない 課題2. Redux Observable
  17. import { ajax } from 'rxjs/ajax'; const fetchUserEpic = (action$,

    state$) => action$.pipe( ofType('FETCH_USER'), mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( map(response => ({ type: 'FETCH_USER_FULFILLED', payload: response })) ) ); 1 2 3 4 5 6 7 8 9 10 11 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 5 map(response => ({ 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 map(response => ({ import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( 5 6 type: 'FETCH_USER_FULFILLED', 7 payload: response 8 })) 9 ) 10 ); 11 payload: response import { ajax } from 'rxjs/ajax'; 1 2 const fetchUserEpic = (action$, state$) => action$.pipe( 3 ofType('FETCH_USER'), 4 mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( 5 map(response => ({ 6 type: 'FETCH_USER_FULFILLED', 7 8 })) 9 ) 10 ); 11 import { ajax } from 'rxjs/ajax'; const fetchUserEpic = (action$, state$) => action$.pipe( ofType('FETCH_USER'), mergeMap(({ payload }) => ajax.getJSON(`/api/users/${payload}`).pipe( map(response => ({ type: 'FETCH_USER_FULFILLED', payload: response })) ) ); 1 2 3 4 5 6 7 8 9 10 11 ・コードが複雑で、Rxjs の学習コストが⾼い ・API response に型がつかない 課題2. Redux Observable
  18. ・創業当初、API ドキュメントがなく、⽕種が勃発 ・2019 年、Api client に Postman を導⼊ ・2020 年、Swagger

    を導⼊ 課題3. REST API 資⾦難により、frontend, backend を1 ⼈で担当。 開発スピード低下を懸念し、Swagger のメンテナンスしなくなる😓
  19. backend

  20. 課題1. REST API の問題 ・endpoint の命名に悩む 規則がなく統⼀感がない ( 単数形と複数形が混在しているなど) ・増⼤していく⼤量のAPI

    達 (v1, v2, v3...)
  21. 課題2. controller, View の問題 ・restful な設計に、⼀部なっていない 独⾃で定義されているaction が多い ・jbuilder にfrontend

    で使われないデータが混⼊する # app/controllers/api/v1/user_controller.rb class Api::V1::UserController < ApiController def create ... end def web_registration ... end end 1 2 3 4 5 6 7 8 9 10 11 12
  22. 課題3. frontend とのコミュニケーションの問題 ・クライアント都合( ちょっとした変更) で、サーバーサ イドに依頼して実装を修正するという煩わしさ ・API ドキュメントの管理コストが⼤きい ・frontend

    は、API の実装もしくは、mock server の作 成を待たないと、実装が進められない
  23. 課題まとめ ☑ リリースから3 年が経過し、リファクタリング、API ドキュメントの メンテンスなどの、保守運⽤コストが増⼤してきた。 ☑ frontend では、時の流れとともにbest practice

    がかわってきた。 を許容できなくなってきた。 ☑ backend では、REST 特有の問題である などが問題になってきた。 ユースケース毎のAPI 実装の煩わしさ API ドキュメント管理の煩わしさ コミュニケーションコスト増加 API の実装を待たないと実装ができない 型がないなどの開発体験
  24. API endpoint 名で悩みたくない これ以上API endpoint 増やしたくない API ドキュメント管理煩わしい・・・! 型安全なSchema driven

    な開発をしたい frontend の開発時期を遅らせたくない GraphQL を導⼊しよう!
  25. エンジニア3 ⼈でTry したこと A さん: Frontend, GraphQL 経験++ B さん:

    Backend, GraphQL 経験- わたし: Frontend, Backend, GraphQL 経験+/- A B
  26. 1. 会議 GraphQL っていいですよね! キャッチアップ よろしくお願いいたします!

  27. 2. 設計の検討 BFF

  28. BFF (backend for frontend) フロントエンドとバックエンドの中間に位置 するサーバーを置くアーキテクチャ

  29. API request 結婚⽣活

  30. 必要なrequest に個別に答えてくれる ⼀度やったことを覚えている マニュアルを作ってれる

  31. Frontend Backend 関⼼ 関⼼

  32. メリット Frontend は ServerSide の実装に 依存しない 型がつく Redux 廃⽌できる (Apollo

    cache) overfetch 発⽣しない Server Side の実装が Frontend の細かい要求に 引っ張られなくなる API endpoint の悩みなし API ドキュメント管理不要 API の結果はキャッシュされ Frontend からのリクエスト削減
  33. デメリット ・単⼀障害点 → BFF が何らかの理由で正常に機能しない場合、サービス全体に 影響が出る。 ・監視対象の増加と、デプロイの懸念事項の増加 → メンテナンスコスト増加 ・BFF

    ⾃体の実装負荷
  34. コンウェイの法則からみた BFF 導⼊のタイミング システム設計( アーキテクチャ) は、組織構造 を反映させたものになる

  35. 組織は分離されるけどいいの? ⼈数少ないのに、疎な関係が正義なの? 横断的に開発している⼈はどうなる? コンウェイの法則からみた BFF 導⼊のタイミング システムの分離 = 組織の分離

  36. 各チームが独⽴した意思決定できるよう 他チームとの依存関係を断ち切る必要が あると判断される時 コンウェイの法則からみた BFF 導⼊のタイミング

  37. Graphql Ruby の導⼊

  38. REST → GraphQL への移⾏

  39. なんとか、動くようになったが

  40. 消えぬ不安 ・エラーハンドリング設計 ・Schema 設計 ・Log 設計 ・N + 1 問題

    etc ベストプラクティスが何もわからない・・・ 社内に知⾒を持ったエンジニアが不在で、⼿探りな状況 本番運⽤に耐えうる? ⾼速に開発できる???
  41. 最終的なジャッジ GraphQL 導⼊⾒送り😓

  42. API endpoint 名で悩みたくない これ以上API endpoint 増やしたくない API ドキュメント管理煩わしい・・・! 型安全なSchema driven

    な開発をしたい frontend の開発時期を遅らせたくない この2 つを対応しました
  43. Schema 駆動開発 実装前に、wiki 上で schema とresponse の議論

  44. 得られた教訓 ⼩規模スタートアップで技術選定を決める時 組織構造に思いを馳せることが⼤事

  45. エンジニアの⼈員が少ない コミュニケーションコストはまだ許容範囲内 サービスはまだスケールしていない サービス数がまだ少ない お互いが依存した密結合で value を発揮している組織のうちは 疎結合を促し組織を分割させてしまう技術選定は 時期尚早かも

  46. リソース不⾜な状況では 社内に⼗分な経験をもったエンジニアがいない 新しめの技術選定は悪⼿になる 無理をせず、ビジネスの成⻑を優先し リソース確保ができるまで耐える 得られた教訓

  47. まとめ 保守運⽤の課題を抱えた⼩規模スタートアップで GraphQL を導⼊しようと奮闘した しかし、導⼊によるシステムの成⻑が組織の成⻑に おいつかず、ビジネスへの悪影響が懸念された 苦しい時は無理をせず、ビジネスの成⻑とリソースの確保 を待ってから、攻めに転じるタイミングの⾒極めが重要 と思われた