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

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

hiroya iizuka
September 24, 2021
4.9k

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

hiroya iizuka

September 24, 2021
Tweet

Transcript

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

    通っていた。 エンジニア 株式会社BeatFit にエンジニ アとして⼊社。 [ ⽇課] Codewars, LeetCode Hack The Box Type Challenge
  2. 弊社エンジニアチームの歴史 2018 年創業 外部エンジニア2 名(frontend, backend) で開発スタート その後 React Native

    エンジニア4 名 Rails エンジニア2 名 2020 年、資⾦繰りが⼀時悪化し 正社員エンジニア1 名、業務委託エンジニア2 名に・・・
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. ・創業当初、API ドキュメントがなく、⽕種が勃発 ・2019 年、Api client に Postman を導⼊ ・2020 年、Swagger

    を導⼊ 課題3. REST API 資⾦難により、frontend, backend を1 ⼈で担当。 開発スピード低下を懸念し、Swagger のメンテナンスしなくなる😓
  9. 課題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
  10. 課題まとめ ☑ リリースから3 年が経過し、リファクタリング、API ドキュメントの メンテンスなどの、保守運⽤コストが増⼤してきた。 ☑ frontend では、時の流れとともにbest practice

    がかわってきた。 を許容できなくなってきた。 ☑ backend では、REST 特有の問題である などが問題になってきた。 ユースケース毎のAPI 実装の煩わしさ API ドキュメント管理の煩わしさ コミュニケーションコスト増加 API の実装を待たないと実装ができない 型がないなどの開発体験
  11. エンジニア3 ⼈でTry したこと A さん: Frontend, GraphQL 経験++ B さん:

    Backend, GraphQL 経験- わたし: Frontend, Backend, GraphQL 経験+/- A B
  12. メリット Frontend は ServerSide の実装に 依存しない 型がつく Redux 廃⽌できる (Apollo

    cache) overfetch 発⽣しない Server Side の実装が Frontend の細かい要求に 引っ張られなくなる API endpoint の悩みなし API ドキュメント管理不要 API の結果はキャッシュされ Frontend からのリクエスト削減
  13. 消えぬ不安 ・エラーハンドリング設計 ・Schema 設計 ・Log 設計 ・N + 1 問題

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