Slide 1

Slide 1 text

新タクシー配車システムの裏側 2017-05-23 : ヒカラボ発表資料 JapanTaxi : 相田 岳彦

Slide 2

Slide 2 text

自己紹介 2016-06 JapanTaxi 入社 06-07 月 某 API サーバ / Golang 07-11 月 全国タクシー Android / Java 10-12 月 Google Maps 対応 12-01 月 某バッチサーバ / Scala 12-02 月 全国タクシー API / Rails 03 月- 新タクシー配車システム / ES6, Rails いろいろやってます

Slide 3

Slide 3 text

今回話すこと 新タクシー配車システムの紹介 技術的負債を避けるための取り組み Rails API 開発編 React フロントエンド開発編

Slide 4

Slide 4 text

今回話さないこと Rails や React アプリの構築方法 CI/CD の構築方法

Slide 5

Slide 5 text

そもそも配車システムって何さ ざっくりまとめると

Slide 6

Slide 6 text

お客さんからの注文を受けて オペレータが空車状況などを確認して タクシーを配車するためのシステム

Slide 7

Slide 7 text

今回話すところ API は Rails で作られていて フロントは React で作られているサービス

Slide 8

Slide 8 text

ということで技術的な話

Slide 9

Slide 9 text

まずは Rails の話から

Slide 10

Slide 10 text

Rails 開発で防いでおきたかったこと とりあえず ID パターン テーブルモデル中毒

Slide 11

Slide 11 text

とりあえず ID パターン ( ご存知の方)

Slide 12

Slide 12 text

あらゆるテーブルに ID を入れてしまう症状 詳細は "SQL アンチパターン" 参照

Slide 13

Slide 13 text

大前提 ウェブフレームワークの都合で テーブル設計を歪める判断は基本的にありえない

Slide 14

Slide 14 text

対処 Rails にはきちんと primary key を 明示する仕組みが用意されてる

Slide 15

Slide 15 text

Q. そうはいっても "id" がないと 冗長になるコードはあるよね? A. それでもプロダクト根幹の DB 設計を歪めるほうがダメ

Slide 16

Slide 16 text

テーブルモデル中毒 ( ご存知の方)

Slide 17

Slide 17 text

先週名付けました ( 聞いたことがなくても安心してください)

Slide 18

Slide 18 text

テーブルと 1:1 で紐付いた「モデル」で 全てを表現しようとする症状

Slide 19

Slide 19 text

行き着く先は 典型的なファットモデル… !

Slide 20

Slide 20 text

これが rails-way なのだという誤解を 頻繁に見かける超要注意パターン

Slide 21

Slide 21 text

テーブルモデル中毒を 予防するには… ?

Slide 22

Slide 22 text

レビューで解決… ? レビュアーが全員 毎回忘れずに チェックするのは現実的だろうか(反語)

Slide 23

Slide 23 text

開発者全員が危険性と 回避方法を理解すればいい… ?

Slide 24

Slide 24 text

しかし未経験の負債への対処はとても難しい ファットモデルのヤバさは ファットになるまで気付けない ファットになってしまったら もうすでに遅かったりして…

Slide 25

Slide 25 text

あらためて テーブルモデル中毒を 予防するには一体どうしたら… ?

Slide 26

Slide 26 text

私たちの答え 「モデリングを開発フローに組み込もう」

Slide 27

Slide 27 text

具体的には? アクションごとの仕様を宣言するための 言語内 DSL をコントローラに用意

Slide 28

Slide 28 text

個々のリクエストとレスポンスに 対応するクラスをまず最初に定義する それぞれのクラスに対応する JSON Schema ファイルも生成する

Slide 29

Slide 29 text

コード例 update アクションの仕様を宣言する場合 api :update, :request => Requests::V1::Hello::CreateParameter :response => Resources::V1::Greeting::Hello

Slide 30

Slide 30 text

これによって テーブルモデルを引き回すコードや view で巨大な JSON を構築するようなコード が生まれてしまう余地を取り除いた

Slide 31

Slide 31 text

さらに 指定した型にならないリクエストは 400 指定した型にならないレスポンスは 500 も自動化できた!

Slide 32

Slide 32 text

副次的な効果

Slide 33

Slide 33 text

JSON Schema を導入したことで : API 仕様が自動的にバージョン管理される うっかり破壊的変更を入れても簡単に気付ける (スキーマファイルも変更されるため)

Slide 34

Slide 34 text

リソースのモデリングによって : リソースの型をバージョニングできる 安全にバージョンアップできる 機能拡張に対する心理的障壁の除去

Slide 35

Slide 35 text

言語内 DSL の表現力によって リソースを今まで通りに抽象化できる JSON Schema の手作業記述を回避 正規表現のコピペ問題なども回避

Slide 36

Slide 36 text

次は React の話 というよりは React-Redux の話

Slide 37

Slide 37 text

React-Redux で防いでおきたかったこと implicit state implicit action fat dispatcher

Slide 38

Slide 38 text

implicit state 実装者の頭の中だけに state の定義がある状態

Slide 39

Slide 39 text

implicit state 「どのような状態が正しいのか」が コードで表現されていない状態 フィールドの更新漏れ・追加忘れといった 人的ミスが誘発される

Slide 40

Slide 40 text

解決方法 全ての state をクラスとして明示的に定義 初期状態や更新方法をクラス内で完結させる

Slide 41

Slide 41 text

implicit action 実装者の頭の中だけに action の定義がある状態

Slide 42

Slide 42 text

implicit action 「この action にはどのような値があるのか」 「この action はどの type と紐付いているのか」 抱えている問題は implicit state とほぼ同じ

Slide 43

Slide 43 text

解決方法 対処策も implicit state とほぼ同じ ( ただし action の場合はクラス化の必要なし)

Slide 44

Slide 44 text

具体的には… ? action と type を紐付ける ActionFactory を用意

Slide 45

Slide 45 text

これによって : factory に action の定義が明文化される reducer の依存先を action-factory だけにできる 文字列定数の type を羅列する苦行の回避

Slide 46

Slide 46 text

コード例 actions.js // 該当する顧客が見つかった場合のアクション export const CustomerFound = ActionFactory({ type: 'OrderEditor/CustomerFound', /** * @param {CustomerOutline} customer */ currentCustomer: customer => customer, });

Slide 47

Slide 47 text

reducer.js // 注文編集フォームの reducer export const editorReducer = ReducerFactory.create({ initializer: () => { return EditorState.empty(); }, /** * @param {EditorState} state * @param {Object} action - see ./actions.js */ handlers: (state, action) => ({ [CustomerFound]: () => { return state.updateState({ customer: action.currentCustomer, }); }, }), ... });

Slide 48

Slide 48 text

fat dispatcher 文字通り dispatcher が肥大化する症状 fat model や fat controller と同種のもの

Slide 49

Slide 49 text

解決方法 dispather の責務を絞る

Slide 50

Slide 50 text

具体的には? dispatcher は 文字通り action を dispatch するだけに留める 各種データの取得処理は他のモジュールに切り出す 典型例は API リクエスト レイヤードアーキテクチャを導入して責務を分担

Slide 51

Slide 51 text

レイヤー構造 layouts 画面内の component に紐づく処理 domain 複数の component で共有するロジック repository API リクエスト ストレージ (cookie, LocalStorage) 操作 dispatcher が利用する多くのモジュールは ほとんどの場合はこのレイヤにある (相対パス地獄は webpack の resolve.root 指定で回避)

Slide 52

Slide 52 text

まとめ Rails Rails Way をダメな設計の弁明手段にしない ダメなのは必ずしも Rails のせいではない React (Redux) サンプルコードはそのまま真似してはいけない プロジェクトに適した方法で抽象化しよう - n-