Slide 1

Slide 1 text

大規模Unityゲーム開発の設計事例 〜ドメイン駆動設計とDIコンテナを導入した一年を振り返る〜 Applibot 畑山政道

Slide 2

Slide 2 text

2 自己紹介 【経歴】 広告系Flashコンテンツ開発 → 2011年 アメーバピグ Flash開発で(株)サイバーエージェント入社 → スマホブ ラウザゲーム開発 → スマホUnityゲーム開発 → (株)アプリボット所属 【主な仕事】 Unity全般・設計・3D描画関係・昔はFlashでアニメーション作ったり演出・UXも考えたりしていました 畑山 政道 Unityエンジニア 2011年 中途入社

Slide 3

Slide 3 text

はじめに・ドメイン駆動設計を導入した経緯 1 2 3 4 目次 ドメイン駆動設計とUnity適用までの具体的な解説 DIコンテナとUnity適用までの具体的な解説 その他の設計手法の紹介 5 実装の詳細 6 まとめ

Slide 4

Slide 4 text

1. はじめに・ドメイン駆動設計を導入した経緯

Slide 5

Slide 5 text

5 スマートフォン向けのソーシャルゲームを開発中 2つの大事な要素 1. 素早く開発してアプリをリリースする 2. そのあとの運用・新機能追加をスムーズに行う つまり、開発スピードとメンテンス性の両立が必要 アプリ開発に求められる事

Slide 6

Slide 6 text

6 設計が重要 設計の綺麗さは金銭的な価値も生む • イテレーションが素早く回せる事で試行錯誤の回数が増え、品質が上がる • リリース後、運用に入っても機能追加がすぐに実装できる • バグが出にくい • バグが出ても即座に対応できる といった事はビジネス上では重要

Slide 7

Slide 7 text

7 設計が重要 逆に設計を疎かにすると・・ 機能実装に時間がかかり、バグが多い場合、ユーザー離れ・機会損失に繋がる

Slide 8

Slide 8 text

8 多人数での開発だと • 自己流の設計が至るところに散らばってしまう • 自己流がぶつかり合うと、コードレビューが難しくなる • 諦め (わからない/面倒だから良いや) • 対立 (自己流 vs 自己流) 設計が重要

Slide 9

Slide 9 text

9 チーム独自の方法だと • 本を参照したり・ネットで検索出来ない • チームの外に出た時に通用しない・覚え直し チーム内で思想を統一したい・実績のある体系化された手法が欲しい 設計が重要

Slide 10

Slide 10 text

10 EasyとSimpleの違い https://twitter.com/t_wada/status/1377147203077111814 和田 卓人さんのtweetから引用 設計が重要 - easyとsimple

Slide 11

Slide 11 text

11 大規模な開発にはsimpleが向いていると判断。 Easyはモック開発であったり、小規模な開発で は向いている。 EasyではなくSimpleな方針を取る。 その代わり手数は増える(コード量は増える)が 分かりやすい構造を目指す。 Simpleの実現にDDDが向いていると判断 EasyとSimpleの違い 設計が重要 - easyとsimple https://twitter.com/t_wada/status/1377147203077111814 和田 卓人さんのtweetから引用

Slide 12

Slide 12 text

12 ドメイン駆動設計とは 2003年、エリック・エヴァンスの書籍が発売 される。 Domain Driven Designの頭文字を取って DDDとも呼ばれる。 上手くシステム開発を進めるためのパターン・ ランゲージ。 良くある問題と、それに対するベストプラク ティスについて集めたパターン集。

Slide 13

Slide 13 text

13 ゲーム開発ではあまり聞かない ゲームに適用できるのか? 可能。ジャンルはあまり関係ない。 ドメインに焦点をあてて開発する、というだけ ドメイン駆動設計とは

Slide 14

Slide 14 text

14 ドメインって何だ? 「ソフトウェアが解決しようとしている問題の対象領域」 会計システムであれば、会計に必要な金銭・帳票といった概念。 物流システムなら、物流の倉庫・貨物・輸送手段といった概念。 一意に決まるものではなく、ソフトウェアによって違う。 ゲーム制作で言うなら、そのゲーム独自の概念・ルール・仕様 ドメイン駆動設計とは

Slide 15

Slide 15 text

15 ゲーム開発において、わりと当たり前の事を言っている • ディレクター・プランナーとよく対話し、ゲームが必要としている独自の機 能(ドメイン)をよく理解する • 対話して明らかになった知識(ドメイン)を元に、共通のモデルを作りあげる • モデルをプログラムに純度高く反映させる どうやったらこれらを実現できるのか? というベストプラクティスが説明さ れている。 ドメイン駆動設計とは

Slide 16

Slide 16 text

16 DDDスコアカード https://www.informit.com/articles/article.aspx?p=1944876&seqNum=2 より引用 ドメイン駆動設計とは 向かないジャンル 「完全にデータ中心である」 「小規模・シンプルな場合」

Slide 17

Slide 17 text

17 反対に向いているのは、継続的に機能が変更され、複 雑性をもったアプリケーション。ゲーム開発はこれに 当てはまる。 向いているジャンル ドメイン駆動設計とは

Slide 18

Slide 18 text

18 ドメイン駆動設計 == ドメインに焦点をあてた開発 ドメインモデルをコードに正確に落とし込む事が大事。 ベースとなる技術 オブジェクト指向 ドメイン駆動設計とは

Slide 19

Slide 19 text

19 ここで言うオブジェクト指向とは? ・データと関数を個別に扱わずに、双方を一体化したオブジェクトを基礎要素にする ・オブジェクト間の相互作用を重視して、プログラムを構築する ・現実世界の”物”や”概念”をオブジェクトに模して表現する ドメイン駆動設計 - オブジェクト指向 +

Slide 20

Slide 20 text

20 逆にオブジェクト指向では無いものを考える 手続き型 データと振る舞い(関数)の記述が分かれている。オブジェクトは単なるデータ 構造としてみる。 ドメイン駆動設計 - オブジェクト指向

Slide 21

Slide 21 text

21 オブジェクト指向を踏まえて ドメイン駆動設計に向かないジャンル 「高速化が最重要である場合」 現在Experimental版のECS(Entity Component System)はデータ指向であり、オ ブジェクト指向では書けないため、ドメイン駆動設計が適用しずらい ドメイン駆動設計 - オブジェクト指向

Slide 22

Slide 22 text

22 「高速化が最重要である場合」 ゲームのドメイン部分はDDDで開発し、 高速化が必要になる部分はECS、のような住み分けが必要になる (おそらくECS化されるような部分は グラフィック描画、物理演算、サウンド処理、AIなど?) ドメイン駆動設計 - オブジェクト指向

Slide 23

Slide 23 text

2. ドメイン駆動設計とUnity適用までの具体的な解説

Slide 24

Slide 24 text

24 ドメイン駆動設計の原典。 しかし、かなり難しい。 ドメイン駆動やるぞ! と意気込んで読み始め ると、まず挫折しがち。 参考書籍 エヴァンス本 原典: エヴァンス本

Slide 25

Slide 25 text

25 PDFで配布されている。 こちらもドメイン駆動設計をコンパクトに要約 したもの。日本語版もある。 ドメインモデルを作っていく過程などの例がと ても分かりく、オススメ https://www.infoq.com/jp/minibooks/domain-driven-design-quickly/ 参考書籍 DDD Quickly

Slide 26

Slide 26 text

26 帯や「はじめに」にあるように、エヴァンス本にあるプロ グラミングに適用するパターンをメインに紹介している。 ・チーム内で読書会を実施 ・その後も継続的に勉強会実施 ドメインについても多く言及されている。 その名の通り、入門に最適。 https://www.amazon.co.jp/dp/B082WXZVPC 参考書籍 ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

Slide 27

Slide 27 text

27 ドメインモデリングをはじめとして、DDD全般的に解説 されている。 ドメインモデリングについて書かれている書籍は少ないの で、大変参考になる。 https://booth.pm/ja/items/1835632 参考書籍 ドメイン駆動設計 モデリング/実装ガイド

Slide 28

Slide 28 text

28 PDFで配布されている。 全容を把握するのに非常に役に立つ。 https://www.domainlanguage.com/ddd/ reference/ 日本語に訳している方も https://zenn.dev/takahashim/books/fb4cdc32f8e95c 参考書籍 DDD Reference

Slide 29

Slide 29 text

29 改めてゲーム制作のドメインとは何か考える 「ソフトウェアが解決しようとしている問題の対象領域」 「ゲームが表現しようとしている対象領域」 ・自分たちが作っているゲームで表現しようとしているもの ・ゲームが提供する「遊び」 参考: ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH https://booth.pm/ja/items/1835632

Slide 30

Slide 30 text

30 改めてゲーム制作のドメインとは何か考える 「ゲームが表現しようとしている対象領域」 データベース ファイルI/O 物理エンジン UI サウンド処理 描画エンジン ユーザー入力 世界観 キャラのスキル 成長ロジック ダメージ計算 キャラ性能 通信 行動エリア 地形 キャラ属性

Slide 31

Slide 31 text

31 データベース ファイルI/O 物理エンジン UI サウンド処理 描画エンジン ユーザー入力 世界観 キャラのスキル 成長ロジック ダメージ計算 キャラ性能 通信 行動エリア 地形 システムを構成する要素から、汎用的な部分を抜いて残る部分 キャラ属性 改めてゲーム制作のドメインとは何か考える 「ゲームが表現しようとしている対象領域」

Slide 32

Slide 32 text

32 • 自キャラの成長ロジックなど • どうやってレベルアップする? • 武器/防具を装備する? • スキルの存在 • インゲーム • ゲームのルール • いつスキルが発動するのか? • ダメージの計算方法は? 改めてゲーム制作のドメインとは何か考える 「ゲームが表現しようとしている対象領域」→ ゲーム独自の概念・ルール・仕様

Slide 33

Slide 33 text

33 自分たちが作っているゲームで実現しようとしているもの • アクションゲームだと • ステージ情報 • 何がどこに配置される? • 壊せる・壊せない・移動する etc.. • 自キャラ・敵キャラの配置 • 自分・敵キャラのステータス • HP、MP、攻撃方法 etc.. • ジャンプする・敵を踏みつける事で敵を撃退する、のようなゲーム固有のルール 改めてゲーム制作のドメインとは何か考える

Slide 34

Slide 34 text

34 将棋ゲームのドメイン知識 の一例 • 登場キャラクター • 40枚の駒 • 駒の性能は8種類。それぞれ動ける範囲が違う • 成長ロジック • 成駒。動ける範囲が増える • mapの広さ・位置 • 9x9の81マスで戦う • 相手の駒を取り、保持する/使う 改めてゲーム制作のドメインとは何か考える

Slide 35

Slide 35 text

35 • 9人 vs 9人 • 選手には打率・防御率・スタミナ、etc…などの能力パラメーターがある • ストライク3つでアウト • バッターはボールを打ち返す • 攻撃側・防御側に分かれ、交互に入れ替わる • これらにゲーム特有のルールも加わる • 選手の成長要素、必殺技、アイテムetc.. 野球ゲームのドメイン知識 の一例 改めてゲーム制作のドメインとは何か考える

Slide 36

Slide 36 text

36 これらのドメイン知識をゲーム中で扱えるように、モデルを作りあげる。 オリジナルゲームの場合、最初から明確な事は多くなかったりする。 対話・モデリングを繰り返し、ぼんやりしたモデルの輪郭をハッキリさせていく。 改めてゲーム制作のドメインとは何か考える

Slide 37

Slide 37 text

37 ドメインとそれ以外で分離させる事が大事。ドメインロジックをUIコンポーネントに 書いたりしない。 Viewとドメインを分離する。ここらへんはMVC的な発想と同じ。 ドメインの分離

Slide 38

Slide 38 text

38 どういう時に、どういう条件で点滅するのか? をViewには記述しない。 瀕死状態の定義や、瀕死状態かどうかの判断 を書くのはドメインロジック。 お題 「瀕死のキャラクターが点滅する」 ドメインの分離

Slide 39

Slide 39 text

代表的な概念・パターンの紹介

Slide 40

Slide 40 text

エヴァンス本で紹介されているパターン一覧 DDD難民に捧げる Domain-Driven Designのエッセンス 第1回 ドメイン駆動設計とは https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap1.html より引用 重要 + 実例が示しやすい赤枠部分を中心に紹介します

Slide 41

Slide 41 text

41 アプリが対象とする領域の専門家のこと 銀行の業務支援アプリを作るなら、 銀行の業務を熟知する銀行員がドメインエキスパート ゲームにおけるドメインエキスパートは? 現プロジェクトでは、ディレクター・プランナーといったゲームの ルール・世界・仕様を考える人たち、と捉えた。 ドメイン エキスパート

Slide 42

Slide 42 text

42 ドメインエキスパートと絶えず対話し、ドメインをうまくモデリングするのが大切。 仕様書をそのまま実装する、というのはNG。 仕様書に記載された機能の裏にある目的などを理解する必要がある。 ドメイン エキスパート

Slide 43

Slide 43 text

43 プロジェクト内で使う共通の言葉を定義する。 開発者とドメインエキスパートが同じ単語を別の意味で用いないようにする。 ユビキタス言語

Slide 44

Slide 44 text

44 例) キャラクターが持つ特殊能力について・・ 使用する用語をプロジェクト全体で統一する。 バラバラだと変換コストが発生するし、勘違いも生まれる。 アビリティ スキル スキル ユビキタス言語

Slide 45

Slide 45 text

45 プログラム中に使われる関数・変数もユビキタス言語を採用する。 しかし日本語だった場合は難しい。 そのためにも、現プロジェクトでは英語との対応表を作って管理している。 サーバー・クライアントでプログラム内で使われる単語を合わせるのにも役立つ。 ユビキタス言語

Slide 46

Slide 46 text

プログラムに適用出来るパターンを一部紹介 値オブジェクト 1 エンティティ 2 3 リポジトリ 4 サービス 5 集約 6 境界づけられたコンテキスト 7 レイヤードアーキテクチャ

Slide 47

Slide 47 text

47 • C#の値型とは無関係 • 一度生成されたら中身が変化しない。不変である。 • 中身が同じなら等価とみなす 1. 値オブジェクト

Slide 48

Slide 48 text

48 実装に面倒な箇所があるが、 不変性によって実装は大幅に単純化され、安全に共有や参照渡しができるようになる という大きなメリットがある DDDに関係無く有用 1. 値オブジェクト

Slide 49

Slide 49 text

49 値オブジェクトを 疑似コードで表現すると 1. 値オブジェクト

Slide 50

Slide 50 text

50 現プロジェクトでは、 全てのフィールドをreadonlyにして、コンスタントラクタで値を設定している 1. 値オブジェクト 値オブジェクトの実装について - 不変の実装

Slide 51

Slide 51 text

51 厳密には • 全てのフィールド変数が同一であるか比較するEquals()を用意 • 全てのフィールドを考慮したGetHashCode()関数を用意する必要がある 1. 値オブジェクト 値オブジェクトの実装について - 等価の実装

Slide 52

Slide 52 text

52 Equals()、GetHashCode()があると・・ 1. 値オブジェクト 値オブジェクトの実装について - 等価の実装 中身で比較される

Slide 53

Slide 53 text

53 1. 値オブジェクト Dictionaryのkeyとして使う時、中身ベースで使用される これはstructのVector2の例だが、これと同じ事が出来るようになる。 値オブジェクトの実装について - 等価の実装 Equals()、GetHashCode()があると・・

Slide 54

Slide 54 text

54 • データベースのエンティティとは無関係 • 生成された後、中身がどんどん変化していくもの 2. エンティティ 同一性比較のため、目印となるidを持つ。idが同じであれば、中身が違っても等価 とみなす 同一性によって区別される 中身が変化しても同一のものとして扱う

Slide 55

Slide 55 text

55 現プロジェクトでは • 変化していくユーザー情報 • 成長していくキャラクター情報など • 同一のキャラクターが、level、hp、mpなどのパラメーター変化していく事を表現す る 2. エンティティ

Slide 56

Slide 56 text

56 エンティティを 疑似コードで表現すると 2. エンティティ

Slide 57

Slide 57 text

57 「物」としてモデリングできないものもある。そうしたものは値オブジェクトやエ ンティティを取り扱う「サービス」として実装する 3. サービス 気を抜くと手続き型となる。多用しないように注意。

Slide 58

Slide 58 text

58 関連するオブジェクトの集まり。データを変更するための単位として扱われる 4. 集約 OrderItem Create Destroy Price VatRate Value to_i to_f Customer Order add_item remove_item total_price Entity Entity Entity Value object 窓口 = 集約Root 集約内部 この中はカプセル化されている Aggregate 窓口を通さないアクセスは認めない 引用 Facade Pattern as the way to implement Aggregate http://rubyblog.pro/2017/05/facade-pattern

Slide 59

Slide 59 text

59 ひとつの塊として扱う 塊の中にアクセスするための窓口を用意しており、窓口を飛び越して中身にアクセス 出来ない Facadeパターンによく似ている 4. 集約

Slide 60

Slide 60 text

60 Facadeパターン Facade適用後 Subsystem内に、 外から自由にアクセスしている 窓口を通してしかアクセスできない 引用 Facade Pattern as the way to implement Aggregate http://rubyblog.pro/2017/05/facade-pattern 窓口を設ける事で、複雑な構造をカプセル化する 4. 集約

Slide 61

Slide 61 text

61 データ(集約)の置き場所 データストアを操作する処理をカプセル化する。集約の保存・取得はここを通す。 他のクラスはデータストアを直接操作しない。 DBがある事を意識せず、メモリ内コレクションのように振る舞う。 5. リポジトリ 依頼者 集 約 集 約 write read 保存 問い合わせ 返却 リポジトリ

Slide 62

Slide 62 text

62 複数チームで同時開発をしていると、 ある概念に対して複数のモデルが出来る事がある。 これに対し、無理に統一された大きなモデルを作る必要はない。 6. 境界づけられたコンテキスト メインロジックが扱う キャラクターのモデル Viewが扱う キャラクターのモデル

Slide 63

Slide 63 text

63 それぞれのモデルが適用される境界を明確にし、 境界内だけで適用されるモデルを作る。 境界を明示的にする事で、境界内を独立して開発可能になり、 他の箇所との結合度が減る。 6. 境界づけられたコンテキスト ? コンテキストが異なる キャラクター キャラクター キャラクター

Slide 64

Slide 64 text

64 7. レイヤードアーキテクチャ ・ユーザーインターフェース (プレゼンテーション) ・アプリケーション ・ドメイン ・インフラストラクチャー の4層に分ける 「エリック・エヴァンスのドメイン駆動設計」より引用

Slide 65

Slide 65 text

65 インフラストラクチャの位置を変更したバージョンを採用。 7. レイヤードアーキテクチャ ヴォーン・ヴァーノン 著 「実践 ドメイン駆動設計」より引用

Slide 66

Slide 66 text

66 実践ドメイン駆動設計で紹介されているバージョン。 ドメインオブジェクトを管理するインフラストラクチャ層 が、ドメイン層を参照できた方が都合が良かったため。 Assembly Definitionsを使って管理 7. レイヤードアーキテクチャ

Slide 67

Slide 67 text

67 各パターンがどの層にあてはまるか インフラストラクチャー層 • 基盤的な機能の実装 • 通信機能、DBアクセス、ローカルセーブデータなど の実装 • リポジトリの実装 7. レイヤードアーキテクチャ

Slide 68

Slide 68 text

68 ユーザーインターフェース層 (プレゼンテーション層) • UI表示 • アウトゲームのMVC/MVPで構成された各画面 現プロジェクトでは、 MonoBehaviour継承クラスだからUI層、とはしていない。 そのクラスが提供している機能で判断している。 各パターンがどの層にあてはまるか 7. レイヤードアーキテクチャ

Slide 69

Slide 69 text

69 アプリケーション層 • ドメインオブジェクト・基盤機能を使って、アプリ ケーション全体の調整・進行役を担う • ドメインとUI層の架け橋 各パターンがどの層にあてはまるか 現プロジェクトでは、ユーザー情報、キャラクター情報の管理、取 得、変更などを担う機能実装がある。 機能毎にユースケース・クラスを作成し、そこに処理を記述してい る。 キャラクターをレベルUPさせる・装備を変更する・etc.. 7. レイヤードアーキテクチャ ゲームが持つ機能の実装

Slide 70

Slide 70 text

70 ドメイン層 • ドメインロジックの実装 • 値オブジェクト • エンティティ • ドメインサービス 各パターンがどの層にあてはまるか システムの心臓部 7. レイヤードアーキテクチャ

Slide 71

Slide 71 text

71 ドメイン層が他のレイヤーと隔離されてい る事が重要。 他にもパターンは ・オニオンアーキテクチャ ・ヘキサゴナルアーキテクチャなどがあ る。 参考 : [DDD]ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か https://qiita.com/little_hand_s/items/ebb4284afeea0e8cc752 7. レイヤードアーキテクチャ

Slide 72

Slide 72 text

72 Assembly Definitionsで定義 メリット 相互参照を無くして依存関係が単純になる レイヤードアーキテクチャの実装

Slide 73

Slide 73 text

73 レイヤードアーキテクチャを厳密に運用するのは結構難しい。 特にAssembly Definitionsを使って厳密に依存関係をコントロールする場合。 上から下へ、単一の方向にしかアクセス出来なくなる。 とはいえ逆方向にあるレイヤーの機能を使いたい事はある。 現プロジェクトでの解決方法を紹介します。 レイヤードアーキテクチャの実装

Slide 74

Slide 74 text

74 依存関係逆転の原則 レイヤードアーキテクチャの実装 具象 ではなく、抽象 だけを参照するようにする 具象 → 実装クラス 抽象 → interfaceやabstractなどの抽象宣言 <参考> Robert C.Martin,角 征典,高木 正弘. Clean Architecture 達人に学ぶソフトウェアの構造と設計

Slide 75

Slide 75 text

75 依存関係逆転の原則 レイヤードアーキテクチャの実装 Presenter Application 上層から下層への依存関係しか存在しなくなる ソースコードの依存方向 (※ 処理の流れではない) Assembly Definitionsを設定 UIレイヤー (プレゼンテーション層) アプリケーション層

Slide 76

Slide 76 text

76 レイヤードアーキテクチャの実装 下層から上層のクラスを参照する事はできない。 しかし、上層に対して処理を実行したい。 Presenter Application 依存関係逆転の原則

Slide 77

Slide 77 text

77 レイヤードアーキテクチャの実装 アクセス可能な階層のinterfaceに依存 するようにする。 依存関係逆転の原則 Presenter Application IOutput OK OK

Slide 78

Slide 78 text

78 実際の処理の流れ と、ソースコードの参照方向が逆になる レイヤードアーキテクチャの実装 Presenter Application Presenter Application IOutput 処理の流れ ソースコードの参照方向 依存関係逆転の原則

Slide 79

Slide 79 text

79 しかし、結局は上層のインスタンスを取得する必要がある。 では、どう取得するのか? Abstract Factory、Service Locatorなどの解決方法があるが、 現プロジェクトでは後述のDIコンテナを使用した。 レイヤードアーキテクチャの実装 依存関係逆転の原則 Presenter Application IOutput 実体であるviewは、 どうやって取得するのか?

Slide 80

Slide 80 text

3. DIコンテナとUnity適用までの具体的な解説

Slide 81

Slide 81 text

81 多用されがちだったsingletonを使いたくない。singletonだと・・ • global変数 • 依存関係が見えづらい • 具象クラスに依存するため依存関係逆転の原則が使えない • Interfaceを使った実装の入れ替えが難しい 経緯

Slide 82

Slide 82 text

82 Interfaceを使った実装の入れ替え とは 状況に応じて、実装A と 実装B を 切り替える 経緯 Interface 実装 A 実装 B

Slide 83

Slide 83 text

83 外部から必要とするインスタンスを渡すのがDI (Dependency Injection) (「DIコンテナ」ではなく、 ただの「DI」) DIコンテナとは これを自動でやってくれるのが 「DIコンテナ」

Slide 84

Slide 84 text

84 Zenject(exenject) https://github.com/modesttree/Zenject リポジトリにあるサンプルコードを引用して説明します。 DIコンテナとは

Slide 85

Slide 85 text

85 DIコンテナとは 階層構造の一番下

Slide 86

Slide 86 text

86 DIコンテナとは 上から渡してあげる

Slide 87

Slide 87 text

87 DIコンテナとは 渡すものを作る必要がある

Slide 88

Slide 88 text

88 この繰り返しが、ずっと上の階層まで続いてしまう DIコンテナとは 上から渡してあげる

Slide 89

Slide 89 text

89 最終的に、上記のような依存関係の解決だけを担ってくれるものが必要になる。 上記のような処理を自動で解決してくれるのがDIコンテナ。 (誰が何を必要としているのか・生成の順序などを管理。生成知識を一手に担う黒子のような存在) DIコンテナとは 48. GoFデザインパターンとDI (前編) w/ twada https://fukabori.fm/episode/48

Slide 90

Slide 90 text

90 Unityでの実際の使用イメージ ISomeServiceとして、SomeServiceを登録

Slide 91

Slide 91 text

91 Unityでの実際の使用イメージ

Slide 92

Slide 92 text

92 • 各種サービスの連携 • レイヤーを跨いだ参照の受け渡し • 逆方向のレイヤーにある実装を使用する場合は、 interfaceを置く場所で制御 プロジェクトでの使い方 依存関係逆転の原則を活用 Interface 実装 ここにinterfaceを置く この層の実装を使いたい

Slide 93

Slide 93 text

93 Interfaceに依存する事で、実装の差し替えを行う事ができる。 実装の差し替え インターフェース IOutput 通常UI UI非表示 log出力のみ 例「デバッグ用に、UI非表示でlog出力だけ行う高速周回モードの実装」 通常時 デバッグ時 プロジェクトでの使い方

Slide 94

Slide 94 text

94 DIコンテナを導入するとどう変わるのか クラス間の依存関係把握が簡潔になる・アクセスコントロールがやりやすい あるクラスの機能を使うためにはコンスタントラクタで宣言する必要があるため、 依存関係が見えやすい。 不要なクラスの利用がなくなり、役割分担も明確になる。 Singletonはどこからアクセスされるのか把握が難しい。

Slide 95

Slide 95 text

95 落とし穴 ”DIコンテナ上で管理されているオブジェクト同士を、気の向くまま思うままに関連付けします。DIコン テナ上に管理されていないければ有り得ないような依存性であっても気にせず依存させるため、図8の ように依存性が網の目のようになります。” 網の目依存 DIコンテナの本当の使いどころ https://www.ulsystems.co.jp/topics/025 より引用

Slide 96

Slide 96 text

96 落とし穴 網の目依存はやりがちなのでは。 あらゆるクラスをDIコンテナに登録、自由に参照を取得する (全てがsingleton / 全てstaticクラスな事と同じ)

Slide 97

Slide 97 text

97 落とし穴 DIコンテナに登録するクラスは良く考慮する 現プロジェクトではサービスやリポジトリのRootインスタンスのみがDIコンテナに 登録可能。 Root要素から下は参照関係の解決にDIコンテナは使わない。 参照の受け渡し(ただのDI)で解決している Easyのためではない。simpleにするために使う

Slide 98

Slide 98 text

98 ライブラリ Zenject (extenject) を採用 Zenjectの固有機能はあまり使わず、他のDIコンテナライブラリに簡単に乗り換えら れるようにしている

Slide 99

Slide 99 text

99 ライブラリ •プロジェクト起動時、シーン読み込み直後の初期化の際にDIコンテナに詰める・取 得するだけ •シーン初期化以降、動的に生成されるinstance/prefabにはinjectionしていない •シーンはタイトル画面・アウトゲーム・インゲームのような粒度で分けている •基本はコンストラクタ インジェクション •mono behaviorに対してはメソッド インジェクション マイルドな使い方

Slide 100

Slide 100 text

4. その他の設計手法の紹介

Slide 101

Slide 101 text

101 • ロジックが書かれた集約・Entity・値オブジェクトなどのユニットテスト • ドメインモデルとして纏める事でテストしやすくなる • ライブラリ的な機能のテスト これらはUnityのTest Runnerを使い、jenkinsで定期実行 テスト

Slide 102

Slide 102 text

102 関数の呼び出し元を顧客、呼ばれる関数を供給者と見立てる。 顧客と供給者の間に契約が結ばれている、というメタファー。 「顧客が契約を守るなら、供給者は仕事を保証する」 具体的には? 契約による設計 オブジェクト指向入門 第2版 原則・コンセプト バートランド・メイヤー (著), 酒匂 寛 (翻訳)

Slide 103

Slide 103 text

103 下記の3つの条件を満たすようにコードを記述する 契約による設計 事前条件 関数の開始時に、関数を呼ぶ側で保証すべき条件 事後条件 関数が終了時に保証すべき条件 不変条件 そのオブジェクトが常に満たすべき条件 コードにすると? 参考URL https://developer.hatenastaff.com/entry/2016/09/01/163542

Slide 104

Slide 104 text

104 契約による設計 参考URL : 契約による設計、例外、表明の関係について個人的なまとめ https://qiita.com/hiko1129/items/f312212070716f672ff6 こういった四角形を表すクラスがあるとする

Slide 105

Slide 105 text

105 契約による設計 参考URL : 契約による設計、例外、表明の関係について個人的なまとめ https://qiita.com/hiko1129/items/f312212070716f672ff6 事前条件 ・処理の最初に引数のチェック 事前条件

Slide 106

Slide 106 text

106 契約による設計 参考URL : 契約による設計、例外、表明の関係について個人的なまとめ https://qiita.com/hiko1129/items/f312212070716f672ff6 事後条件 ・関数の最後で結果・returnする値のチェック 事後条件 ・処理の最後にreturnする値をチェック 事後条件

Slide 107

Slide 107 text

107 契約による設計 参考URL : 契約による設計、例外、表明の関係について個人的なまとめ https://qiita.com/hiko1129/items/f312212070716f672ff6 不変条件 ・このobjectが守るべき条件をチェック 不変条件

Slide 108

Slide 108 text

108 ドメイン駆動設計でも「表明(Assertion)」としてパターンが紹介されている。 現プロジェクトでは、これから充実させていくところ。主にドメインロジック をチェック。 契約による設計

Slide 109

Slide 109 text

109 小さい単位で独立してテスト可能なように作る 単体で動くように作る とても重要。最初期からこれを念頭に作る。 ゲームを最初から立ち上げずに、最小ではprefab単位で挙動のチェックが出来るように 結合度が低く、使い回しが効くパーツが作られる

Slide 110

Slide 110 text

110 アウトゲームの各画面・ポップアップなどに関して 単体で動くように作る とても重要。最初期からこれを念頭に作る。 ゲームを最初から立ち上げずに、最小ではprefab単位で挙動のチェックが出来るように MVPのView部分だけ切り出してテスト可能 View部分を単独で作り込める 現プロジェクトの一例

Slide 111

Slide 111 text

111 インゲームのviewテスト用シーン テスト項目をinspecterに表示。 ボタン押下でviewの挙動をチェックできる。 単体で動くように作る

Slide 112

Slide 112 text

112 単体で動くように作る Inspecterにボタンを表示するのはアセット「Odin」を使用 インゲームのviewテスト用シーン

Slide 113

Slide 113 text

113 prefabの構造規約 ドメイン駆動設計における集約の考え方を適用する • Prefabパーツへのアクセスはroot要素のコンポーネントを経由する • inspecter、Find()、GetComponentInChildren()等でprefabのroot要素を飛び越 えて中身の参照を取得しない Subsystem内に、 外から自由にアクセスしている 窓口を通してしかアクセスできない

Slide 114

Slide 114 text

114 prefabの構造規約 UnityにはEasyにするための仕組みが沢山ある。 しかし敢えて使わない。 デバッグ機能やモックシーンは使っても良い (EasyでOK) Subsystem内に、 外から自由にアクセスしている 窓口を通してしかアクセスできない

Slide 115

Slide 115 text

5. 実装の詳細 例外・つまづいた点・悩みごと

Slide 116

Slide 116 text

実装においての例外

Slide 117

Slide 117 text

117 DIコンテナだと困るもの UIのボタンクラスなど、UnityのHierarchyツリー上で下層に置かれるようなUIパー ツなど。 末端オブジェクトからDIコンテナに入っているオブジェクトにアクセスしたい場合。 DIコンテナでの例外パターン SoundManager.Instance.PlaySe(); こういった機能は割り切ってsingleonにしている。 例: ボタンを押したら音を鳴る

Slide 118

Slide 118 text

118 Observerパターン ・ イベントの発行 ゲーム固有のイベントを発行・管理するものもsingletonにしている DIコンテナでの例外パターン サウンド再生もイベントで表現できるが、単独で使用される事が多いため敢えて分けている Observer.Instance.Notify("XxxEvent"); どんなイベントが発行されるか? たとえばps4のトロフィー獲得表示。何かを達成したタイミングで表示されるもの

Slide 119

Slide 119 text

つまづいた点

Slide 120

Slide 120 text

120 値オブジェクト等のImmutableなクラスに変更。 ただ情報を他のレイヤーに運ぶだけのオブジェクトであれば、 DTO(Data Transfer Object)として表現。 現プロジェクトではDTOもImmutableとして作っている。 とりあえず色々Entityにされがち ドメイン駆動設計 - つまづいた点 生成後に変化しない物もEntityとして作ってしまった。 対処

Slide 121

Slide 121 text

121 Entity・値オブジェクトにドメインロジックが書かれず、サービスに手続き型で書 かれてしまう ドメインモデル貧血症 ドメイン駆動設計 - つまづいた点 OOPを意識して書く。 チームでモデリングの技量を上げる必要がある。継続的に勉強会を実施。 対処

Slide 122

Slide 122 text

122 例えばUser情報管理オブジェクトなど。 複数のEntityや値オブジェクトから出来る集約は、直接中身のエンティティ等を触り たくなる。 集約の境界が曖昧で、集約内部に直接アクセスを許してしまう ドメイン駆動設計 - つまづいた点

Slide 123

Slide 123 text

123 面倒だが、ここは必要経費と捉えてRootを通したアクセスのみ認めるように変更。 情報を伝えるためのバケツリレーのようなメソッドがたくさん出来てしまうが、ここ は我慢。 (ここもeasy vs simpleで、simpleを採用する話) 集約の境界が曖昧で、集約内部に直接アクセスを許してしまう ドメイン駆動設計 - つまづいた点 対処

Slide 124

Slide 124 text

124 集約の境界が曖昧で、集約内部に直接アクセスを許してしまう ドメイン駆動設計 - つまづいた点 理想的には全てのアクセスで集約Rootを通すようにしたい。 ただ、それだと実装が煩雑になる部分も出てくるので、部分的に許可している。 内部の値を一時的に外部から参照するだけならOK 境界内のオブジェクトは互いに参照を保持し合ってOK OK 集約内部に持つインスタンスのメソッドを、外部から実行するのはNG NG

Slide 125

Slide 125 text

125 集約の境界が曖昧で、集約内部に直接アクセスを許してしまう ドメイン駆動設計 - つまづいた点 基本的にはデメテルの法則を適用する。 クラスCのメソッドfは、次のオブジェクトのメソッドのみを呼び出すべき • Cそのもの • fで生成されたオブジェクト • fの引数で渡されたオブジェクト • Cのインスタンス変数に保持されたオブジェクト fは、上記の許されたメソッドから返されたオブジェクトのメソッドを呼び出し てはいけません。つまり友達とのみ会話し、知らない人とは会話してはいけない のです。 Robert C.Martin,花井 志生. Clean Code アジャイルソフトウェア達人の技 (Japanese Edition) (Kindle の位置No.2626-2631). Kindle 版.

Slide 126

Slide 126 text

126 集約の境界が曖昧で、集約内部に直接アクセスを許してしまう ドメイン駆動設計 - つまづいた点 ただし、デメテルの法則を適用するのは振る舞いを持つオブジェクトに対してのみ。 振る舞いを持たないデータ構造は内部を公開しても構わない。 データ構造に過ぎず、何ら振る舞いを持たないのであれば、これらは内部構造をごく自然に公開しており、デメテルの法則は 適用されません。 Robert C.Martin,花井 志生. Clean Code アジャイルソフトウェア達人の技 (Japanese Edition) (Kindle の位置No.2654-2655). Kindle 版.

Slide 127

Slide 127 text

127 ドメイン駆動設計 - つまづいた点 集約のルールを徹底するだけでスパゲティコードになる可能性はグッと下がる。 とても重要。 Subsystem内に、 外から自由にアクセスしている 窓口を通してしかアクセスできない

Slide 128

Slide 128 text

128 本来、リポジトリは集約の単位で作成される。 しかし集約内部にあるEntity単位でもリポジトリがある状態だった。 集約の内側パーツをリポジトリが持つことになってしまう。 リポジトリの粒度が集約単位ではない ドメイン駆動設計 - つまづいた点 モデリングが上手くいっておらず、集約の範囲が曖昧だったのが原因。集約の境界 をしっかり定義し、その単位で整理しなおす 対処

Slide 129

Slide 129 text

悩んでいる点

Slide 130

Slide 130 text

130 値オブジェクト - Equals()、GetHashCode()の実装について メンバ変数が少ない場合は大丈夫だが、多い場合は実装が大変。 ドメイン駆動設計 - 悩み

Slide 131

Slide 131 text

131 値オブジェクトの実装 | Microsoft Docs https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects 値オブジェクト基底クラスを作って運用する方法がMSドキュメントにて紹介され ているが、パフォーマンス面で不安は残る。 値オブジェクト - Equals()、GetHashCode()の実装について ドメイン駆動設計 - 悩み

Slide 132

Slide 132 text

132 C#9のinitキーワード、record型が来れば楽になりそう。 Unityでも早く使えますように 現状、数が少なければ手動で記述。 数が多い場合はIDEの自動生成を使うのが楽だが、メンバ変数が増えた際、忘れず に生成しなおす必要がある。 そのため、タプルを使った実装も検討中。 値オブジェクト - Equals()、GetHashCode()の実装について ドメイン駆動設計 - 悩み 対処

Slide 133

Slide 133 text

133 Equals()が自動的に実装されるが、継承が出来ない。 パフォーマンスの懸念も。 値オブジェクトの実装について - structを使うとどうなるのか? このような理由で、現プロジェクトではクラスを使っている ドメイン駆動設計 - 悩み

Slide 134

Slide 134 text

134 大きな集約をどこまで許容するか • 分割した場合、整合性・不変条件をどう保っていくか ある概念が情報を大量に保持しており、どれも密接に関係していて切り離すのが難 しいケースがよくある。 ドメイン駆動設計 - 悩み

Slide 135

Slide 135 text

135 大きな集約をどこまで許容するか • 分割した場合、整合性・不変条件をどう保っていくか 参考 DDDで複数集約間の整合性を確保する方法(サンプルコードあり)[ドメイン駆動設計] https://little-hands.hatenablog.com/entry/2021/03/08/aggregation トランザクション単位で分割する手法があるが、 UnityのクライアントコードではDBが絡むトランザクションが関係ない事も多い。 その場合は大きいままでも良い? モデリングが上手く行っていないのかも? まだ明確な答えは出ておらず、いったん大きいまま扱っている。 ドメイン駆動設計 - 悩み

Slide 136

Slide 136 text

136 ドメイン駆動設計の導入直後、 ゲームに必要な汎用機能の実装をどのレイヤーに置くのか悩んだ。 特にUnityの機能を使った、Viewと関連する部分。 Unityでの実装がどのレイヤーになるのか ? ドメイン駆動設計 - 悩み

Slide 137

Slide 137 text

137 ユースケースの実現(アプリケーションが提供する機能)と捉え、現状アプリケーション層で 実装されている。 しかしドメインと関係の無い基盤機能と捉える方が適切なため、実装はインフラストラク チャ層、その機能を使う処理がアプリケーション層にある、という形の方が適切だった Unityでの実装がどのレイヤーになるのか ドメイン駆動設計 - 悩み 汎用機能 ・アセットバンドルを読み込む ・ポップアップを表示 ・シーンの読み込み、画面遷移させる、など ・音を出す ?

Slide 138

Slide 138 text

138 SoundManager.Instance.PlayBgm(); 実装クラス指定アクセスになる。 interfaceを使った依存関係逆転の原則も使えない。 ServiceLocatorを使ってsingletonを纏める手法を検討中。 しかし一部singletonになっているものは、気軽にレイヤーを変更できない ドメイン駆動設計 - 悩み

Slide 139

Slide 139 text

139 適切なレイヤーはどこなのか 未だに悩む。 とはいえドメインとそれ以外を分離する事が最も大切。 そこ以外は厳密に考えなくても良いのかも? ドメイン駆動設計 - 悩み

Slide 140

Slide 140 text

6. まとめ

Slide 141

Slide 141 text

141 ドメイン駆動設計 良いところ • DDDを導入する事でチーム内で統一された設計思想を共有できるようになった • 悩んだ時 / 説明する際に参照できるリソースが豊富 • データと振る舞いがセットで記述される事でコードの保守性・可読性が向上する • ドメインへの理解が推奨される • 一度習得すれば所属プロジェクトが変わっても様々な面で力になる • UnityでなくてもOK。特定の技術にあまり左右されない

Slide 142

Slide 142 text

142 ドメイン駆動設計 • 導入のハードルはなかなか高い • 継続的な学習は必要 • チーム内に浸透するまで時間がかかる • まずはプログラムの実装パターン適用からでも効果はある • それでも各実装パターンへの正確な理解は必要 • ドメイン駆動の名の通り、ドメインへの理解を深める事が本質 • しかし形から入る事で分かる事も多い 大変なところ・注意したいところ

Slide 143

Slide 143 text

143 DIコンテナ 良いところ • singletonの代わりとなる • 多態性を使ったモック作成がやりやすい • 依存関係を管理しやすい 大変なところ • 最初の学習コスト • 結局やってる事は単純ながら、概念を掴むのが難しい

Slide 144

Slide 144 text

144 ドメイン駆動設計 と DIコンテナ 2つは非常に相性が良い。 レイヤードアーキテクチャを実践するうえで役に立つ。 まとめ 双方とも、アプリの種類や使用している技術に左右されずらく、 汎用的な知識・手法として役に立つ。 設計するうえで、非常にオススメです。

Slide 145

Slide 145 text

No content

Slide 146

Slide 146 text

146 エリック・エヴァンスのドメイン駆動設計 https://www.amazon.co.jp/dp/B00GRKD6XU ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 https://www.amazon.co.jp/dp/B082WXZVPC 実践ドメイン駆動設計 https://www.amazon.co.jp/dp/B00UX9VJGW ドメイン駆動設計 モデリング/実装ガイド https://booth.pm/ja/items/1835632 Clean Code アジャイルソフトウェア達人の技 https://www.amazon.co.jp/dp/B078HYWY5X Clean Architecture 達人に学ぶソフトウェアの構造と設計 https://www.amazon.co.jp/dp/B07FSBHS2V オブジェクト指向入門 第2版 原則・コンセプト https://www.amazon.co.jp/dp/4798111112 参考書籍

Slide 147

Slide 147 text

147 DDDスコアカード https://www.informit.com/articles/article.aspx?p=1944876&seqNum=2 Domain Driven Design(ドメイン駆動設計) Quickly 日本語版 https://www.infoq.com/jp/minibooks/domain-driven-design-quickly/ DDD Reference - Domain Language https://www.domainlanguage.com/ddd/reference/ DDDリファレンス 定義とパターン概要 (鋭意修正中, CC-BY) https://zenn.dev/takahashim/books/fb4cdc32f8e95c ドメイン駆動設計をゲーム開発に活かす https://www.slideshare.net/masuda220/ss-111011089 ゲームで学ぶ「役に立つ」ドメインモデルの考え方 - Qiita https://qiita.com/MinoDriven/items/7b4609d0bc6717769060 DDD難民に捧げる Domain-Driven Designのエッセンス 第1回 ドメイン駆動設計とは https://www.ogis-ri.co.jp/otc/hiroba/technical/DDDEssence/chap1.html より引用 Facade Pattern as the way to implement Aggregate http://rubyblog.pro/2017/05/facade-pattern 参考資料URL

Slide 148

Slide 148 text

148 [DDD]ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か https://qiita.com/little_hand_s/items/ebb4284afeea0e8cc752 DIコンテナの本当の使いどころ https://www.ulsystems.co.jp/topics/025 48. GoFデザインパターンとDI (前編) w/ twada https://fukabori.fm/episode/48 契約による設計の紹介 - Hatena Developer Blog https://developer.hatenastaff.com/entry/2016/09/01/163542 契約による設計、例外、表明の関係について個人的なまとめ https://qiita.com/hiko1129/items/f312212070716f672ff6 値オブジェクトの実装 | Microsoft Docs https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/implement-value-objects DDDで複数集約間の整合性を確保する方法(サンプルコードあり)[ドメイン駆動設計] https://little-hands.hatenablog.com/entry/2021/03/08/aggregation 参考資料URL