Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
既存のRESTful な RailsプロジェクトにGraphQL導入を検討した話
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
hiroya iizuka
September 24, 2021
5.1k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
既存のRESTful な RailsプロジェクトにGraphQL導入を検討した話
hiroya iizuka
September 24, 2021
More Decks by hiroya iizuka
See All by hiroya iizuka
学問と資産
hiroyaiizuka
0
480
clean architecture と経営
hiroyaiizuka
0
1.5k
3つのNext.jsプロジェクトを 新卒エンジニアと一緒に 開発した話
hiroyaiizuka
1
470
ReactNative + microCMS の設計にすごく悩んだ話
hiroyaiizuka
1
1.2k
実戦に役立つFirebase_Analytics_使用集
hiroyaiizuka
0
180
Featured
See All Featured
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
540
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
390
How to Talk to Developers About Accessibility
jct
2
240
What's in a price? How to price your products and services
michaelherold
247
13k
Designing Powerful Visuals for Engaging Learning
tmiket
1
420
Discover your Explorer Soul
emna__ayadi
2
1.1k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Ruling the World: When Life Gets Gamed
codingconduct
0
260
Speed Design
sergeychernyshev
33
1.9k
Skip the Path - Find Your Career Trail
mkilby
1
150
So, you think you're a good person
axbom
PRO
2
2.1k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
870
Transcript
既存のRESTful な Rails プロジェクトに GraphQL 導⼊を検討した話 株式会社BeatFit CTO 飯塚 浩也
医師 循環器内科、総合診療医と して、⼤学 - 地⽅の病院を 8 年間勤務。 IT は⼤の苦⼿。 パソコン教室のアビバに
通っていた。 エンジニア 株式会社BeatFit にエンジニ アとして⼊社。 [ ⽇課] Codewars, LeetCode Hack The Box Type Challenge
None
今⽇のお話 ⼩規模エンジニア組織の課題 課題解決を⽬指した技術選定と現実とのギャップ 最終的にどんな教訓がえられたか? ⼩規模なエンジニア組織での 技術選定に参考になれば 🙏
うおおおお 〜! Twitter でインターン研修の連絡
内定のご連絡
翌⽇
レガシーな技術選定のせい・・・? Rxjs, Redux, REST API...
弊社エンジニアチームの歴史 2018 年創業 外部エンジニア2 名(frontend, backend) で開発スタート その後 React Native
エンジニア4 名 Rails エンジニア2 名 2020 年、資⾦繰りが⼀時悪化し 正社員エンジニア1 名、業務委託エンジニア2 名に・・・
技術スタック
Frontend
課題1. Redux による状態管理 Text Technology Radar
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
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
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
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
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
・創業当初、API ドキュメントがなく、⽕種が勃発 ・2019 年、Api client に Postman を導⼊ ・2020 年、Swagger
を導⼊ 課題3. REST API 資⾦難により、frontend, backend を1 ⼈で担当。 開発スピード低下を懸念し、Swagger のメンテナンスしなくなる😓
backend
課題1. REST API の問題 ・endpoint の命名に悩む 規則がなく統⼀感がない ( 単数形と複数形が混在しているなど) ・増⼤していく⼤量のAPI
達 (v1, v2, v3...)
課題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
課題3. frontend とのコミュニケーションの問題 ・クライアント都合( ちょっとした変更) で、サーバーサ イドに依頼して実装を修正するという煩わしさ ・API ドキュメントの管理コストが⼤きい ・frontend
は、API の実装もしくは、mock server の作 成を待たないと、実装が進められない
課題まとめ ☑ リリースから3 年が経過し、リファクタリング、API ドキュメントの メンテンスなどの、保守運⽤コストが増⼤してきた。 ☑ frontend では、時の流れとともにbest practice
がかわってきた。 を許容できなくなってきた。 ☑ backend では、REST 特有の問題である などが問題になってきた。 ユースケース毎のAPI 実装の煩わしさ API ドキュメント管理の煩わしさ コミュニケーションコスト増加 API の実装を待たないと実装ができない 型がないなどの開発体験
API endpoint 名で悩みたくない これ以上API endpoint 増やしたくない API ドキュメント管理煩わしい・・・! 型安全なSchema driven
な開発をしたい frontend の開発時期を遅らせたくない GraphQL を導⼊しよう!
エンジニア3 ⼈でTry したこと A さん: Frontend, GraphQL 経験++ B さん:
Backend, GraphQL 経験- わたし: Frontend, Backend, GraphQL 経験+/- A B
1. 会議 GraphQL っていいですよね! キャッチアップ よろしくお願いいたします!
2. 設計の検討 BFF
BFF (backend for frontend) フロントエンドとバックエンドの中間に位置 するサーバーを置くアーキテクチャ
API request 結婚⽣活
必要なrequest に個別に答えてくれる ⼀度やったことを覚えている マニュアルを作ってれる
Frontend Backend 関⼼ 関⼼
メリット Frontend は ServerSide の実装に 依存しない 型がつく Redux 廃⽌できる (Apollo
cache) overfetch 発⽣しない Server Side の実装が Frontend の細かい要求に 引っ張られなくなる API endpoint の悩みなし API ドキュメント管理不要 API の結果はキャッシュされ Frontend からのリクエスト削減
デメリット ・単⼀障害点 → BFF が何らかの理由で正常に機能しない場合、サービス全体に 影響が出る。 ・監視対象の増加と、デプロイの懸念事項の増加 → メンテナンスコスト増加 ・BFF
⾃体の実装負荷
コンウェイの法則からみた BFF 導⼊のタイミング システム設計( アーキテクチャ) は、組織構造 を反映させたものになる
組織は分離されるけどいいの? ⼈数少ないのに、疎な関係が正義なの? 横断的に開発している⼈はどうなる? コンウェイの法則からみた BFF 導⼊のタイミング システムの分離 = 組織の分離
各チームが独⽴した意思決定できるよう 他チームとの依存関係を断ち切る必要が あると判断される時 コンウェイの法則からみた BFF 導⼊のタイミング
Graphql Ruby の導⼊
REST → GraphQL への移⾏
なんとか、動くようになったが
消えぬ不安 ・エラーハンドリング設計 ・Schema 設計 ・Log 設計 ・N + 1 問題
etc ベストプラクティスが何もわからない・・・ 社内に知⾒を持ったエンジニアが不在で、⼿探りな状況 本番運⽤に耐えうる? ⾼速に開発できる???
最終的なジャッジ GraphQL 導⼊⾒送り😓
API endpoint 名で悩みたくない これ以上API endpoint 増やしたくない API ドキュメント管理煩わしい・・・! 型安全なSchema driven
な開発をしたい frontend の開発時期を遅らせたくない この2 つを対応しました
Schema 駆動開発 実装前に、wiki 上で schema とresponse の議論
得られた教訓 ⼩規模スタートアップで技術選定を決める時 組織構造に思いを馳せることが⼤事
エンジニアの⼈員が少ない コミュニケーションコストはまだ許容範囲内 サービスはまだスケールしていない サービス数がまだ少ない お互いが依存した密結合で value を発揮している組織のうちは 疎結合を促し組織を分割させてしまう技術選定は 時期尚早かも
リソース不⾜な状況では 社内に⼗分な経験をもったエンジニアがいない 新しめの技術選定は悪⼿になる 無理をせず、ビジネスの成⻑を優先し リソース確保ができるまで耐える 得られた教訓
まとめ 保守運⽤の課題を抱えた⼩規模スタートアップで GraphQL を導⼊しようと奮闘した しかし、導⼊によるシステムの成⻑が組織の成⻑に おいつかず、ビジネスへの悪影響が懸念された 苦しい時は無理をせず、ビジネスの成⻑とリソースの確保 を待ってから、攻めに転じるタイミングの⾒極めが重要 と思われた