freee Tech Night #2 会計freee 7年目のフロントエンド開発
by
Takumi Ohashi
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
freee 株式会社 会計freee 7年目のフロントエンド開発 2018.03.26 freee Tech Night #2 Takumi Ohashi / @_tohashi
Slide 2
Slide 2 text
2 今日する話 ● 会計freeeというサービスは単一のリポジトリで7年くらいやってきました ● フロントエンドもその中でいろいろあったという話
Slide 3
Slide 3 text
3 今日する話 ● 会計freeeというサービスは単一のリポジトリで7年くらいやってきました ● フロントエンドもその中でいろいろあったという話 ○ = 失敗を重ねつつ泥臭くやっていっているという話
Slide 4
Slide 4 text
● ソシャゲ界出身 ● 2014年12月入社 ● フロントエンド寄りエンジニア ● 社内でやってること ○ 会計freeeの開発 ○ フロントエンド啓蒙活動 ○ ボルダリング部部長 Takumi Ohashi / @_tohashi Webアプリケーションエンジニア 4
Slide 5
Slide 5 text
会計freeeについて
Slide 6
Slide 6 text
6 会計freee ● 経理業務を始めとして請求書、ワークフロー管理など ● 2013年ローンチ ● first commit は2012年 ● モノリシックな Rails アプリケーション ○ 一部機能はマイクロサービス化 ● フロントエンドも同じリポジトリに
Slide 7
Slide 7 text
7 会計freee
Slide 8
Slide 8 text
8 会計freee
Slide 9
Slide 9 text
9 会計freee
Slide 10
Slide 10 text
10 規模 拡張子と行数 ※1 CoffeeScript向けのテンプレートエンジン .js 352,219 .coffee 49,015 .scss 99,084 .eco※1 16,434 .html.erb 48,116
Slide 11
Slide 11 text
11 その他規模など DBテーブル数 約650 PR番号 45,000超 デプロイ 平均 1/day 2019年の Contributor数 50超 エンドポイント数 3000弱
Slide 12
Slide 12 text
12 freeeのWebアプリケーションエンジニア ● フロントエンドエンジニアとサーバーサイドエンジニアの区分はない ● Rails と React が必修スキルとなる ● もちろん個々の得意領域はある ○ フロントエンド寄りだったり
Slide 13
Slide 13 text
会計freeeの フロントエンド
Slide 14
Slide 14 text
14 ローンチ〜2014ごろ ● Backbone.js ● jQuery ● CoffeeScript ● ECO(テンプレートエンジン) ● SCSS
Slide 15
Slide 15 text
15 ローンチ〜2014ごろ ● 秩序なき世界 ○ 所謂片手間JS ○ フロントエンドは今ほど複雑な要件ではなかった ● ファイル間の依存解決なし ○ 一つのグローバル変数に全てのクラス ○ Sprocketsで結合して1つのjs, cssファイルに
Slide 16
Slide 16 text
16 freee-js-framework ● 所謂社内フレームワーク ● 2つめのアプリ(人事労務freee)開発に伴い切り出されたBackbone.jsのラッパー部 分 + α ○ イベント管理、メモリリーク防止、非同期処理など
Slide 17
Slide 17 text
17 社内フレームワークあるある ● メンテナの欠如 ● いろいろやっていく意思だけがある 状態
Slide 18
Slide 18 text
18 2015〜フロントエンド委員会の発足 この辺りの取り組みに関する過去の資料はこちら https://www.slideshare.net/tkm64/webpack-62692382 https://speakerdeck.com/tohashi/hurontoendofalsemodanhua-tojavascriptmoziyurufalse-yi-cun-jie-jue ● フロントエンドに関心のあるメンバーでフロントエンドの課題洗い出し ○ ファイル間の依存解決、エントリーポイントの分割 ○ ES2015~ への移行 ○ React の導入 ○ フロントエンドのリソースは Webpack でビルドする ● 段階的にこれらの移行を行っていった
Slide 19
Slide 19 text
19 現在 ● 主要な機能はほぼ ES2015~ / React に移行 ○ SPAではなく、機能ごとに独立したエントリーポイント ○ 一部は CoffeeScript / Backbone のまま ● ESLint, Prettierによる静的解析とフォーマット ● flowtypeによる静的型付け ● Storybookによるコンポーネントカタログ ● テストはJest, Storyshots
Slide 20
Slide 20 text
20 Storybook, Storyshots ● コンポーネントのカタログを作成 ● @storybook/addon-storyshots を使うことでstoryごとのDOMのスナップショットを生 成し、差分があれば検知
Slide 21
Slide 21 text
21 レガシー環境から今っぽい環境へ ● 段階的に移行してきた ● 並行してコードベースも開発組織も急速に拡大 ● その過程で数々の失敗も生まれ、一部は負債となってしまった ○ ライブラリの選定ミス ○ アンチパターンな設計 ● フロントエンドが安定してきた今、負債の解消と治安の維持が品質や生産性向上の 為にも急務
Slide 22
Slide 22 text
実際の失敗例
Slide 23
Slide 23 text
facebook/flux を使い続けた
Slide 24
Slide 24 text
24 facebook/flux を使い続けた ● flux が指すものは2つ ○ 1. facebook が提唱したデータフローのアーキテクチャ ○ 2. 1のリファレンス実装ライブラリ ● 会計freeeでは2を使用している ● Dispatcher を提供する flux と Container, ReduceStore を提供するflux-utils からな る非常に薄いライブラリ ○ Redux で言うと Container = Provider, ReduceStore = Reducer
Slide 25
Slide 25 text
25 facebook/flux 導入の背景 ● 2015年 React 導入時に flux 用ライブラリを選定 ● 当時 flux 系ライブラリが乱立 ○ 既に Redux が頭一つ抜けてはいたが、ミドルウェア等のエコシステムへのロック インを危惧、薄い方がいいという判断
Slide 26
Slide 26 text
26 facebook/flux で起きた諸問題 ● 治安の乱れ ● 薄いゆえに色々できてしまう ○ Component から直接 Store を参照、単方向データフローの崩壊 ○ Component と ActionCreator の密結合、dispatchの起点が不明瞭に ● 今やマイナーなので書き方のサンプルがインターネットに少ない
Slide 27
Slide 27 text
27 昨年Reduxへの移行を検討するも... ● 時既に遅し ○ facebook/flux を利用したコードは膨大な量に ■ 移行作業はActionCreatorとdispatchの分解が特にネック ○ facebook/flux と Redux が共存する移行期が長期に渡りそう ● 2015年時点で facebook/flux を選んだのは完全に間違いではなかった(と思う) ○ ただもっと早く全体を見渡して移行の判断を下すべきだった
Slide 28
Slide 28 text
28 facebook/flux でなるべく治安を保っていく ● リファレンス実装の明示 ● Lintで防げる箇所は防ぐ ○ Container component 以外で Action の import 禁止など ● 実質社内フレームワークのようになっていってしまうリスクはあるが・・・
Slide 29
Slide 29 text
29 FSA(Flux Standard Action)の採用 https://github.com/redux-utilities/flux-standard-action ● Redux 等で推奨されている Action の規約 ● flowtype で dispatch の引数がこの規約に沿うよう チェック
Slide 30
Slide 30 text
コンポーネントの状態を Storeで管理する
Slide 31
Slide 31 text
31 コンポーネントの状態をStoreで管理する ● 例えば以下のような状態 ○ モーダルの開閉 ○ ローディング ○ フォームの入力値 ● これらの状態を持つ UIStore を作って flux のデータフローに載せる ○ ※ここで言う Store = Redux における Reducer です ● コンポーネントの状態の変更も Action を通じて行う
Slide 32
Slide 32 text
32 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils
Slide 33
Slide 33 text
33 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils コンポーネント側のトリガー発火
Slide 34
Slide 34 text
34 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils APIリクエスト開始を示す Actionをdispatch
Slide 35
Slide 35 text
35 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils スピナーの状態をvisibleにして コンポーネントに通知
Slide 36
Slide 36 text
36 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils APIリクエスト送信
Slide 37
Slide 37 text
37 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils APIリクエスト終了を示す Actionをdispatch APIリクエスト終了
Slide 38
Slide 38 text
38 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils リクエストペイロードを処理 スピナーの状態をhiddenにして コンポーネントに通知
Slide 39
Slide 39 text
39 State をどこで管理すべきか、明確な答えはない https://redux.js.org/faq/organizing-state ● RealWorld 等のサンプル見ても色々な流派が ● Redux の公式ドキュメントFAQ:適宜判断しましょう(要約)
Slide 40
Slide 40 text
40 ローディングひとつで一本記事が書ける https://developers.freee.co.jp/entry/react-loading-pattern ● 非同期処理に関しては将来的に React.Suspense という選択肢も
Slide 41
Slide 41 text
41 コンポーネントの状態をStoreで管理してきた所感 ● 親から子のStateが見たいなら、それは親のStateであるべき ● Reducer の肥大化がつらい ○ 非同期ごとに2つのAction ● コンポーネントで扱うか、Storeで扱うかの判断基準はどこに?
Slide 42
Slide 42 text
42 ライフサイクル内でdispatchの悲劇 ● UIStoreの変化タイミングを取るために、componentDidUpdate 等のライフサイクル で prevProps と比較 ○ ライフサイクルから Action が dispatch される事態が多発 ● facebook/flux では dispatch からの同一イベントループ内で再度 dispath を呼び出 すと例外が投げられる ● コンポーネントの動作自体に影響はないと思いきや・・・
Slide 43
Slide 43 text
43 React16アップデート時の思わぬ障壁に ● React16からはコンポーネント内で Uncaught Error が発生すると root まで遡ってコ ンポーネント全体が unmount される ○ 表示上の不整合を防ぐための仕様変更 ● Error Boundary を設定するか例外処理をちゃんとする ● この場合そもそもライフサイクルから dispatch しない
Slide 44
Slide 44 text
44 指針を統一 ● コンポーネントの状態はコンポーネントが持つ ● 親から参照したければそれは親のState ● async/await が使えればよりシンプルに書ける
Slide 45
Slide 45 text
Reactコンポーネントの継承
Slide 46
Slide 46 text
46 コンポーネントの継承 ● かつて BaseModal という コンポーネントが発生 ● モーダル関連の共通ロジックを 抽象化 ● 継承して各renderメソッドを override
Slide 47
Slide 47 text
47 実際の運用 ● renderごとoverride ● BaseModal 側の1メソッドを使いた かった模様 ● 実質単なるヘルパのinclude
Slide 48
Slide 48 text
48 そもそもReactで継承はすべきではない ● ReactのコンポーネントはViewControllerではない ○ MVCからの移行だとオブジェクト指向的に捉えがち ● 常に同じ props から同じ DOM を生成するアトミックな関数 ○ render という明確な主体 ● 抽象化は別のアプローチで行う ○ Higher Order Component ○ Render Props ○ children
Slide 49
Slide 49 text
49 公式ドキュメントには https://reactjs.org/docs/composition-vs-inheritance.html
Slide 50
Slide 50 text
失敗を振り返って
Slide 51
Slide 51 text
51 失敗を振り返って ● これらの設計やライブラリ全てが悪というわけではない ○ 少なくとも大規模開発というコンテキストでは辛い面が多かった ● 問題の根幹は初期の設計や規約がちゃんと整備できていなかったこと ● 大規模ゆえに一度定着してしまうと軌道修正が難しい ● 分業制ではない = 練度もバラバラ ○ 設計の指針が示されていないと悪いコンポーネントが参考にされてしまう ● サーバーサイド含めて見ている中で、警察業務は限界がある
Slide 52
Slide 52 text
52 分業でなくても秩序を保っていくために ● 月並みだけどフロントエンドの設計に関してもコンセンサスを取っていくことが大事 ○ コーディングガイドライン ○ リファレンス実装の明示 ○ コンポーネントカタログ ● 自動化できるところ ○ ESLint整備 ○ Renovate による継続的なパッケージのアップデート
Slide 53
Slide 53 text
53 Renovate ● package.json の更新PRを送ってきてくれるサービス ○ CHANGELOG などもまとめてくれる
Slide 54
Slide 54 text
54 Renovate ● 頻度や粒度、グルーピングなど細かく設定できる ● パッケージ数多いと手動でやるのは意外と面倒 ● 確認とマージ作業は色々な人に担当してもらい、フロントエンドのパッケージ周りに 触れる機会を増やしていく
Slide 55
Slide 55 text
55 組織全体への取り組み ● プロダクト立ち上げ時のボイラープレートを提供 ○ 推奨パッケージなど ○ もちろん自分で選定したい人はそちらでも ● フロントエンドについて少人数で話す会 ○ flux がなぜ必要か ○ Babel, Webpack, npm の役割と関係
Slide 56
Slide 56 text
56 今後の展望 ● 共通UIコンポーネントライブラリの開発 ○ freeeのプロダクト間で統一的なスタイル・挙動の画面を誰でも 作れるように ● フロントエンド委員会も継続的に活動中 ○ 課題はまだまだ山盛り ○ 全機能の React 化 ○ React Hooks を行儀よくやっていくためには
Slide 57
Slide 57 text
スモールビジネスを、 世界の主役に。