Featureモジュール間の依存解決

Posted on | 86 words | ~1 min
  • Context
    • iOSアプリ
    • UIKitコードベースとの付き合い
    • feature-modularizedなマルチモジュール構成をとった場合の課題
  • 課題
    • Featureモジュール間の画面遷移をどう実現するか
      • why: featureモジュール同士は依存関係をつくれない(循環参照)
      • so: FeatureAからFeatureBの画面(VC)インスタンスを生成したい場合、globalに定義される共通の抽象インタフェースへの依存で解決する必要性が生じる
    • VC生成にあたり、それにぶら下がる依存解決上の課題
      • ときに、single source of truth用途(インメモリで保持される状態アクセスなど)のsingletonインスタンスをFeatureモジュール間で引き回したい。暗黙依存でなくDI(constructor injection)の形式で。テスト容易性。
      • 得てして、このインスタンスが実際に用いられる層はVCではなく、依存先の層(model/usecase)であるケースがほとんど
      • 依存解決のためのインスタンスのバケツリレー
      • 使用側(下位)のコンストラクタ(DI仕様)変更に伴う、上位側からの依存注入し直しバケツリレー変更のつらみ
  • 依存解決やり方
    • モジュール内に存在する各VCを生成するためのFactoryなインタフェースをglobalに定義(eg. 各Featureモジュールが共通に依存するCoreモジュールに定義するなど)
    • 各Featureは共通化されたFactoryインタフェースを用いて別Featureの画面を生成し、遷移先として指定する
  • 各社の知見
    • cookpad社の取り組み
      • ViewBuilderがFeature内VC生成のインタフェース
      • Coreに定義されるEnvironmentをつかってbuild
      • buildの中でRouter初期化時にenvironmentをDI
      • Router内で任意のViewDescriptor(これはCoreレベルで公開)を初期化、environment.resolve(descriptor)でVCインスタンスを得る
      • application targetで、Environment protocol準拠することで、ViewDescriptor毎のVCインスタンス生成の具象コードを実装
      • 課題
        • Environmentのresolve実装するとき、Descriptorの網羅が必要
        • Descriptorはstaticなenumではないので、case網羅のためにコンパイラチェックの恩恵を受けられない
        • switch caseを網羅するのに工夫が必要(実際cookpadでは存在するDescriptorのcase網羅が行われるようにコードの自動生成のしくみを構築していて、カバーされていないcaseがあればコンパイルエラーになるようにしているとのこと)
        • しくみの構築と運用のコスト
        • もっとラクにコンパイルタイムでの依存解決を保証するしくみがあるとうれしい
    • はてな社の取り組み
      • uber/needleを用いたDIコードの自動生成(≒ Dagger)
      • 型安全なEnvironment実装生成不要でViewBuilder相当のDI定義を各Featureモジュールに寄せられる
      • 本体app targetではRootComponentとそれにぶら下がる各FeatureComponentが必要とするFactoryを定義するだけで済む
      • 依存解決の不足をコンパイラチェック可能に
  • 実装How
    • uber/needle
      • CoreにVCインスタンス化用のBuildable定義
      • FeatureでComponent定義
        • 画面に対応するBuildable型を返却するBuilderを内部で実装
      • app targetでRootComponentを定義
        • childとなる各FeatureComponentのDependencyに必要なfactory定義を施す
      • singletonを引き回したい場合は、 NeedleFoundationのsharedでfactoryをラップすることで解決できる
    • app target
      • RootComponent
    • Features
      • Feature Alpha
        • FeatureAlphaComponent
          • FeatureAlphaFooBuilder
      • Feature Bravo
        • FeatureBravoComponent
          • FeatureBravoFooBuilder
    • Core
      • AlphaFooBuildable
      • BravoFooBuildable
image