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

freee Tech Night #2 会計freee 7年目のフロントエンド開発

freee Tech Night #2 会計freee 7年目のフロントエンド開発

4d0bec5f32c219c0f5d4280a2a4b250a?s=128

Takumi Ohashi

March 26, 2019
Tweet

Transcript

  1. freee 株式会社 会計freee 7年目のフロントエンド開発 2018.03.26 freee Tech Night #2 Takumi

    Ohashi / @_tohashi
  2. 2 今日する話 • 会計freeeというサービスは単一のリポジトリで7年くらいやってきました • フロントエンドもその中でいろいろあったという話

  3. 3 今日する話 • 会計freeeというサービスは単一のリポジトリで7年くらいやってきました • フロントエンドもその中でいろいろあったという話 ◦ = 失敗を重ねつつ泥臭くやっていっているという話

  4. • ソシャゲ界出身 • 2014年12月入社 • フロントエンド寄りエンジニア • 社内でやってること ◦ 会計freeeの開発

    ◦ フロントエンド啓蒙活動 ◦ ボルダリング部部長 Takumi Ohashi / @_tohashi Webアプリケーションエンジニア 4
  5. 会計freeeについて

  6. 6 会計freee • 経理業務を始めとして請求書、ワークフロー管理など • 2013年ローンチ • first commit は2012年

    • モノリシックな Rails アプリケーション ◦ 一部機能はマイクロサービス化 • フロントエンドも同じリポジトリに
  7. 7 会計freee

  8. 8 会計freee

  9. 9 会計freee

  10. 10 規模 拡張子と行数 ※1 CoffeeScript向けのテンプレートエンジン .js 352,219 .coffee 49,015 .scss

    99,084 .eco※1 16,434 .html.erb 48,116
  11. 11 その他規模など DBテーブル数 約650 PR番号 45,000超 デプロイ 平均 1/day 2019年の

    Contributor数 50超 エンドポイント数 3000弱
  12. 12 freeeのWebアプリケーションエンジニア • フロントエンドエンジニアとサーバーサイドエンジニアの区分はない • Rails と React が必修スキルとなる •

    もちろん個々の得意領域はある ◦ フロントエンド寄りだったり
  13. 会計freeeの フロントエンド

  14. 14 ローンチ〜2014ごろ • Backbone.js • jQuery • CoffeeScript • ECO(テンプレートエンジン)

    • SCSS
  15. 15 ローンチ〜2014ごろ • 秩序なき世界 ◦ 所謂片手間JS ◦ フロントエンドは今ほど複雑な要件ではなかった • ファイル間の依存解決なし

    ◦ 一つのグローバル変数に全てのクラス ◦ Sprocketsで結合して1つのjs, cssファイルに
  16. 16 freee-js-framework • 所謂社内フレームワーク • 2つめのアプリ(人事労務freee)開発に伴い切り出されたBackbone.jsのラッパー部 分 + α ◦

    イベント管理、メモリリーク防止、非同期処理など
  17. 17 社内フレームワークあるある • メンテナの欠如 • いろいろやっていく意思だけがある 状態

  18. 18 2015〜フロントエンド委員会の発足 この辺りの取り組みに関する過去の資料はこちら https://www.slideshare.net/tkm64/webpack-62692382 https://speakerdeck.com/tohashi/hurontoendofalsemodanhua-tojavascriptmoziyurufalse-yi-cun-jie-jue • フロントエンドに関心のあるメンバーでフロントエンドの課題洗い出し ◦ ファイル間の依存解決、エントリーポイントの分割 ◦

    ES2015~ への移行 ◦ React の導入 ◦ フロントエンドのリソースは Webpack でビルドする • 段階的にこれらの移行を行っていった
  19. 19 現在 • 主要な機能はほぼ ES2015~ / React に移行 ◦ SPAではなく、機能ごとに独立したエントリーポイント

    ◦ 一部は CoffeeScript / Backbone のまま • ESLint, Prettierによる静的解析とフォーマット • flowtypeによる静的型付け • Storybookによるコンポーネントカタログ • テストはJest, Storyshots
  20. 20 Storybook, Storyshots • コンポーネントのカタログを作成 • @storybook/addon-storyshots を使うことでstoryごとのDOMのスナップショットを生 成し、差分があれば検知

  21. 21 レガシー環境から今っぽい環境へ • 段階的に移行してきた • 並行してコードベースも開発組織も急速に拡大 • その過程で数々の失敗も生まれ、一部は負債となってしまった ◦ ライブラリの選定ミス

    ◦ アンチパターンな設計 • フロントエンドが安定してきた今、負債の解消と治安の維持が品質や生産性向上の 為にも急務
  22. 実際の失敗例

  23. facebook/flux を使い続けた

  24. 24 facebook/flux を使い続けた • flux が指すものは2つ ◦ 1. facebook が提唱したデータフローのアーキテクチャ

    ◦ 2. 1のリファレンス実装ライブラリ • 会計freeeでは2を使用している • Dispatcher を提供する flux と Container, ReduceStore を提供するflux-utils からな る非常に薄いライブラリ ◦ Redux で言うと Container = Provider, ReduceStore = Reducer
  25. 25 facebook/flux 導入の背景 • 2015年 React 導入時に flux 用ライブラリを選定 •

    当時 flux 系ライブラリが乱立 ◦ 既に Redux が頭一つ抜けてはいたが、ミドルウェア等のエコシステムへのロック インを危惧、薄い方がいいという判断
  26. 26 facebook/flux で起きた諸問題 • 治安の乱れ • 薄いゆえに色々できてしまう ◦ Component から直接

    Store を参照、単方向データフローの崩壊 ◦ Component と ActionCreator の密結合、dispatchの起点が不明瞭に • 今やマイナーなので書き方のサンプルがインターネットに少ない
  27. 27 昨年Reduxへの移行を検討するも... • 時既に遅し ◦ facebook/flux を利用したコードは膨大な量に ▪ 移行作業はActionCreatorとdispatchの分解が特にネック ◦

    facebook/flux と Redux が共存する移行期が長期に渡りそう • 2015年時点で facebook/flux を選んだのは完全に間違いではなかった(と思う) ◦ ただもっと早く全体を見渡して移行の判断を下すべきだった
  28. 28 facebook/flux でなるべく治安を保っていく • リファレンス実装の明示 • Lintで防げる箇所は防ぐ ◦ Container component

    以外で Action の import 禁止など • 実質社内フレームワークのようになっていってしまうリスクはあるが・・・
  29. 29 FSA(Flux Standard Action)の採用 https://github.com/redux-utilities/flux-standard-action • Redux 等で推奨されている Action の規約

    • flowtype で dispatch の引数がこの規約に沿うよう チェック
  30. コンポーネントの状態を Storeで管理する

  31. 31 コンポーネントの状態をStoreで管理する • 例えば以下のような状態 ◦ モーダルの開閉 ◦ ローディング ◦ フォームの入力値

    • これらの状態を持つ UIStore を作って flux のデータフローに載せる ◦ ※ここで言う Store = Redux における Reducer です • コンポーネントの状態の変更も Action を通じて行う
  32. 32 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils

  33. 33 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils コンポーネント側のトリガー発火

  34. 34 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils APIリクエスト開始を示す Actionをdispatch

  35. 35 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils スピナーの状態をvisibleにして コンポーネントに通知

  36. 36 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils APIリクエスト送信

  37. 37 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils APIリクエスト終了を示す Actionをdispatch

    APIリクエスト終了
  38. 38 非同期処理時にスピナーを出す Component ActionCreator UIStore Store API Utils リクエストペイロードを処理 スピナーの状態をhiddenにして

    コンポーネントに通知
  39. 39 State をどこで管理すべきか、明確な答えはない https://redux.js.org/faq/organizing-state • RealWorld 等のサンプル見ても色々な流派が • Redux の公式ドキュメントFAQ:適宜判断しましょう(要約)

  40. 40 ローディングひとつで一本記事が書ける https://developers.freee.co.jp/entry/react-loading-pattern • 非同期処理に関しては将来的に React.Suspense という選択肢も

  41. 41 コンポーネントの状態をStoreで管理してきた所感 • 親から子のStateが見たいなら、それは親のStateであるべき • Reducer の肥大化がつらい ◦ 非同期ごとに2つのAction •

    コンポーネントで扱うか、Storeで扱うかの判断基準はどこに?
  42. 42 ライフサイクル内でdispatchの悲劇 • UIStoreの変化タイミングを取るために、componentDidUpdate 等のライフサイクル で prevProps と比較 ◦ ライフサイクルから

    Action が dispatch される事態が多発 • facebook/flux では dispatch からの同一イベントループ内で再度 dispath を呼び出 すと例外が投げられる • コンポーネントの動作自体に影響はないと思いきや・・・
  43. 43 React16アップデート時の思わぬ障壁に • React16からはコンポーネント内で Uncaught Error が発生すると root まで遡ってコ ンポーネント全体が

    unmount される ◦ 表示上の不整合を防ぐための仕様変更 • Error Boundary を設定するか例外処理をちゃんとする • この場合そもそもライフサイクルから dispatch しない
  44. 44 指針を統一 • コンポーネントの状態はコンポーネントが持つ • 親から参照したければそれは親のState • async/await が使えればよりシンプルに書ける

  45. Reactコンポーネントの継承

  46. 46 コンポーネントの継承 • かつて BaseModal という コンポーネントが発生 • モーダル関連の共通ロジックを 抽象化

    • 継承して各renderメソッドを override
  47. 47 実際の運用 • renderごとoverride • BaseModal 側の1メソッドを使いた かった模様 • 実質単なるヘルパのinclude

  48. 48 そもそもReactで継承はすべきではない • ReactのコンポーネントはViewControllerではない ◦ MVCからの移行だとオブジェクト指向的に捉えがち • 常に同じ props から同じ

    DOM を生成するアトミックな関数 ◦ render という明確な主体 • 抽象化は別のアプローチで行う ◦ Higher Order Component ◦ Render Props ◦ children
  49. 49 公式ドキュメントには https://reactjs.org/docs/composition-vs-inheritance.html

  50. 失敗を振り返って

  51. 51 失敗を振り返って • これらの設計やライブラリ全てが悪というわけではない ◦ 少なくとも大規模開発というコンテキストでは辛い面が多かった • 問題の根幹は初期の設計や規約がちゃんと整備できていなかったこと • 大規模ゆえに一度定着してしまうと軌道修正が難しい

    • 分業制ではない = 練度もバラバラ ◦ 設計の指針が示されていないと悪いコンポーネントが参考にされてしまう • サーバーサイド含めて見ている中で、警察業務は限界がある
  52. 52 分業でなくても秩序を保っていくために • 月並みだけどフロントエンドの設計に関してもコンセンサスを取っていくことが大事 ◦ コーディングガイドライン ◦ リファレンス実装の明示 ◦ コンポーネントカタログ

    • 自動化できるところ ◦ ESLint整備 ◦ Renovate による継続的なパッケージのアップデート
  53. 53 Renovate • package.json の更新PRを送ってきてくれるサービス ◦ CHANGELOG などもまとめてくれる

  54. 54 Renovate • 頻度や粒度、グルーピングなど細かく設定できる • パッケージ数多いと手動でやるのは意外と面倒 • 確認とマージ作業は色々な人に担当してもらい、フロントエンドのパッケージ周りに 触れる機会を増やしていく

  55. 55 組織全体への取り組み • プロダクト立ち上げ時のボイラープレートを提供 ◦ 推奨パッケージなど ◦ もちろん自分で選定したい人はそちらでも • フロントエンドについて少人数で話す会

    ◦ flux がなぜ必要か ◦ Babel, Webpack, npm の役割と関係
  56. 56 今後の展望 • 共通UIコンポーネントライブラリの開発 ◦ freeeのプロダクト間で統一的なスタイル・挙動の画面を誰でも 作れるように • フロントエンド委員会も継続的に活動中 ◦

    課題はまだまだ山盛り ◦ 全機能の React 化 ◦ React Hooks を行儀よくやっていくためには
  57. スモールビジネスを、 世界の主役に。