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

スモールビジネスを、 世界の主役に。