Slide 1

Slide 1 text

       © Chatwork 巨大なSPAの技術的負債と 向き合い続けるテクニック(2023年秋) 2023年11月21日 Chatwork株式会社 澁谷 哲也 Chatwork株式会社

Slide 2

Slide 2 text

自己紹介 2 Chatwork株式会社 澁谷 哲也 Tetsuya Shibutani プロダクト本部 エンジニアリングマネージャー ビジネスチャットツールを作っています @shibe_23

Slide 3

Slide 3 text

「Chatwork」の WebFrontendについて

Slide 4

Slide 4 text

「Chatwork」のWebFrontendの特徴 ● 巨大なSPA、かつ利用中ほとんどリロードされずに使われるアプリケーション ● 双方向性があり、ユーザーからのアクション以外で状態が変わる ● JavaScriptだけで18万行 ● CSSも1万7千行 ○ Styled-Componentを利 用しているため、実際に はもっと多い 巨大なコードベース ● ブラウザベースのアプリケー ションとしては珍しく、URL単 位でアプリケーションとして分 割されていない ● 再読み込みのない一枚のページ で全UIが動作している 純粋なSinglePageApplication ● Webサイトベースの「入力 -> 確認 -> 完了」と情報が一方向な Webアプリではない ● Google SpreadSheetのような GUIアプリとしての複雑さを持 つ GUIアプリとしての複雑性 4

Slide 5

Slide 5 text

技術的負債の状況 ● jQuery -> React + DDD -> React + Reduxの3世代に渡るアーキテクチャ ● jQuery製のUIとReact製のUIが混在している ○ 全体のうち約2万3000行がjQuery製 ● 認知的複雑度(Cognitive Complexity)の合計は、およそ9,000 ○ jQuery世代はおよそ5,000 ● 各世代ごとの詳細は下記を参照ください 組織フェーズを見据えたWEBフロントエンドのアーキテクチャと変遷(JS Conf 2021)

Slide 6

Slide 6 text

技術的負債は何によってもたらされるか ● ソフトウェアは常にアップデートされ続ける ● 周辺の環境の変化に追従する必要があるため「何もしない」という選択肢はない ○ (例: セキュリティアップデートやインフラのサポート終了) ● 「あるべき姿」自体が変化したり、そこに至る道のりが険しくなることで、理想に到達するまでの時 間が延びる 原因 例 時間経過 ● 技術的なパラダイムシフトが起きた ● ライブラリのアップデートに追従する必要がある 目的不確実性 ● 事業フェーズやユーザー数の増減に伴う要求の変化 ● 市場への解像度が高まったために、コードに表現するべきものが変化した ● 仕様が本質的に複雑、または整理されていない 方法不確実性 ● その時点ではベストなコードだったが、後に「もっとこうすればよかった」が判明した ● スキル不足や時間へのプレッシャーなどの理由からコードの内部品質を確保できなかった

Slide 7

Slide 7 text

技術的負債についての考え方 ● 技術的負債は「アプリケーションのあるべき姿と現状との差分」 ○ 現状からあるべき姿に到達するまでに掛かる時間(= リソース)と向き合っている ● 日々変化する状況が技術的負債を産み続ける ○ jQuery全盛期にReactの登場は予測できなかった ○ 現在のベストプラクティスも、新しい技術の登場によってあるべき姿から遠ざかる ○ 要件の変化やエンジニアの経験によってもあるべき姿が変化していく ● 負債は無くすものではなく、様々な要因を加味してコントロールするもの ○ 技術的負債を抱えたコードでも、仕様変更によって丸ごと不要になるケースもある ■ 必ずしも負債がゼロであることが適切とは限らない 技術的負債と継続的に向き合う仕組みづくりが重要

Slide 8

Slide 8 text

技術的負債と継続的に向き合う方法 8 1. 段階的に返却するためのテクニック 2. 安全にリリースするための仕組み 今回は具体例をいくつかご紹介します

Slide 9

Slide 9 text

段階的に返済するテクニック

Slide 10

Slide 10 text

jQueryの世界とReactの世界を分離する ● jQuery製のコードとReact製のコードの間に腐敗防止層を設ける ● interfaceをReact側から提供することで依存関係を制御する ● Reactの世界にjQueryが侵入することを防ぎ、古いコードを捨てやすくしている jQuery時代のアーキテクチャをReact化するために大切なACL層のお話

Slide 11

Slide 11 text

jQuery製のUIとReact製のUIを混在させる ● 段階的にリファクタリングするためにUIごとにReact化したい ● ReactDOM.renderを使った時の課題 ○ Context APIを全てのレンダリングツリーに流し込む必要性 ○ デバッグ時にレンダリングツリーごとにしかプロファイルが取れない ● ReactDOM.createPortal で同一の親コンポーネントを指定してマウントするようにした ● 部分的な置き換えにおける注意点 ○ イベントハンドラの多重登録 ○ 再描画の度にjQueryで作成したDOMが残り続けることによるメモリリーク … React … jQuery

Slide 12

Slide 12 text

TypeScriptのStrict modeを有効にする ● jQuery時代のコードはany型を一部許容している ○ 一方で、普段のコードを書く時に暗黙的なany型の混入を防ぎたい ● strictを有効にするために、TypeScript Compiler APIを使って型エラーが起きている箇所に @ts-expect-error を自動で追加した ○ 新規で@ts-expect-errorを書かないようにpre commit時にチェックしている ● 型エラーが隠蔽されるケースがあるので、トレードオフには注意

Slide 13

Slide 13 text

安全にリリースするための仕組み

Slide 14

Slide 14 text

技術的負債と継続的に向き合う = 小さく試して改善し続ける ● コードを適切に処理できれば確実に不具合を回避することはできるか? ○ できない ○ 負債の影響は積み重なって予期しない不具合として現れる ● リリースしなければ本当に問題がないかは確認できない ○ 失敗の影響を最小限に抑えなければ、リファクタリングは重たいプロセスになってしまう ● “どうコードをきれいにするか”も大事だが、 “小さく失敗できる体制をつくる“ことが最も重要

Slide 15

Slide 15 text

リリースフローの全体像 ● GitHub Flowを採用 ● mainブランチにマージされるとすぐに社内検証ユーザーにデプロイされる ● 本番リリースされるのは、その時点のmainブランチの最新コミット ● 先行して自分たちが触ることで、ユーザーにとって致命的な不具合がリリースされることを回避 社内検証ユーザーにリリー ス main feature-x 社内検証されたものが 本番リリース

Slide 16

Slide 16 text

コミットハッシュごとのスナップショットを生成する ● PR作成時 / 更新時 / mainブランチへのマージ時にビルドしたjsファイルをS3にアップロードする ● コミットハッシュをURLにパラメータとして付与することで、指定したバージョンでアプリを開いて 検証できる仕組みがある ○ バージョンごとの現象確認が容易 ● リリースブランチへマージされた履歴がjsファイルとして既に生成されている、というのがポイント commit hash: c63df1b164b73d71… main commit hash: f632892477af9be29… commit hash: ebbd2436080a165c… commit hash: 8238f9b315116c5c0…

Slide 17

Slide 17 text

自前のリリースシステムで高速なリリース / 切り戻しを実現する ● リリースは生成済のjsファイルを本番環境にコピーするだけ ● mainブランチの過去の状態も保存されているため、切り戻しも容易 ● 緊急時の切り戻しは実行 -> リリース完了で約3秒 ※ 切り戻しの判断も必要なので、障害対応自体はもう少しかかります

Slide 18

Slide 18 text

Feature Toggleで安全にリリース内容を制御する ● Feature Toggleで社内検証ユーザーにのみリリースすることができる ● 大きめのリファクタリングなど、本番環境へのリリースを阻害することなく検証期間を設けることが できる ● 非公開の制御もFeature Toggleをオフにするだけ ● Feature Toggleによって分岐が増えて複雑化する場合もあるため要注意

Slide 19

Slide 19 text

実際どうなのか

Slide 20

Slide 20 text

技術的負債の地層化 ● 設計としての技術的負債はjQuery -> React + DDD -> React + Redux と積み重なっている ● 各世代を返済しきらずに新たな設計思想を追加すると負債が「地層化」する ○ 新たなライブラリを導入したい、状態管理などの設計方針を改善したいなど理由はさまざま ● あるべき姿に向かうコストを払いきれなくなった時点で負債の返却が止まる ○ 負債返却のためのプロジェクトが長期化してコストが膨らんでしまう ○ ボトムアップの改善が様々な要因で途絶えてしまう ● 「Chatwork」のWebFrontendでは一定の地層化を許容しているとも言える ○ 必要な分だけ段階的に返済できる ■ リプレースせずに中身を入れ替え続けている ○ 地層を重ねる弊害が大きくなるのであれば、集中したリファクタリングも必要になってくる ● 規模の大きさに苦慮している一方、一定の認知負荷を許容しつつ事業的な要求に応えている状態

Slide 21

Slide 21 text

負債の返却は継続中、ただし新しいトレードオフも選択し続けている ● バックエンドの部分的なリプレースが数年単位で継続している ○ その一環として、クライアント側との接続にGraphQLを利用する予定 ○ バックエンド側の負債返却としてメリットがある ● GraphQLという技術スタックが追加される反面、API周辺の負債を段階的に取り除くことができる ○ スキーマ定義がない旧APIからの脱却 ○ 特定条件でのkeyの欠損を検知できないなど、API側の負債も解消 ○ UX観点でもパフォーマンス向上が見込める

Slide 22

Slide 22 text

負債の返却は継続中、ただし新しいトレードオフも選択し続けている ● React18へのアップデートも対応中 ○ タイムラインのレイアウト制御など、レンダリングがブロックされる前提で書かれていたコード がある ○ 細かなUIも含めると組み合わせが非常に多く、影響範囲が未知数 ○ 社内に限定配布し、自分たちで使う仕組みがあるからこそ大胆にアップデート -> 検証の流れが 作れている

Slide 23

Slide 23 text

ACL層のメリット / デメリット ● ACL層によって旧コードとの依存関係は整理しやすくなった ● 一方でACL層は「一時しのぎ」とも言えるため、そのままだと肥え続ける ○ ACL層を定期的に整理するなど、地道な改善は必要 ○ 旧コードへの依存状況が可視化されるため、負債状況の目安にもなる 変更回数が多いファイルTOP10 (参考元) git でファイルごとのコミット数を取ってきて,プロジェクト中のホットなファイルを割り出すという試み

Slide 24

Slide 24 text

技術的負債を解消する文化 ● 原則としてエンジニア主導でどのように技術的負債を解消していくかは考えられている ● 機能開発のタイミングで、関連する箇所のリファクタリングもスケジュールに組み込んでいる ● 各職能別のチームでは、自律的にリファクタリングを計画している ● 小さく解消し続けることで、負債の蓄積 < 負債の解消 という構図を持ち続けたい

Slide 25

Slide 25 text

まとめ

Slide 26

Slide 26 text

技術的負債と現実的に向き合うために ● 技術的負債を解消するより、どのように距離を保つか ○ 段階的に負債を返却するためのテクニック ○ 安全にリリースするための仕組み ● 未来は誰にも予測できない。だからこそ小さく試して改善し続ける ○ “小さく失敗できる”体制が重要

Slide 27

Slide 27 text

今回の話に興味を持っていただいた方へ ● エンジニアHub様掲載の記事もご覧ください React+Reduxによる状態管理とフロントエンドの技術的負債 ─ 長く継続するサービスのアプリケーション設計

Slide 28

Slide 28 text

最後に ● Chatworkではフロントエンドエンジニアを募集しています ● 巨大なSPAと一緒に向きあい改善し続けてくださる方、ぜひご応募ください! 採用ページはこちら

Slide 29

Slide 29 text

働くをもっと楽しく、創造的に