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

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

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

Takumi Ohashi

March 26, 2019
Tweet

More Decks by Takumi Ohashi

Other Decks in Technology

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  4. ● ソシャゲ界出身
    ● 2014年12月入社
    ● フロントエンド寄りエンジニア
    ● 社内でやってること
    ○ 会計freeeの開発
    ○ フロントエンド啓蒙活動
    ○ ボルダリング部部長
    Takumi Ohashi / @_tohashi
    Webアプリケーションエンジニア
    4

    View full-size slide

  5. 会計freeeについて

    View full-size slide

  6. 6
    会計freee
    ● 経理業務を始めとして請求書、ワークフロー管理など
    ● 2013年ローンチ
    ● first commit は2012年
    ● モノリシックな Rails アプリケーション
    ○ 一部機能はマイクロサービス化
    ● フロントエンドも同じリポジトリに

    View full-size slide

  7. 7
    会計freee

    View full-size slide

  8. 8
    会計freee

    View full-size slide

  9. 9
    会計freee

    View full-size slide

  10. 10
    規模 拡張子と行数
    ※1 CoffeeScript向けのテンプレートエンジン
    .js 352,219
    .coffee 49,015
    .scss 99,084
    .eco※1
    16,434
    .html.erb 48,116

    View full-size slide

  11. 11
    その他規模など
    DBテーブル数 約650
    PR番号 45,000超
    デプロイ 平均 1/day
    2019年の
    Contributor数
    50超
    エンドポイント数 3000弱

    View full-size slide

  12. 12
    freeeのWebアプリケーションエンジニア
    ● フロントエンドエンジニアとサーバーサイドエンジニアの区分はない
    ● Rails と React が必修スキルとなる
    ● もちろん個々の得意領域はある
    ○ フロントエンド寄りだったり

    View full-size slide

  13. 会計freeeの
    フロントエンド

    View full-size slide

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

    View full-size slide

  15. 15
    ローンチ〜2014ごろ
    ● 秩序なき世界
    ○ 所謂片手間JS
    ○ フロントエンドは今ほど複雑な要件ではなかった
    ● ファイル間の依存解決なし
    ○ 一つのグローバル変数に全てのクラス
    ○ Sprocketsで結合して1つのjs, cssファイルに

    View full-size slide

  16. 16
    freee-js-framework
    ● 所謂社内フレームワーク
    ● 2つめのアプリ(人事労務freee)開発に伴い切り出されたBackbone.jsのラッパー部
    分 + α
    ○ イベント管理、メモリリーク防止、非同期処理など

    View full-size slide

  17. 17
    社内フレームワークあるある
    ● メンテナの欠如
    ● いろいろやっていく意思だけがある
    状態

    View full-size slide

  18. 18
    2015〜フロントエンド委員会の発足
    この辺りの取り組みに関する過去の資料はこちら
    https://www.slideshare.net/tkm64/webpack-62692382
    https://speakerdeck.com/tohashi/hurontoendofalsemodanhua-tojavascriptmoziyurufalse-yi-cun-jie-jue
    ● フロントエンドに関心のあるメンバーでフロントエンドの課題洗い出し
    ○ ファイル間の依存解決、エントリーポイントの分割
    ○ ES2015~ への移行
    ○ React の導入
    ○ フロントエンドのリソースは Webpack でビルドする
    ● 段階的にこれらの移行を行っていった

    View full-size slide

  19. 19
    現在
    ● 主要な機能はほぼ ES2015~ / React に移行
    ○ SPAではなく、機能ごとに独立したエントリーポイント
    ○ 一部は CoffeeScript / Backbone のまま
    ● ESLint, Prettierによる静的解析とフォーマット
    ● flowtypeによる静的型付け
    ● Storybookによるコンポーネントカタログ
    ● テストはJest, Storyshots

    View full-size slide

  20. 20
    Storybook, Storyshots
    ● コンポーネントのカタログを作成
    ● @storybook/addon-storyshots を使うことでstoryごとのDOMのスナップショットを生
    成し、差分があれば検知

    View full-size slide

  21. 21
    レガシー環境から今っぽい環境へ
    ● 段階的に移行してきた
    ● 並行してコードベースも開発組織も急速に拡大
    ● その過程で数々の失敗も生まれ、一部は負債となってしまった
    ○ ライブラリの選定ミス
    ○ アンチパターンな設計
    ● フロントエンドが安定してきた今、負債の解消と治安の維持が品質や生産性向上の
    為にも急務

    View full-size slide

  22. 実際の失敗例

    View full-size slide

  23. facebook/flux を使い続けた

    View full-size slide

  24. 24
    facebook/flux を使い続けた
    ● flux が指すものは2つ
    ○ 1. facebook が提唱したデータフローのアーキテクチャ
    ○ 2. 1のリファレンス実装ライブラリ
    ● 会計freeeでは2を使用している
    ● Dispatcher を提供する flux と Container, ReduceStore を提供するflux-utils からな
    る非常に薄いライブラリ
    ○ Redux で言うと Container = Provider, ReduceStore = Reducer

    View full-size slide

  25. 25
    facebook/flux 導入の背景
    ● 2015年 React 導入時に flux 用ライブラリを選定
    ● 当時 flux 系ライブラリが乱立
    ○ 既に Redux が頭一つ抜けてはいたが、ミドルウェア等のエコシステムへのロック
    インを危惧、薄い方がいいという判断

    View full-size slide

  26. 26
    facebook/flux で起きた諸問題
    ● 治安の乱れ
    ● 薄いゆえに色々できてしまう
    ○ Component から直接 Store を参照、単方向データフローの崩壊
    ○ Component と ActionCreator の密結合、dispatchの起点が不明瞭に
    ● 今やマイナーなので書き方のサンプルがインターネットに少ない

    View full-size slide

  27. 27
    昨年Reduxへの移行を検討するも...
    ● 時既に遅し
    ○ facebook/flux を利用したコードは膨大な量に
    ■ 移行作業はActionCreatorとdispatchの分解が特にネック
    ○ facebook/flux と Redux が共存する移行期が長期に渡りそう
    ● 2015年時点で facebook/flux を選んだのは完全に間違いではなかった(と思う)
    ○ ただもっと早く全体を見渡して移行の判断を下すべきだった

    View full-size slide

  28. 28
    facebook/flux でなるべく治安を保っていく
    ● リファレンス実装の明示
    ● Lintで防げる箇所は防ぐ
    ○ Container component 以外で Action の import 禁止など
    ● 実質社内フレームワークのようになっていってしまうリスクはあるが・・・

    View full-size slide

  29. 29
    FSA(Flux Standard Action)の採用
    https://github.com/redux-utilities/flux-standard-action
    ● Redux 等で推奨されている Action の規約
    ● flowtype で dispatch の引数がこの規約に沿うよう
    チェック

    View full-size slide

  30. コンポーネントの状態を
    Storeで管理する

    View full-size slide

  31. 31
    コンポーネントの状態をStoreで管理する
    ● 例えば以下のような状態
    ○ モーダルの開閉
    ○ ローディング
    ○ フォームの入力値
    ● これらの状態を持つ UIStore を作って flux のデータフローに載せる
    ○ ※ここで言う Store = Redux における Reducer です
    ● コンポーネントの状態の変更も Action を通じて行う

    View full-size slide

  32. 32
    非同期処理時にスピナーを出す
    Component ActionCreator
    UIStore
    Store
    API Utils

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. 41
    コンポーネントの状態をStoreで管理してきた所感
    ● 親から子のStateが見たいなら、それは親のStateであるべき
    ● Reducer の肥大化がつらい
    ○ 非同期ごとに2つのAction
    ● コンポーネントで扱うか、Storeで扱うかの判断基準はどこに?

    View full-size slide

  42. 42
    ライフサイクル内でdispatchの悲劇
    ● UIStoreの変化タイミングを取るために、componentDidUpdate 等のライフサイクル
    で prevProps と比較
    ○ ライフサイクルから Action が dispatch される事態が多発
    ● facebook/flux では dispatch からの同一イベントループ内で再度 dispath を呼び出
    すと例外が投げられる
    ● コンポーネントの動作自体に影響はないと思いきや・・・

    View full-size slide

  43. 43
    React16アップデート時の思わぬ障壁に
    ● React16からはコンポーネント内で Uncaught Error が発生すると root まで遡ってコ
    ンポーネント全体が unmount される
    ○ 表示上の不整合を防ぐための仕様変更
    ● Error Boundary を設定するか例外処理をちゃんとする
    ● この場合そもそもライフサイクルから dispatch しない

    View full-size slide

  44. 44
    指針を統一
    ● コンポーネントの状態はコンポーネントが持つ
    ● 親から参照したければそれは親のState
    ● async/await が使えればよりシンプルに書ける

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  48. 48
    そもそもReactで継承はすべきではない
    ● ReactのコンポーネントはViewControllerではない
    ○ MVCからの移行だとオブジェクト指向的に捉えがち
    ● 常に同じ props から同じ DOM を生成するアトミックな関数
    ○ render という明確な主体
    ● 抽象化は別のアプローチで行う
    ○ Higher Order Component
    ○ Render Props
    ○ children

    View full-size slide

  49. 49
    公式ドキュメントには
    https://reactjs.org/docs/composition-vs-inheritance.html

    View full-size slide

  50. 失敗を振り返って

    View full-size slide

  51. 51
    失敗を振り返って
    ● これらの設計やライブラリ全てが悪というわけではない
    ○ 少なくとも大規模開発というコンテキストでは辛い面が多かった
    ● 問題の根幹は初期の設計や規約がちゃんと整備できていなかったこと
    ● 大規模ゆえに一度定着してしまうと軌道修正が難しい
    ● 分業制ではない = 練度もバラバラ
    ○ 設計の指針が示されていないと悪いコンポーネントが参考にされてしまう
    ● サーバーサイド含めて見ている中で、警察業務は限界がある

    View full-size slide

  52. 52
    分業でなくても秩序を保っていくために
    ● 月並みだけどフロントエンドの設計に関してもコンセンサスを取っていくことが大事
    ○ コーディングガイドライン
    ○ リファレンス実装の明示
    ○ コンポーネントカタログ
    ● 自動化できるところ
    ○ ESLint整備
    ○ Renovate による継続的なパッケージのアップデート

    View full-size slide

  53. 53
    Renovate
    ● package.json の更新PRを送ってきてくれるサービス
    ○ CHANGELOG などもまとめてくれる

    View full-size slide

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

    View full-size slide

  55. 55
    組織全体への取り組み
    ● プロダクト立ち上げ時のボイラープレートを提供
    ○ 推奨パッケージなど
    ○ もちろん自分で選定したい人はそちらでも
    ● フロントエンドについて少人数で話す会
    ○ flux がなぜ必要か
    ○ Babel, Webpack, npm の役割と関係

    View full-size slide

  56. 56
    今後の展望
    ● 共通UIコンポーネントライブラリの開発
    ○ freeeのプロダクト間で統一的なスタイル・挙動の画面を誰でも
    作れるように
    ● フロントエンド委員会も継続的に活動中
    ○ 課題はまだまだ山盛り
    ○ 全機能の React 化
    ○ React Hooks を行儀よくやっていくためには

    View full-size slide

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

    View full-size slide