yotiky Tech Blog

とあるエンジニアの備忘録

複数のComponentのイベント関数の実行順

Unity 基礎シリーズ 目次

目次

検証環境

Unity:2017.4.5f1

Componentが複数の場合の実行順

基本的にHierarchyやInspectorの配置順に関係なく、Componentの実行順は保証されない。

1つのGameObjectに複数のComponent

Editor上でComponentをAddした順番を記憶してて、最後に追加したComponentから逆順に実行されているように見える。上から順にComponentを貼っつけていった場合、綺麗に降順になる。

f:id:yotiky:20181120132245p:plain

  1. Component3.Awake
  2. Component2.Awake
  3. Component1.Awake
  4. Component3.Start
  5. Component2.Start
  6. Component1.Start
  7. Component3.Update
  8. Component2.Update
  9. Component1.Update

次に下のように、Component3、Component2、Component1の順に貼っ付けると結果はこうなる。並び順でもなく追加した順なのがわかる。

f:id:yotiky:20181121011040p:plain

  1. Component1.Awake
  2. Component2.Awake
  3. Component3.Awake
  4. Component1.Start
  5. Component2.Start
  6. Component3.Start
  7. Component1.Update
  8. Component2.Update
  9. Component3.Update

だがしかしUnityを再起動したりSceneをUnload/Loadしようものなら、手中に収めていた実行順がたちまちランダムになる。1つのGameObjectについている複数のComponentはおそらく下から順に呼ばれるようになる。この辺の挙動を紐解く。

Componentは何順に実行されるのか

InspectorをDebug表示にするとInstanceIDが表示されるのだが、Componentの呼び出し順はこのIDの昇順になってそうだ。問題なのはこのIDはシリアライズされないので、Unityの再起動やSceneの読み込みなどのタイミングで一定のロジックを通って採番されなおすことになる。

こちらは上から順にComponentを貼っつけた状態。新たにくっつけたComponentは、マイナス方向にある程度幅を取った数字が割り振られていく。IDの昇順なので、このまま実行すればComponent1、Component2、Component3の順に呼び出される。

f:id:yotiky:20181122005354p:plain

  1. Component1.Awake
  2. Component2.Awake
  3. Component3.Awake

続いて、1度SceneをUnloadしてLoadし直す。するとプラス方向に下から上へIDが採番され直しているのがわかる。仮にランダムな順番にComponentをくっつけた場合でも下から順に採番され直す。この例だと実行結果は先程と変わらない。

f:id:yotiky:20181122005416p:plain

  1. Component1.Awake
  2. Component2.Awake
  3. Component3.Awake

1つのGameObject内であれば採番され直したタイミングで下から順に呼び出されるようになる。たぶん。
これが編集中は新たに貼っつけたComponentとIDの順番があべこべ状態になって実行順がランダムになると錯乱する原因かも。

ツリー状のGameObjectの場合の実行順

以前の記事では他への参照のありなしや、他のGameObjectを使ってみたりして順番に変化が起きるか検証したりもしたが影響がないので分けずに書く。

こちらも上から順にComponentを貼っつけていった場合、そのまま実行すれば綺麗に降順になる。

f:id:yotiky:20181120132228p:plain

  1. GameObject3-1-1.Awake
  2. GameObject3-1.Awake
  3. GameObject3.Awake
  4. GameObject2-1-1.Awake
  5. GameObject2-1.Awake
  6. GameObject2.Awake
  7. GameObject1-1-1.Awake
  8. GameObject1-1.Awake
  9. GameObject1.Awake

次に孫、子、親の順で下からつけた場合の結果。親、子、孫の順に呼び出される。

  1. GameObject1.Awake
  2. GameObject2.Awake
  3. GameObject3.Awake
  4. GameObject1-1.Awake
  5. GameObject2-1.Awake
  6. GameObject3-1.Awake
  7. GameObject1-1-1.Awake
  8. GameObject2-1-1.Awake
  9. GameObject3-1-1.Awake

そしてUnityを再起動したりSceneをUnload/Loadしようものなら、手中に収めていた実行順がたちまちランダムになる。 今度は先程のように予測可能な状態ではない。一度ランダムになると変更を一切加えなければUnityを再起動してもシャッフルされないので、何かしらの採番ルールはありそうではあるが、凡人には到底予測不可能な値で採番される。

こちらはComponentを追加した直後のID。実行すれば降順で呼び出される。

name InstanceID
GameObject1 -26518
GameObject1-1 -26790
GameObject1-1-1 -26980
GameObject2 -27150
GameObject2-1 -27386
GameObject2-1-1 -27552
GameObject3 -27746
GameObject3-1 -27980
GameObject3-1-1 -28174

続いて、1度SceneをUnloadしてLoadし直す。実行結果はランダムと言っても過言ではない。

name InstanceID
GameObject1 13096
GameObject1-1 13160
GameObject1-1-1 13274
GameObject2 13222
GameObject2-1 13308
GameObject2-1-1 13124
GameObject3 13292
GameObject3-1 13216
GameObject3-1-1 13200
  1. GameObject1.Awake
  2. GameObject2-1-1.Awake
  3. GameObject1-1.Awake
  4. GameObject3-1-1.Awake
  5. GameObject3-1.Awake
  6. GameObject2.Awake
  7. GameObject1-1-1.Awake
  8. GameObject3.Awake
  9. GameObject2-1.Awake

まとめ

InstanceIDはGameObjectの一意な識別子として扱われることがあるIDだが、例に上げたように実行時に変更されることはないが、SceneのLoadやUnityの再起動で変わる可能性がある。また、GameObjectとして書かれてることが多いが、GetInstanceIDメソッドは実際はComponentクラスが持っており、GameObjectに対して呼び出した場合はTransformのIDが返される。
Componentの実行順は予測できないので保証されない(常に変わる可能性がある)として理解しておく必要がある。 それでも制御したい場合は、「Script Execution Order」というものもある。1

参考

Scriptのイベント関数の実行順と実行可否

Unity 基礎シリーズ 目次

編集履歴

目次

検証環境

Unity:2017.4.5f1

主な関数の順番

  1. Awake : one time
  2. OnEnable : each time object is enabled
  3. Start : one time
  4. Update : iteration
  5. OnDisable : each time object is disabled
  6. OnDestroy : one time

GameObjectとComponentがactive/non-activeの時に呼ばれる関数

Awake/OnDestroyは、GameObjectに紐づき、他はComponentに紐づくっぽい。
AwakeはGameObjectが有効になった時に最初に1度だけ呼ばれる。Componentに紐づくその他の関数は、GameObjectの初期化であるAwakeをトリガーに呼ばれるようになる。
activeかどうかは自身のGameObject(activeSelf)に加え、親の影響(activeInHierarchy )も受ける。

    bool isActiveFirst;
    void Awake()
    {
        Debug.Log("Awake");
    }
    void OnEnable()
    {
        isActiveFirst = true;
        Debug.Log("OnEnable");
    }
    void Start()
    {
        Debug.Log("Start");
    }
    void Update()
    {
        if (isActiveFirst)
        {
            isActiveFirst = false;
            Debug.Log("Update");
        }
    }
    void OnDisable()
    {
        Debug.Log("OnDisable");
    }
    void OnDestroy()
    {
        Debug.Log("OnDestroy");
    }

Initial state

  • GameObject : non-active / Component : non-active
    • not called
  • GameObject : non-active / Component : active
    • not called
  • GameObject : active / Component : non-active
    1. Awake
  • GameObject : active / Component : active
    1. Awake
    2. OnEnable
    3. Start
    4. Update

Activate

  1. GameObject : non-active / Component : non-active
    • not called
  2. GameObject : active / Component : non-active
    1. Awake
  3. GameObject : active / Component : active
    1. OnEnable
    2. Start
    3. Update

Disable/Enable

  1. GameObject : active / Component : active
    1. Awake
    2. OnEnable
    3. Start
    4. Update
  2. GameObject : active / Component : non-active | GameObject : non-active / Component : active
    1. OnDisable
  3. GameObject : active / Component : active
    1. OnEnable
    2. Update

Destroy

  1. GameObject : active / Component : active
    1. Awake
    2. OnEnable
    3. Start
    4. Update
  2. GameObject : destroy
    1. OnDisable
    2. OnDestroy

StartのCoroutine(コルーチン)を使った場合に呼ばれる順番

Coroutineの再開はUpdateが呼ばれた後に実行される。

    IEnumerator Start()
    {
        Debug.Log("Start1");
        yield return null;
        Debug.Log("Start2");
    }

    int counter = 0;
    void Update()
    {
        if (counter < 3)
        {
            counter++;
            Debug.Log("Update" + counter);
        }
    }
  1. Start1
  2. Update1
  3. Update2
  4. Start2
  5. Update3

StartのCoroutine(コルーチン)完了後にUpdateを開始する

CoroutineはGameObjectが有効な時に実行され、Coroutineが開始された後GameObjectが無効になるとCoroutineも無効になる。CoroutineはUpdateと異なり、1度開始されるとComponentの有効/無効とは関係なく動作する。

    IEnumerator Start()
    {
        var i = 1;
        Debug.Log("Start" + i);
        enabled = false;
        foreach (var x in Enumerable.Range(0, 3))
        {
            i++;
            Debug.Log("Start" + i);
            yield return null;
        }

        enabled = true;
        i++;
        Debug.Log("Start" + i);
    }

    int counter = 0;
    void Update()
    {
        if (counter < 3)
        {
            counter++;
            Debug.Log("Update" + counter);
        }
    }
  1. Start1
  2. Start2
  3. Start3
  4. Start4
  5. Start5
  6. Update1
  7. Update2
  8. Update3

イベント関数以外の関数

独自に実装したメソッドの呼び出しは、GameObject/Component の有効/無効とは関係なく実行できる。これはAwakeが呼ばれていないGameObjectやComponentでもメソッドの呼び出しは可能であることも意味する。

f:id:yotiky:20181120132303p:plain

    public Script4Sub script;
    bool done;
    void Update()
    {
        if (!done)
        {
            done = true;
            Debug.Log("Update");
            script.WriteLog();

            Debug.Log("- GameObject : non-active / Component : active");
            script.gameObject.SetActive(false);
            script.WriteLog();

            Debug.Log("- GameObject : non-active / Component : non-active");
            script.enabled = false;
            script.WriteLog();
        }
    }
    public void WriteLog()
    {
        Debug.Log("GameObject is active ? :" + gameObject.activeSelf);
        Debug.Log("Component is active ? :" + enabled);
    }
  • GameObject : active / Component : active
    • GameObject is active ? : True
    • Component is active ? : True
  • GameObject : non-active / Component : active
    • GameObject is active ? : False
    • Component is active ? : True
  • GameObject : non-active / Component : non-active
    • GameObject is active ? : False
    • Component is active ? : False

参考

Unityと協調するためのアーキテクチャ『MVP4U』

これまでいくつかの記事でアーキテクチャデザインパターンを見てきた。Unity独自の事情なども徐々に咀嚼し、ある程度動きそうなアーキテクチャを開発チームにアウトプットできたので、ここらで共有しておこうと思う。まだ適用し始めた段階なので、今後いくつかのプロジェクトで試してみて内容を精査していければと考えている。

編集履歴

  • 2018/11/28 サンプルプロジェクトを掲載

目次

序章

UWP / Web / UnityのMVx

MVxはView を分離するためのアーキテクチャデザインパターン。いくつかのプラットフォームにおいてどういう構成となるか比較してみる。

f:id:yotiky:20181116001505p:plain

それぞれのViewを見ていくと、

UWP(WPF)は、XAMLというマークアップ言語の部分をViewとして捉えがちだが、Viewの裏に持つFrameworkとMVVM Frameworkが強力な土台となってなりたっている。ただ、実装者から見ると、XAMLとそれをカスタマイズする機能を実装すれば良い設計となっているので、主にXAML≒Viewとして扱われる事が多い。

Web(JavaScript)は、大きくフロントエンドとバックエンド(サーバーサイド)でMVxの構成を取る。その上でさらにフロントエンド内でもMVxを取る二重構成。
フロントエンドに注目した場合、HTML+CSSが極めてピュアなView(フロントヤード)、JavaScriptによる機能実装部分がView以外(バックヤード)ということができる。

Unityは、コンポーネント指向、つまり機能を部品として実装し、組み合わせることで全体を構成する設計思想を取っている。コンポーネント指向に対し、"Viewをどう捉えるか"が難しいという問題を持っている。

UnityのMVPを考える

UnityはUWP(WPF)とWeb(JavaScript)の中間のような構成になっている。
UWP(WPF)ほど実装者に露出する部分が高度に設計された作りになっていないし、そうするための強力なFrameworkも整っていない。また、Webのフロントヤードほど「ピュアなViewとそれ以外」のような設計がされているわけでもない。このため、「Unityのコンポーネント指向に歩み寄りつつ、如何にViewとして実装者が扱いやすい形を定義するか」が肝となる。

MVP4U (MVP for Unity)

f:id:yotiky:20181116002113p:plain

Unityでは、Rxを組み合わせてMVPを構築することが流行っており、PresenterはViewとModelを参照し、逆方向には戻り値やRxで依存関係を一方向に担保する。
MVP4Uでは、Viewの部分に「Componentを調和・調整する役目」として"FrontOrchestrator"を定義する。詳細は次の通り。

f:id:yotiky:20181116002130p:plain

Hierarchyでは、最上位にあたるRootと一階層目にParent、その配下にそれぞれ3DモデルなどのGameObjectが配置される。
RootはSceneに唯一のScenePresenterを持ち、ParentではViewとPresenterをそれぞれ1つ持つ。ScenePresenterには下位のPresenterをすべて登録し、エントリーポイントとしてPresenterの初期化とその順序を制御する役割を持つ。

MVP4UにおけるViewの目的は、「Componentを束ね、Presenterが必要とする情報、操作を公開する」こと。紐づくParent配下で、Script側から操作する必要があるComponentを参照する。自身に機能として処理は持たない。これを許容すると、Viewは使い勝手が良い立ち位置なので処理が集約してしまい、コードが肥大化することが予想される。機能は各Componentに持たせることで適切な責務を分担する。

FrontOrchestratorは、自身のComponentで処理が完結しないComponent同士を連携させたい時に調整する役割を持つ。
MVPを導入し始めた頃に、「Viewとして公開し、PresenterでView同士を紐づける」ことをやりがちだがこれが該当する。PresenterはModelとの橋渡しなので、View同士のコネクションのために無理にPresenterを経由する必要はないので、ここでその辺を吸収するわけだ。

XR

UWP/Web/Unity(XR)のView

f:id:yotiky:20181116002155p:plain

UnityのとくにXRアプリにおいては、MVx パターンが適用しにくい。なぜか。
他のアプリ(UWP/Web/uGUIなど)では"画面"という単位が存在し、画面遷移することでアプリのシナリオが進行する。この"画面"を単位としてViewを定義することで、ViewとViewの境目が明確になる。一方、XRには"空間"しか存在しない。1つの空間を区切る単位が存在しないため、無理に区切ろうとすると結局1つの空間が1つのViewになってしまう。

シナリオの進行

UnityはGame Loopでプログラムが駆動している。1 ループの一巡で、必要な処理が開始して終わるまでがFrameと言う単位になり、ループを繰り返してタイムラインを形成する。
f:id:yotiky:20181116002222p:plain
f:id:yotiky:20181116002226p:plain

アプリを作成するには、Game Loopとは別にアプリのタイムライン、シナリオを組み立ててそれを進行させる必要がある。また画面があるアプリでは、"画面のフレーム(枠)"に対し「状態を入れ替える」ことで遷移するイメージができるが、画面のないXRでは、空間に存在する物の「状態を変化させる」イメージが近いのかも知れない。

XRでの適用例

構成

MVP4UをXRの事情に合わせて適用する例を紹介する。

f:id:yotiky:20181116002321p:plain

シナリオの進行を管理する部分を、アプリのエンジンとして通常のMVP構成とは別けて考える。
シナリオの章となるChapter2を遷移する単位とし、ScenarioはChapterのつながりが書かれた台本となる。シナリオの進行役にはDirectorを配置する。ディクレターに渡す台本を替えると異なるストーリーが展開される、みたいな作り。状態をチャプターを跨いで共有したい場合は、シナリオの文脈ということで、SenarioContextに記録して共有する。
Hierarchyでは、RootにあたるSpaceと、ParentにあたるChapterを持つことになる。HierarchyのChapterはScenarioのChapterとほとんどの場合は1:1の関係である。

フロントヤードでは、Unityのお作法に近いかたちで実装していけば良い。
ComponentやFrontOrchestratorは、Startで初期化したり、gameobjectを直接いじったり、Observableが必要なら使えば良い。FrontOrchestratorがプレーンなクラスで実装できるならその方が良いが、必要があればMonoBehaviourを使っても良いだろう。フロントヤードで初期化の順序を制御したくなったらViewで纏めて調整することもできる。

Chapterの定義

HierarchyのChapterの配下には、各チャプターで登場する3Dモデルが定義される。
空間において同じ登場人物(3Dモデル)は唯一であり、「状態を入れ替える」のではなく「状態を変化させる」と考えると自然にも思える。また、 GameObjectを何かで区切ってそれを跨ぐことは、Unityに対して不自然となるため調和が乱れる。(コストがかかる)Chapterは、"同じ登場人物(3Dモデル)が被らないことを前提に区切れる単位"が最適解となりそう。

f:id:yotiky:20181116002436p:plain

エンジンとして定義したディレクターが、シナリオのチャプターを切り替えることで、アプリのストーリーが展開していく。

f:id:yotiky:20181116002453p:plain

Engineの初期化とScenarioの進行

このあたりはサンプルで実装した詳細なので参考程度に。
エンジン部分の初期化はエントリーポイントであるScenePresenterが全体初期化時に一緒に担当する。

f:id:yotiky:20181116002515p:plain

Scenarioの進行では、各Presenterと対になるUseCasesがScenarioDirectorに遷移を依頼。
ScenarioDirectorは、遷移実行前後のフックポイントを持っているので、それをUseCases / Presenterへと伝播させ、それぞれ必要な遷移前処理 / 遷移後処理を実行できるようになっている。

f:id:yotiky:20181116002529p:plain

遷移へのアプローチ

画面遷移の手段として、①1つのSceneで処理する方法、②Sceneを切り替える方法、③対象をPrefab化しInstantiateして入れ替える方法、を耳にする。
①は、これまでに説明してきたものとなる。②は、ロード時間やデータの共有の手間をペイできるほどのストーリーがまだ出てきていないので保留しているが、HierarchyのRoot直下に一層追加されて、バックヤードも対になる層を持たせてその層で新たなContextが共有されるようなイメージではある。③は、ステージを完全に分断(=3Dモデルが被らない)できるストーリーであれば、バックヤードはそのまま応用が効きそうだし、有効そうである。だが、こちらもまだ使うほどの場面には出くわしていない。

サンプルプロジェクト

構成

最後に参考までにサンプルで実装したアプリの構成を載せておく。
レイヤーの定義を決めかねているので、MVP+αな感じでパッケージだけ分けてある。UI以外は一旦Modelとしてまとめちゃってる状態。
ここではDIもテストも考慮していないので、疎結合のためのInterfaceは切っておらず、規約としてしか使っていない。(IViewくらい)
Model内にModelがあるが、サンプルではドメイン相当となる処理がほとんどないので使っていない。UIには、Sound系やAnimation系なんてのも出てくるのかなとぼんやり。

f:id:yotiky:20181116002544p:plain

github.com


  1. イベント関数の実行順 - Unity マニュアル

  2. サンプルではenumとして各章を定義した

オブジェクト指向とVirtual Reality

オブジェクト指向の説明で「現実のモノを表現する」とそれに対するコメントを見て考えにふけったメモ書き。

バーチャルリアリティとは?

日本バーチャルリアリティ学会によると、

 バーチャルリアリティのバーチャルが仮想とか虚構あるいは擬似と訳されているようであるが,これらは明らかに誤りである.バーチャル (virtual) とは,The American Heritage Dictionary によれば,「Existing in essence or effect though not in actual fact or form」と定義されている.つまり,「みかけや形は原物そのものではないが,本質的あるいは効果としては現実であり原物であること」であり,これはそのままバーチャルリアリティの定義を与える.

現実のものと表面的に似ているかどうかではなく、本質的、効果的に人がものとして捉えられるかってこと。

写実的な絵画ではなく抽象絵画。一見それに見えない。だが本質を捉えて表現されているため、それは紛れもなくそれである。

オブジェクト指向」は「Virtual Object」=「実質的なモノ」なんじゃなかろうか。 人がその"モノ"を"モノ"として捉えるエッセンスを持たせる、持つように組み立てて表現すること。

オブジェクトの「名前」によって"モノ"を決定し、「属性」と「振る舞い」で"モノ"を捉えるための実質的なエッセンスを定義する。

上手く表現できないと鑑賞する人に伝わらないし、そのオブジェクトに対する没入感を得られない。

Reduxの概要

Unityのアプリケーションのアーキテクチャを考えるのに、参考になるかと思ってJavaScript界隈の最近の流行を調べて見たので覚書。
JavaScriptiOS界隈もそんなに詳しくないので、入門記事やそのサンプルなど眺めながら理解した内容です。

目次

TL;DR

ReduxはJavaScriptアプリのための、状態を管理するためのフレームワーク
View、表示に関する責務はそれに特化したフレームワークに委ね、組み合わせて利用する事が多い。代表的なのはReact。

Redux + α

iOS ReSwiftを元にReduxを読み解く

一番解りやすかったのがiOS向けにReduxを説明した記事だったのでこのような形に。1 斜体はReSwift特有の事項。

  • State
    • Data, POCO
    • Root(e.g. AppState)を起点としたツリー構造
    • Reducerに渡すものはStateTypeを継承してマーキング
  • Action
    • 処理種別と入力パラメータを持つData, POCO
    • 処理手続きのための申請書みたいなイメージ
    • Actionを継承してマーキング
    • 「具体的にどう変化させるか」という結果ではなく、UseCase(何を指示するか)で定義する
  • Reducer
    • Actionを受理しStateを更新する
    • StateとReducerは対になる(つまりツリー、reducer composition)
    • ActionはすべてのReducerにBroadcastされる(伝搬)
    • 各Reducerは自身で判断し、対のStateを変更する
    • (state, action) => state を満たす副作用のない純粋関数
      • 引数の値を変更しない
      • API呼んだり、ページ遷移などダメ
      • 毎回結果の変わるものを使わない(現在日時とか乱数とか)
    • Root(e.g. appReduce)を起点に下位のReducerにブロードキャスト
  • Store
    • シングルトン
    • データを保持する(「State+Reducer」のツリー)
    • Dispatchメソッドを提供する
    • 状態の変更通知するため、リスナー(View)を登録するメソッドを提供する
      • Dispatchの結果、登録されたリスナーに通知される
      • この時Storeから現在のStateを取得することもできる
  • Dispatch
    • Actionを引数にStoreに手続きを依頼する窓口
  • ActionCreator
    • Actionを構築し生成するだけ

Reduxの処理の関連図。 f:id:yotiky:20181018225458p:plain

個人的なイメージはこんな感じ。
f:id:yotiky:20181018225444p:plain

React

  • Viewを表示することに特化したライブラリ
  • 仮想DOM(Virtual DOM)
    • 生のDOM(HTMLのインスタンス)ではなく、対となるツリーな構造体
    • StateAとStateBのHTML的なdiffを取り差分を埋めるpatchを生成し、patchを適用することで状態を遷移させる(表示を変化させる)
    • 生のDOMを操作する文化から脱却

Redux(JS) 上級

  • Middleware
    • ActionやReducerで必要になりそうな副作用のある処理や非同期処理はここに押し込める

イメージはこんな感じになるのかな。
f:id:yotiky:20181018225432p:plain

まとめ

サーバーサイドはViewとそれ以外のModelって発想でModelを小さく表現してその実巨大なわけだけど、フロントサイドもMiddlewareと言うロジックを書く領域小さく表現してるが結構身が詰まってそうだなぁと。

HTMLはRootがあってそこからTreeがあるってのが大前提にあり、ReduxもReactもそれを踏まえた上での仕組みですね。
UIデザインの構成もコンポーネント志向により親子構造を構築する流れもありそうだし、全体通して方向性が一致してていいのではと思う。

一方UnityはHierarchyがツリー構造ではあるものの、全然枝葉の違う所同士で割と縦横無尽に関係を築きたがるし、相互に関連するものも多いので、Tree縛りでUI、状態を構成するとなると相当に大変かなと思う。Unityそのものを喰らうぐらいの思想とパワーがないと厳しかろうて。
部分的に親子構造を持ちつつ親が子を操作するのではなく、伝播させるって仕組みくらいは参考になりそうなならなそうな、使い所は考えたい。

参考

IoCとService LocatorとDIの関係

目次

概要

連載的に書き始めたアーキテクチャのお話で、IoC、DIという単語が出てきたので軽くまとめておく。

制御の反転と実現

登場人物はIoC、Service Locator、DI。すべてパターンと呼ばれるものの一種。

IoC」は「Inversion of Control」の略で、日本語だと「制御の反転」。
インターフェイスを間に挟み仲介することで、具象クラスが何になるかは外部で決めるようにするというパターン。
これ自体は汎用的な名前で定義されており、続く「Service Locator」、「DI」がそれを実現するためのパターンとして定義される。

f:id:yotiky:20180928053641p:plain

「Service Locator」は、SingletonなFactoryに、生成したインスタンスをキャッシュする機能を備えたようなもの。
事前にService Locatorに使う具象クラスを定義し、使う人がService Locatorに問い合わせると良しなにインスタンスを返してくれる。それが何の具象クラスかは知らない。

f:id:yotiky:20180928053648p:plain

「DI」は、「Dependency Injection」の略で、日本語だと「依存性の注入」。
呼び出す側が使う人に具象クラスを渡してあげるパターン。使う人はインターフェイスで受け取るので、それが何の具象クラスかは知らない。

f:id:yotiky:20180928053656p:plain

Injection(注入する方法)はいくつかあって、コンストラクタ、セッター(プロパティ)、フィールド、メソッドなど。インターフェイスってのもあるらしい。

「DI」自体はフレームワークがなくても実装可能ではある。
しかし「DI」には、「DI コンテナ」が多く存在する。「DI パターン」を実現するために有効なフレームワークだ。
「DI コンテナ」により、先の多様なInjectionに対応したり、Injectionの処理自体を裏で引き受けてくれたり、インスタンスのライフサイクルを管理したりと手厚いサポート受けられるようになる。前述のInjectionに対しては、属性をマーキングすることで実現する方法も手にれられる。

f:id:yotiky:20180928055240p:plain

「DI」自体は、外から注入するというだけのパターンであるが、「DI コンテナ」は「Service Locator」の役割も包含してしまっているように思える。
そりゃみんな「Service Locator」は置き去りにするわけだ。
ついでに、機能的に多くを必要としないからか「Service Locator コンテナ」は見かけたことがない。「IoC コンテナ」と言ったときは間違いなく「DI コンテナ」のことだろう。

Injectionは、コンストラクタが推奨されているようだ。インスタンスが生成された時には、必要な初期化が終わってるべきだというわけ。
ただし、Unityに限っては多くの生成処理がUnity側にあるので、コンストラクタが使えない。そのため次点でメソッドとなるらしい。

「Service Locator」と「DI」の使い分けについては、2004年と古いがMartin Fowlerの考察が載っていて興味深い。1

参考にさせて頂いたサイト

Unityを取り巻くアーキテクチャ 第2部 「DDDのアーキテクチャ」

アーキテクチャは読み手の解釈に委ねられる部分が多かったり、提唱されたあとも進化や変化があったりするので、あくまで現時点でそれぞれどう捉えてるかの覚書と考察を。雑ならくがき、雑がき。

※編集したり更新したりする予定です。

目次

概要

つらつらと書き始めたら思いの外風呂敷が広がってしまったので、「MVxとLayerd Circleについて」、「DDDについて」、「Unityで実装する話」の3部構成くらいに収まるといいなぁと言った所です。
今回の記事は第2部「DDDについて」にあたります。 第1部を読まれていない方は、全体における導入部分もありますのでそちらも読んで頂けると嬉しいです。

DDD

「DDD」は、「Domain Driven Design」。「X Driven "Development"」ではないので注意してほしい。開発プロセスではなく、ドメインを中心に"設計を成熟させる"ための思想、パターン、HowTo。言い換えると「どのように駆動していくと設計が成熟するか」の方法論。アーキテクチャの構成、つまり『アプリケーションの静的側面を図式化して説明する』だけの話ではない。

「DDD」は、2003年にEric Evansが『Domain-driven design』という書籍で提唱した。その後、2013年にVaughn Vernonが『Implementing Domain-Driven Design』という書籍で「DDD」を解説している。前者を「DDD」や「原典」、後者を「IDDD」などと呼ぶ。
アーキテクチャについては、この2つは異なるアーキテクチャを持ち出しているらしい。ここからは原典のお話し。

Domain

ドメイン」とはなんぞや、これがとても分かりづらく厄介。
アプリケーションが対象とする業務領域、業務の関心事なんて言われるが、それって一体何って話。

一部の業務アプリケーションを例に取ると少し想像しやすいと思う。会計なら"会計"を成す一連の処理。
"どこから入力"されようが、入力されたものが正しければ会計処理自体には影響がない。また、データが"どこから取得"できて、"どこに保存"されるのかも、会計処理自体には影響がない。さらに、会計処理を"ソフトウェアとして具現化"するために必要な処理もまた会計処理自体とは切り離される。

ソフトウェアであろうがなかろうが、とにかく正しくインプットされ、必要なデータが取れたり、保存されたりされていれば会計処理自体は論理的に動く、つまりそこが「ドメイン」。

この切り口で行くとゲームも同じように、インプットを分離し、データへのアクセスを分離し、スマホだろうが、紙のボードだろうが、机上だろうが、動くロジカルな部分が「ドメイン」となる。インゲーム/アウトゲームなんて分け方もあるが、どちらも関心事には違いないし、アウトゲームを動かさないとインゲームが動かない事もあるのでどちらも「ドメイン」。ログは「ドメイン」から外れるだろうし、認証はゲームを動かす根底には関係ないものの必要な要素、ということで「サブドメイン」なんてロールも用意されてるらしい。

ドメインを駆動する基盤

で、この「ドメイン」にはエキスパートがいる。いなかったりもするが。
特に業務系で、先に上げた会計や金融などは業務への深い知識が必須となる。
(...ゲームを「ドメイン」として設計してる人はいるんだろうか。)

過去に会計パッケージのプロジェクトに携わった事があるが、50代、60代のおじちゃまがExcel方眼紙に「もしXXがOOだったら」といった日本語プログラミングを書いて、若い人がコードに起こすなんてことを経験したこともあり、一朝一夕で「業務領域」を理解するのは難しいなと思わせることもあった。

DDDでは、このエキスパートと、ユーザー、プログラマ、設計、ソースコードに至るまで、アプリケーションを取り巻く環境において「モデル」を共通言語として透過的に「ドメイン」が扱われる。つまりドメインをモデルで表現する。
エキスパートが話す「ドメインモデル」は、ソースコードレベルで同じ表現として実装される。人および設計、さらにコード間で透過的に同じ意味として扱われることで、相互に関係、関心を維持し、設計を成熟の道へ駆動するための礎となる。

「モデル」を共通言語として扱うには、"関心事をモノとして捉え、人に分かりやすいモノとして設計する"オブジェクト指向設計が馴染むのはよく分かる。人同士がイメージして捉えづらいものを共通言語として使用するのは無理がある。

ここまでが導入にあたって、で導入が済んだら漸くアーキテクチャの話。

モデル構成要素(アーキテクチャ

DDDとは別軸に、「モデル」を元に開発を進める技法 MDD(Model Driven Development)というものが存在する。オブジェクト指向UML界隈で、モデリングを中心に据えた開発手法となる。(モデリングを極めるとコードへと自動変換されるという崇高な手法を手に入れられるらしいが。。)

ドメイン駆動は「ドメイン」をすべての中心に置く壮大な思想である。故に実現するための手段すべてがオリジナルで準備されているわけではなく、一段階噛み砕いた先で使えるツールを必要とする。ここで先程のMDD(のうち主に設計手法)をツールとして利用する。前の項でも書いたようにMDDの「モデル」や元となるオブジェクト指向がよく馴染むのだろう。

ドメインを駆動するには、モデルを駆動させるのである。

さて、DDDのアーキテクチャの話に戻る。

世界の中心であるドメイン層を他から分離すると、UI層、アプリケーション層、ドメイン層、インフラ層となる。左から右へ依存関係を持つ。これ自体は単なるレイヤードアーキテクチャだ。
アプリケーション層は、ソフトウェアとして具現化するために必要な処理を受け持つ。UseCaseやFacade的なやつだ。

f:id:yotiky:20181030034913p:plain

ドメイン層に含まれるドメインモデルは「エンティティ」、「値オブジェクト」、「サービス」に分類される。更にこれらを束ねる「モジュール」(パッケージや名前空間)の4つが重要な構成要素となる。
ドメインモデルのオブジェクトはライフサイクルを管理するため、「ファクトリ」や「リポジトリ」が登場する。これらはドメインとして重要な意味は持たないが必要になるであろう機能ということでドメイン層に持つ。
インフラ層は技術的な関心事を集約する。例えばドメイン層に持つ「リポジトリ」はInterfaceのみを持ち、その中身がどこで永続化されようと同じように操作することができるようにし、永続化するための実処理は特定技術(DBなど)に依存するためインフラ層で担当する。
DBを始め、FileI/Oやその他ストレージ、O/Rマッパー、DIコンテナ、メッセージング、Webサービスなどがある。また、UI周りでもレンダリング機能、Toolkit、ウィジェットなんて記述もありこのあたりは具体的なイメージがあまりついていない。

このあたりはレイヤードアーキテクチャを構成、繋ぎ合わせるために必要となるであろう基本要素であり骨子である。これをベースに「モデル」のリファクタリングを重ねて、Deep Modelへと誘う。
が、この先になってくると継続的に改善して成熟させる話になるのでここまでとする。

IDDDに思いを馳せる

一方で、IDDDにおいては Hexagonal Architecture とそれに関係のありそうないくつかの方針を持ち出しているよう。
Hexagonal Architecture 自体は前回の記事にまとめているのでそちらを参照されたし。
DDD自体はアーキテクチャに依存しない方法論としてレイヤードアーキテクチャを例に提唱されはしたものの、「ドメイン」を隔離するにはそれじゃ足りないんじゃねってとこだろうか。

Layered Circleを持ち出すとどうしてもDIに依存せざるを得ない。した方が楽だし、都合がいい。
DDDとDIの関係については、参考サイトより以下の記述が載っていた。1
2008年と古い記事ではあるが、Eric Evansの解釈が含まれる貴重な内容だ。

DDDのフィロソフィとそれを実現するさせることを助ける技術的なツールボックスOOP、DI、そしておそらくAOP)との線引きを明確にすることが重要だ

と強調した上で、

最近になってDIはとても広く行われるテクニックとなり、それを上手く行えばDDD実践者がデザインを改善する手助けになっています。私からみて一番重要なのは、DIがドメインの分離において役立つということです。

としている。DIばんざい!

Clean Architectureの外周にあるFrameworksに、Application基盤となるDIコンテナは含めないのが頭痛を和らげる解釈になるのかな。

参考にさせて頂いたサイト