Share
Embed
コトダマンにおける技術的負債との戦い: サーバーアプリケーションのリアーキテクティング編 【MIXI TECH CONFERENCE 2023】
MIXI TECH CONFERENCE 2023
にてお話した田中の資料です。
動画:https://youtu.be/PH5rLh4v5VU
セッション詳細:https://techcon.mixi.co.jp/2023/d2-10
MIXI ENGINEERS PRO
March 02, 2023
Video
Transcript
43 slides
-
©MIXI 自己紹介 田中恭友 (Kyosuke Tanaka) デジタルエンターテインメント事業本部 コトダマン事業部 エンジニアグループ マネージャー 2018年4月 株式会社ミクシィ(現
MIXI)新卒入社 モンストドリームカンパニー サーバーサイドの新規開発・運用 2020年3月 コトダマン事業部に異動 サーバーエンジニアとしてスクラム Tで開発・運用 2022年9月 エンジニアマネージャー就任 2 -
©MIXI 3 目次 1. 共闘ことばRPG コトダマンの紹介 2. コトダマン運用における課題 3. サーバーアプリケーションの技術負債
4. リアーキテクティングの狙い・戦略 5. 結果のふりかえり 6. まとめ -
©MIXI 成長の裏で頻発する障害 タイトル移管直後は運用体制も大きく変動したため障害が頻発していました • マスタデータの入稿ミス • マスタデータの巻き戻り • 新規機能実装時のエンバグ •
負荷対策の不足 • etc… 9 データ更新の度に問題が発生するような状態 更新日はドキドキ・事後対応にドタバタ -
©MIXI 解決への取り組み 11 • マスタデータ運用フローの改善 • マスタデータバリデーションツールの導入 • サーバーアプリケーションのリアーキテクティング •
アセット運用フローの改善 • プロジェクト全体での定期的なポストモーテム実施 • などなど... -
©MIXI 解決への取り組み 今日話すのはこれ! 12 • マスタデータ運用フローの改善 • マスタデータバリデーションツールの導入 • サーバーアプリケーションのリアーキテクティング
• アセット運用フローの改善 • プロジェクト全体での定期的なポストモーテム実施 • などなど... -
©MIXI コトダマンのアーキテクチャ概要 一般的なソーシャルゲームによくあるアーキテクチャ 14 APIサーバー Java 8(Amazon Corretto) tomcat struts2
DB (Amazon Aurora) チャットサーバー Java 8(Amazon Corretto) tomcat struts2 その他 運用用途サーバー ゲームクライアント (Unity製) マルチプレイサーバー monobit engine APIサーバー : ゲーム上の様々な処理を実現するための APIを提供(クエスト開始・ガチャ・ etc…) -
©MIXI コトダマンのアーキテクチャ概要 一般的なソーシャルゲームによくあるアーキテクチャ 15 APIサーバー Java 8(Amazon Corretto) tomcat struts2
DB (Amazon Aurora) チャットサーバー Java 8(Amazon Corretto) tomcat struts2 その他 運用用途サーバー ゲームクライアント (Unity製) マルチプレイサーバー monobit engine APIサーバー : ゲーム上の様々な処理を実現するための APIを提供(クエスト開始・ガチャ・ etc…) 今回のリアーキテクティング対象 -
©MIXI 16 APIサーバーの課題 • APIサーバーの実装は複雑化し、保守が困難に ◦ 既存機能の改修時にエンバグが多発 ◦ 開発速度の低下(既存実装の調査にコストがかかる) ◦
中には、エンバグを恐れて改修できない機能も... -
©MIXI APIサーバーのアーキテクチャ概要 17 Action Tool GachaTool QuestTool DAO Entity Action
所謂 Controllerのレイヤ ゲームクライアントとのやりとりを担う リクエスト・レスポンスは独自 DSLで定義 Tool ビジネスロジックを受け持つレイヤ コンテキスト毎に分割されている 例: GachaTool / QuestTool … etc. DAO ORマッパー(Hibernate)が DB上のレコードと1:1対応するクラスを生成 MasterData マスタデータは起動時に全てメモリ上にキャッシュさ れ、singletonクラスが管理 Response Request MasterData ExchangeTool -
©MIXI 問題点① : ドメインモデルの不在 18 CardTool DAO UserCardData オブジェクトのfieldの可視性が制御されず、 どのモジュールからも
setterを利用できる Tool 上の様々なメソッドの中で自由に変更され、 ドメイン上でどのような制約があるのかが不明 DBレコードのあるカラムがどのように変更されるのか が参照先を全て追いかけないとわからない public static void getCard() UserCardData.setUserId(xxx) UserCardData.setLevel(xxx) UserCardData.setAtk(xxx) public static void levelUp() UserCardData.setLevel(xxx) UserCardData.setAtk(xxx) ORマッパーによってDB上のレコードから変換されたオブジェクト -
©MIXI 問題点② : 密結合したビジネスロジック 19 Tool public static 巨大メソッドA public
static 巨大メソッドB Toolは巨大なstatic methodの集合体 手続き的に書かれており、 それぞれの処理に暗黙的な依存関係がある (呼び出し元・先の処理内容が隠蔽されていない) これら巨大メソッドに自動テストはなく、 変更の影響範囲が読めない状態 public static 巨大メソッドC 呼び出し 呼び出し primitiveな値を大量に引数で要求するstatic methodの連鎖 -
©MIXI 巨大基底クラス 問題点③ : 複雑な依存関係 20 巨大UtilityTool A MasterData Cache
Class 依存の方向性が制御されておらず、 モジュール間で複雑に依存しあっている 巨大ビジネスロジック Tool C 巨大UtilityTool B 巨大ビジネスロジック Tool D -
©MIXI 問題点③ : 複雑な依存関係 21 public static GachaExecuteResponse Gacha実行処理( GachaExecuteRequest
request ) { … do_gacha_core_logic(request.gacha_id, request.some_params) … GachaExecuteResult response = new GachaExecuteResult(); response.setGachaResult(...); if (response.getGachaResult == xxx) { … } … return response; } GachaExecuteResponse 特に、最も変更されやすいクライアントに返すレスポンス(≒ View)に依存した処理が問題 ResponseはAPI毎に定義された型であるため、ビジネスロジックの再利用性が全くなくなってしまう クライアント(View)の都合で変更することが困難になり、歪な実装を産んだり、Viewの変更を諦めることになる ガチャのコアロジック GachaExecuteRequest 依存の方向 -
©MIXI リアーキテクティングの狙い • ルールに従って実装しているうちに、 オブジェクト指向らしい設計ができるようになること ◦ ドメインモデルを作成・ビジネスロジックをモデルに集約することを強制し ルールに従った実装をしているうちに学習が進むような設計であること • ルールを元にレビューが行えること
◦ 設計の良し悪しを議論するための土台にできること ◦ レビューコストの低下 • 自動テストを書く文化を成熟させること ◦ ルールに従って実装すればテスタブルな設計になること ◦ 自動テストを必須とする部分・そうでない部分を分けて 少なくともコアなロジックに自動テストを書くことを当たり前にする ◦ TDDが自然と行われる下地を作る 23 -
©MIXI 新アーキテクチャ 24 Presentation UseCase DAO Entity Response Request MasterData
Model Service IRepository Repository Action • オニオンアーキテクチャを参考にシンプルなアーキテクチャに設定 • ビジネスロジックをModel層に集約することを狙う • 依存の方向性を制御し、変動しやすいレイヤにコアロジックが依存しないようにする 依存の方向性 -
©MIXI Modelレイヤ 25 Presentation UseCase DAO Entity Response Request MasterData
Model Service IRepository Repository Action • ビジネスロジックを受け持つ層 ◦ DDDでいう値オブジェクトやエンティティをこのレイヤに置く ◦ 例: User, Charactor, Gacha, Quest … etc • Modelレイヤのクラス中の公開メソッドには原則単体テストを必須とする ◦ 自動生成された getter は対象外 -
©MIXI Serviceレイヤ 26 Presentation UseCase DAO Entity Response Request MasterData
Service IRepository Repository Action • 複数のModelを利用してアプリケーションに必要な機能を実装するレイヤ ◦ 複数のコンテキストで利用される、汎用的かつ複数の Modelが関連する処理を置く ◦ 例: ItemService(アイテムの付与) • できる限りModelのみで実装を行うが、綺麗にまとまらない場合には Serviceを使う • 旧実装(Tool)との繋ぎ込みを担う、腐敗防止層として利用されることもある Model -
©MIXI UseCaseレイヤ 27 Presentation UseCase DAO Entity Response Request MasterData
Service IRepository Repository Action • アプリケーションの機能を実現するために、処理順序を規定するためのレイヤ ◦ 例: ExchangeItemUseCase(アイテムを消費し、別のアイテムを獲得する) • DIされたRepositoryを利用してModel / Serviceを取り出し、ビジネスロジックを順に実行 Model -
©MIXI Presentationレイヤ 28 Presentation UseCase DAO Entity Response Request MasterData
Service IRepository Repository Action • ゲームクライアントとのやりとりを担うレイヤ • クライアントからのリクエストに応じて必要な UseCaseを実行し、 実行結果をクライアントの求める形式のレスポンスに変換し、返却する Model -
©MIXI Presentation Repository 29 UseCase DAO Entity Response Request MasterData
Service IRepository Repository Action • DB上に永続化されたユーザーデータや、メモリ上にキャッシュされたマスタデータから Modelを再構築する • ビジネスロジックは持たず、 Modelの永続化に集中する • UseCaseから利用する場合にはインタフェースを介して依存性を逆転する Model -
©MIXI アーキテクチャの浸透度合い • 新機能はアーキテクチャに則って実装されるようになった • 過去実装については移行を必須とせず、緩やかに改善中 ◦ 機能実装のついでに過去実装の一部を Serviceに切り出す ◦
過去実装から新UseCaseを呼び出す形で共存する ◦ … などなど 32 -
©MIXI Keep : 依存の方向性の制御 • 依存関係が制御されるようになった ◦ クライアント都合でレスポンスが変化しても、ビジネスロジックは影響を受けづらい形に • 副次的な効果
◦ レスポンスが決まらないうちにコアロジックから実装を進める ◦ マスタデータの調整中に DummyのRespositoryを作成してコアロジックから実装を進める などの柔軟な実装順の制御が行いやすくなった ◦ 作業分担も行いやすくなった 33 -
©MIXI Keep : チーム内の設計に関する知見 • チーム内で設計に関する議論が活発に行われるようになった ◦ GitHub Discussionsに議論の場を用意 ◦
普段の開発中に気になったことや提案を記録し、 週次定例で繰り返し議論の場を設けるようになった ◦ 議論の例 ▪ CQRSを導入してみたい! ▪ 細かい命名規則についての議論 • レビューがアーキテクチャを中心に行われるようになった ◦ 共通認識を持てているので議論が捗る 34 GitHub Discussionsを利用した議論 -
©MIXI Keep : テストを書く文化の醸成 • 新アーキテクチャ導入前のテストカバレッジ( Line) : 0.6% •
新アーキテクチャ導入後のテストカバレッジ( Line) ◦ Model : 73% (※ 自動生成された getter はテスト不要としている ) ◦ Service : 65% ◦ UseCase : 47% ◦ Repository : 54% ◦ 全体 : 12% • Model以外のレイヤにもテストが書かれるようになった • PR上で「ここテスト欲しい!」という指摘が行われるように • 既存の巨大モジュールに関しては未だテストが書けていない状態 ◦ テスタブルでない設計のため機能実装の片手間では解決できない 35 -
©MIXI Problem : 実装コストの増加 • 実装スピードがやや低下 ◦ 特にRepositoryの実装においてボイラーテンプレートコードが必要になった ◦ DB上のレコードから大量のカラムを
Modelのコンストラクタに引き渡す処理 36 DAO UserData MasterData Model IRepository Repository.getModel() -
©MIXI Problem : 曖昧な実装ルールによる混乱 • 曖昧なルールが混乱を呼ぶことも ◦ 特にServiceの用途が理解されづらく、 ビジネスロジックをとりあえず置く場になってしまいがちになった ▪
Modelの実装が少なくなり、ドメインモデル貧血症に ◦ 検証期間は細部が曖昧なままの運用だったため、 現状のルールと異なる部分がちらほら • 新規メンバーのイニシャルコスト増加 ◦ 過去実装と新規実装でスタイルが異なり混乱してしまう 37 -
©MIXI Problem : Repository • Repositoryにビジネスロジックが実装されてしまいがちに ◦ クエリ効率を考慮するとやむを得ないことも多い • 暗黙的なRepository同士の依存関係
◦ ModelとDBレコードを切り離したことで単一責任でないテーブルを扱いやすくなった ◦ 一方で、同じテーブルを参照する Repository間で暗黙的な依存が生まれてしまった ▪ 一見関連のない別 Model・Repositoryで同じカラムを参照・変更しているなど • mockが多用されるテスト ◦ UseCaseのテストで都度Repositoryをmockしている ◦ 複数のテスト間で共通のRepositoryが利用できておらず、テスト実装効率が低下 38 -
©MIXI これからの取り組み • アーキテクチャの簡略化 ◦ 変更容易性を残したまま、より実装が高速になるための進化 ◦ ボイラーテンプレートコードを書かなくて済むような工夫を検討中 ▪ Repositoryを廃してO/RマッピングされたオブジェクトをModelとして扱う
▪ テンプレート機能の利用 40 引き続きチーム内で議論を重ねています -
©MIXI これからの取り組み : 過去実装のリライト • スクラム開発T内ではレガシーな過去実装の書き換えは進まない ◦ 片手間では改善できない巨大モジュールをコストをかけて改善する必要 ◦ エンバグを恐れて変更できない状態のモジュールがあることが
選択肢を減らし、プロジェクトの成長可能性を狭めてしまう • チーム体制も含めた改善 ◦ 改善を推し進めるチームとしてSREチームを組成 ◦ ビジネスサイドと議論を重ね、どの時期にどのモジュールを改善するか 優先順位をつけて改善していく 41 -
©MIXI まとめ • サーバーアプリケーションの保守性向上のため、 新アーキテクチャの導入を進めました • 新規機能を新アーキテクチャで実装しました ◦ 実装スピードはやや低下したものの、テスタブルな設計になりました •
チームもアプリケーションと共に成長しました • 既存機能の置き換えはまだまだこれから • より多くの価値をユーザーに届けるために これからも技術負債の解決を進めていきます! 42