Slide 1

Slide 1 text

©MIXI コトダマンにおける技術的負債との戦い: サーバーアプリケーションのリアーキテクティング編 デジタルエンタテインメント事業本部 コトダマン事業部 エンジニアグループ 田中 恭友

Slide 2

Slide 2 text

©MIXI 自己紹介 田中恭友 (Kyosuke Tanaka) デジタルエンターテインメント事業本部 コトダマン事業部 エンジニアグループ マネージャー 2018年4月 株式会社ミクシィ(現 MIXI)新卒入社       モンストドリームカンパニー       サーバーサイドの新規開発・運用 2020年3月 コトダマン事業部に異動       サーバーエンジニアとしてスクラム Tで開発・運用 2022年9月 エンジニアマネージャー就任 2

Slide 3

Slide 3 text

©MIXI 3 目次 1. 共闘ことばRPG コトダマンの紹介 2. コトダマン運用における課題 3. サーバーアプリケーションの技術負債 4. リアーキテクティングの狙い・戦略 5. 結果のふりかえり 6. まとめ

Slide 4

Slide 4 text

©MIXI 共闘ことばRPG コトダマン 4

Slide 5

Slide 5 text

©MIXI 共闘ことばRPG コトダマン 「ことば」で闘う、スマートフォン向け新感覚ことば遊びゲーム 5

Slide 6

Slide 6 text

©MIXI MIXI移管 2019年10月にミクシィ(現 MIXI)に運営移管 6 引用元: 2019.08.09 https://www.sega.jp/topics/detail/190809_7/

Slide 7

Slide 7 text

©MIXI 2023年4月にリリース5周年を迎えます 7 タイトル移管以降も継続的に新機能の開発や改善を行い、 5周年を目前に控えた今でも多くのユーザーさんにプレイいただいています

Slide 8

Slide 8 text

©MIXI 8 コトダマンの運用面の課題

Slide 9

Slide 9 text

©MIXI 成長の裏で頻発する障害 タイトル移管直後は運用体制も大きく変動したため障害が頻発していました ● マスタデータの入稿ミス ● マスタデータの巻き戻り ● 新規機能実装時のエンバグ ● 負荷対策の不足 ● etc… 9 データ更新の度に問題が発生するような状態 更新日はドキドキ・事後対応にドタバタ

Slide 10

Slide 10 text

©MIXI 障害発生数の低下 ● 新規機能の開発や運用と並行して 少しづつ障害の原因を取り除く運動をチーム全体で重ねていきました ● 今では大規模な(ユーザー影響の大きい)障害の発生頻度は低下し 安心して本番環境に変更を加えられるようになりました! ○ 時折ヒューマンエラーは発生するものの、 障害の規模はより小さく、頻度はより低くなりました 10

Slide 11

Slide 11 text

©MIXI 解決への取り組み 11 ● マスタデータ運用フローの改善 ● マスタデータバリデーションツールの導入 ● サーバーアプリケーションのリアーキテクティング ● アセット運用フローの改善 ● プロジェクト全体での定期的なポストモーテム実施 ● などなど...

Slide 12

Slide 12 text

©MIXI 解決への取り組み 今日話すのはこれ! 12 ● マスタデータ運用フローの改善 ● マスタデータバリデーションツールの導入 ● サーバーアプリケーションのリアーキテクティング ● アセット運用フローの改善 ● プロジェクト全体での定期的なポストモーテム実施 ● などなど...

Slide 13

Slide 13 text

©MIXI コトダマンのサーバーアプリケーション 13

Slide 14

Slide 14 text

©MIXI コトダマンのアーキテクチャ概要 一般的なソーシャルゲームによくあるアーキテクチャ 14  APIサーバー  Java 8(Amazon Corretto) tomcat struts2 DB (Amazon Aurora)  チャットサーバー  Java 8(Amazon Corretto) tomcat struts2 その他     運用用途サーバー ゲームクライアント (Unity製) マルチプレイサーバー       monobit engine APIサーバー : ゲーム上の様々な処理を実現するための APIを提供(クエスト開始・ガチャ・ etc…)

Slide 15

Slide 15 text

©MIXI コトダマンのアーキテクチャ概要 一般的なソーシャルゲームによくあるアーキテクチャ 15  APIサーバー  Java 8(Amazon Corretto) tomcat struts2 DB (Amazon Aurora)  チャットサーバー  Java 8(Amazon Corretto) tomcat struts2 その他     運用用途サーバー ゲームクライアント (Unity製) マルチプレイサーバー       monobit engine APIサーバー : ゲーム上の様々な処理を実現するための APIを提供(クエスト開始・ガチャ・ etc…) 今回のリアーキテクティング対象

Slide 16

Slide 16 text

©MIXI 16 APIサーバーの課題 ● APIサーバーの実装は複雑化し、保守が困難に ○ 既存機能の改修時にエンバグが多発 ○ 開発速度の低下(既存実装の調査にコストがかかる) ○ 中には、エンバグを恐れて改修できない機能も...

Slide 17

Slide 17 text

©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

Slide 18

Slide 18 text

©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上のレコードから変換されたオブジェクト

Slide 19

Slide 19 text

©MIXI 問題点② : 密結合したビジネスロジック 19 Tool public static 巨大メソッドA public static 巨大メソッドB Toolは巨大なstatic methodの集合体 手続き的に書かれており、 それぞれの処理に暗黙的な依存関係がある (呼び出し元・先の処理内容が隠蔽されていない) これら巨大メソッドに自動テストはなく、 変更の影響範囲が読めない状態 public static 巨大メソッドC 呼び出し 呼び出し primitiveな値を大量に引数で要求するstatic methodの連鎖

Slide 20

Slide 20 text

©MIXI 巨大基底クラス 問題点③ : 複雑な依存関係 20 巨大UtilityTool A MasterData Cache Class 依存の方向性が制御されておらず、 モジュール間で複雑に依存しあっている 巨大ビジネスロジック Tool C 巨大UtilityTool B 巨大ビジネスロジック Tool D

Slide 21

Slide 21 text

©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 依存の方向

Slide 22

Slide 22 text

©MIXI サーバーアプリケーションの リアーキテクティング 22

Slide 23

Slide 23 text

©MIXI リアーキテクティングの狙い ● ルールに従って実装しているうちに、 オブジェクト指向らしい設計ができるようになること ○ ドメインモデルを作成・ビジネスロジックをモデルに集約することを強制し ルールに従った実装をしているうちに学習が進むような設計であること ● ルールを元にレビューが行えること ○ 設計の良し悪しを議論するための土台にできること ○ レビューコストの低下 ● 自動テストを書く文化を成熟させること ○ ルールに従って実装すればテスタブルな設計になること ○ 自動テストを必須とする部分・そうでない部分を分けて 少なくともコアなロジックに自動テストを書くことを当たり前にする ○ TDDが自然と行われる下地を作る 23

Slide 24

Slide 24 text

©MIXI 新アーキテクチャ 24 Presentation UseCase DAO Entity Response Request MasterData Model Service IRepository Repository Action ● オニオンアーキテクチャを参考にシンプルなアーキテクチャに設定 ● ビジネスロジックをModel層に集約することを狙う ● 依存の方向性を制御し、変動しやすいレイヤにコアロジックが依存しないようにする 依存の方向性

Slide 25

Slide 25 text

©MIXI Modelレイヤ  25 Presentation UseCase DAO Entity Response Request MasterData Model Service IRepository Repository Action ● ビジネスロジックを受け持つ層 ○ DDDでいう値オブジェクトやエンティティをこのレイヤに置く ○ 例: User, Charactor, Gacha, Quest … etc ● Modelレイヤのクラス中の公開メソッドには原則単体テストを必須とする ○ 自動生成された getter は対象外

Slide 26

Slide 26 text

©MIXI Serviceレイヤ  26 Presentation UseCase DAO Entity Response Request MasterData Service IRepository Repository Action ● 複数のModelを利用してアプリケーションに必要な機能を実装するレイヤ ○ 複数のコンテキストで利用される、汎用的かつ複数の Modelが関連する処理を置く ○ 例: ItemService(アイテムの付与) ● できる限りModelのみで実装を行うが、綺麗にまとまらない場合には Serviceを使う ● 旧実装(Tool)との繋ぎ込みを担う、腐敗防止層として利用されることもある Model

Slide 27

Slide 27 text

©MIXI UseCaseレイヤ  27 Presentation UseCase DAO Entity Response Request MasterData Service IRepository Repository Action ● アプリケーションの機能を実現するために、処理順序を規定するためのレイヤ ○ 例: ExchangeItemUseCase(アイテムを消費し、別のアイテムを獲得する) ● DIされたRepositoryを利用してModel / Serviceを取り出し、ビジネスロジックを順に実行 Model

Slide 28

Slide 28 text

©MIXI Presentationレイヤ  28 Presentation UseCase DAO Entity Response Request MasterData Service IRepository Repository Action ● ゲームクライアントとのやりとりを担うレイヤ ● クライアントからのリクエストに応じて必要な UseCaseを実行し、 実行結果をクライアントの求める形式のレスポンスに変換し、返却する Model

Slide 29

Slide 29 text

©MIXI Presentation Repository 29 UseCase DAO Entity Response Request MasterData Service IRepository Repository Action ● DB上に永続化されたユーザーデータや、メモリ上にキャッシュされたマスタデータから Modelを再構築する ● ビジネスロジックは持たず、 Modelの永続化に集中する ● UseCaseから利用する場合にはインタフェースを介して依存性を逆転する Model

Slide 30

Slide 30 text

©MIXI 移行のフロー 30 新アーキテクチャの提案・議論 検証期間 新アーキテクチャに則った実装を必須とせず、 有志が新アーキテクチャで開発を進めていく 実装中に出てきた疑問点について議論し、ルールを成長させる 並行してオブジェクト指向設計に関する読書会も実施 本実行:新規実装に関しては基本的に新アーキテクチャに則って開発する 新アーキテクチャへの移行はチームの状況を見ながら緩やかに進めていきました

Slide 31

Slide 31 text

©MIXI 結果のふりかえり 31

Slide 32

Slide 32 text

©MIXI アーキテクチャの浸透度合い ● 新機能はアーキテクチャに則って実装されるようになった ● 過去実装については移行を必須とせず、緩やかに改善中 ○ 機能実装のついでに過去実装の一部を Serviceに切り出す ○ 過去実装から新UseCaseを呼び出す形で共存する ○ … などなど 32

Slide 33

Slide 33 text

©MIXI Keep : 依存の方向性の制御 ● 依存関係が制御されるようになった ○ クライアント都合でレスポンスが変化しても、ビジネスロジックは影響を受けづらい形に ● 副次的な効果 ○ レスポンスが決まらないうちにコアロジックから実装を進める ○ マスタデータの調整中に DummyのRespositoryを作成してコアロジックから実装を進める などの柔軟な実装順の制御が行いやすくなった ○ 作業分担も行いやすくなった 33

Slide 34

Slide 34 text

©MIXI Keep : チーム内の設計に関する知見 ● チーム内で設計に関する議論が活発に行われるようになった ○ GitHub Discussionsに議論の場を用意 ○ 普段の開発中に気になったことや提案を記録し、 週次定例で繰り返し議論の場を設けるようになった ○ 議論の例 ■ CQRSを導入してみたい! ■ 細かい命名規則についての議論 ● レビューがアーキテクチャを中心に行われるようになった ○ 共通認識を持てているので議論が捗る 34 GitHub Discussionsを利用した議論

Slide 35

Slide 35 text

©MIXI Keep : テストを書く文化の醸成 ● 新アーキテクチャ導入前のテストカバレッジ( Line) : 0.6% ● 新アーキテクチャ導入後のテストカバレッジ( Line) ○ Model : 73% (※ 自動生成された getter はテスト不要としている ) ○ Service : 65% ○ UseCase : 47% ○ Repository : 54% ○ 全体 : 12% ● Model以外のレイヤにもテストが書かれるようになった ● PR上で「ここテスト欲しい!」という指摘が行われるように ● 既存の巨大モジュールに関しては未だテストが書けていない状態 ○ テスタブルでない設計のため機能実装の片手間では解決できない 35

Slide 36

Slide 36 text

©MIXI Problem : 実装コストの増加 ● 実装スピードがやや低下 ○ 特にRepositoryの実装においてボイラーテンプレートコードが必要になった ○ DB上のレコードから大量のカラムを Modelのコンストラクタに引き渡す処理 36 DAO UserData MasterData Model IRepository Repository.getModel()

Slide 37

Slide 37 text

©MIXI Problem : 曖昧な実装ルールによる混乱 ● 曖昧なルールが混乱を呼ぶことも ○ 特にServiceの用途が理解されづらく、 ビジネスロジックをとりあえず置く場になってしまいがちになった ■ Modelの実装が少なくなり、ドメインモデル貧血症に ○ 検証期間は細部が曖昧なままの運用だったため、 現状のルールと異なる部分がちらほら ● 新規メンバーのイニシャルコスト増加 ○ 過去実装と新規実装でスタイルが異なり混乱してしまう 37

Slide 38

Slide 38 text

©MIXI Problem : Repository ● Repositoryにビジネスロジックが実装されてしまいがちに ○ クエリ効率を考慮するとやむを得ないことも多い ● 暗黙的なRepository同士の依存関係 ○ ModelとDBレコードを切り離したことで単一責任でないテーブルを扱いやすくなった ○ 一方で、同じテーブルを参照する Repository間で暗黙的な依存が生まれてしまった ■ 一見関連のない別 Model・Repositoryで同じカラムを参照・変更しているなど ● mockが多用されるテスト ○ UseCaseのテストで都度Repositoryをmockしている ○ 複数のテスト間で共通のRepositoryが利用できておらず、テスト実装効率が低下 38

Slide 39

Slide 39 text

©MIXI これからの取り組み 39

Slide 40

Slide 40 text

©MIXI これからの取り組み ● アーキテクチャの簡略化 ○ 変更容易性を残したまま、より実装が高速になるための進化 ○ ボイラーテンプレートコードを書かなくて済むような工夫を検討中 ■ Repositoryを廃してO/RマッピングされたオブジェクトをModelとして扱う ■ テンプレート機能の利用 40 引き続きチーム内で議論を重ねています

Slide 41

Slide 41 text

©MIXI これからの取り組み : 過去実装のリライト ● スクラム開発T内ではレガシーな過去実装の書き換えは進まない ○ 片手間では改善できない巨大モジュールをコストをかけて改善する必要 ○ エンバグを恐れて変更できない状態のモジュールがあることが 選択肢を減らし、プロジェクトの成長可能性を狭めてしまう ● チーム体制も含めた改善 ○ 改善を推し進めるチームとしてSREチームを組成 ○ ビジネスサイドと議論を重ね、どの時期にどのモジュールを改善するか 優先順位をつけて改善していく 41

Slide 42

Slide 42 text

©MIXI まとめ ● サーバーアプリケーションの保守性向上のため、 新アーキテクチャの導入を進めました ● 新規機能を新アーキテクチャで実装しました ○ 実装スピードはやや低下したものの、テスタブルな設計になりました ● チームもアプリケーションと共に成長しました ● 既存機能の置き換えはまだまだこれから ● より多くの価値をユーザーに届けるために これからも技術負債の解決を進めていきます! 42

Slide 43

Slide 43 text

©MIXI