Dependency injection

138
Dependency injection framework. Dagger2

Transcript of Dependency injection

Dependency injection framework.

Dagger2

WHO?• 松村 勇輝

• Twitter. @Yuki_312

• Yukiの枝折. http://yuki312.blogspot.jp/

• Android Developer

CONTENTS• 依存性

• DI Framework

• Dagger2

• テストとアーキテクチャ

• 補足

ソフトウェア設計の課題

依存性

依存性

依存性GitHubの情報を“保存する”アプリケーション

GitHub <interface>GitHubStore

依存性GitHubの情報を“保存する”アプリケーション

GitHubStoreを使って“保存”を実現する

GitHub <interface>GitHubStore

依存性

implements

インタフェースはそのままでは使えない.

具象化した実装クラスが必要.

GitHubDatabase

GitHub <interface>GitHubStore

依存性実装クラスはインスタンス化しなければならない.

GitHubDatabase

new

GitHub <interface>GitHubStore

依存性実装クラスはインスタンス化しなければならない.

class GitHub { private GitHubStore store = new GitHubDatabase();}

GitHubDatabase

new

GitHub <interface>GitHubStore

依存性newが依存性を生む.

GitHubの保存形式がDatabaseに固定されてしまう.

GitHubDatabase

new

GitHub <interface>GitHubStore

class GitHub { private GitHubStore store = new GitHubDatabase();}

依存性依存性はシステムの振る舞いを固定する.

GitHubDatabase

new

GitHub <interface>GitHubStore

class GitHub { private GitHubStore store = new GitHubDatabase();}

依存性依存性はシステムの振る舞いを固定する.

依存性はシステムの多様性を殺す.

GitHubDatabase

new

GitHub <interface>GitHubStore

class GitHub { private GitHubStore store = new GitHubDatabase();}

依存性依存性はテストの振る舞いを固定する.

依存性はテストの多様性を殺す.

テストが辛い.

DEPENDENCY INJECTION

• 制御の反転 – Inversion of Control

• ハリウッドの原則 – Hollywood Principle

依存性の注入

GitHub GitHubDatabasenew

ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」

依存性の注入

GitHub GitHubDatabasenew

依存性の注入

GitHub GitHubDatabaseInject

ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」

class GitHub { public GitHub(GitHubStore store) {...}}

new GitHub(new GitHubDatabase());

依存性の注入

GitHub GitHubDatabaseInject

ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」

class GitHub { public GitHub(GitHubStore store) {...}}

new GitHub(new GitHubDatabase());

依存性の注入

ハリウッドの原則「おまえが呼ぶな, おれが呼ぶ」

GitHub GitHubStore

GitHubDatabase

Client

new

Inject

class GitHub { public GitHub(GitHubStore store) {...}}

new GitHub(new GitHubDatabase());

依存性の注入GitHubの依存性を制御できるようになった.

e.g. 商用ではDatabase, 検証用ではMemory cacheに...

切り替えが容易になって, テストが楽になる!

CREATION DESIGN

new, new, new...制御の反転だけでは生成コードが散在する

new, new, new...制御の反転だけでは生成コードが散在する

ClientA: new GitHub(new GitHubDatabase());

ClientB: setDatabase(new GitHubDatabase());

ClientC: create(new GitHubDatabase());

new, new, new...e.g. UnitTestではMemory cacheでテストしたい

ClientA: new GitHub(new GitHubDatabase());

ClientB: setDatabase(new GitHubDatabase());

ClientC: create(new GitHubDatabase());

new, new, new...e.g. UnitTestではMemory cacheでテストしたい

ClientA: new GitHub(new GitHubDatabase() new GitHubMemcached());

ClientB: setDatabase(new GitHubDatabase() new GitHubMemcached());

ClientC: create(new GitHubDatabase() new GitHubMemcached());

new, new, new...クライアントも含めたテストが難しい...

システム全体の依存性を変更するのが難儀...

ClientA: new GitHub(new GitHubDatabase() new GitHubMemcached());

ClientB: setDatabase(new GitHubDatabase() new GitHubMemcached());

ClientC: create(new GitHubDatabase() new GitHubMemcached());

FACTORY PATTERN

Factory Pattern

GitHub GitHubStore

GitHubDatabase

Client

Factory Pattern適用前

Factory Pattern

GitHub GitHubStore

GitHubDatabase

Client

new

Factory Pattern適用前

Factory Pattern

GitHub GitHubStore

GitHubDatabase

Client

InjectGitHubDatabase

Factory Pattern適用前

Factory Pattern

GitHub GitHubStore

GitHubDatabase

Client

InjectGitHubDatabase

Factory Pattern適用前

class GitHub { public GitHub(GitHubStore store) {...}}

new GitHub(new GitHubDatabase());

Factory PatternFactory Pattern適用後

GitHub GitHubStore

GitHubDatabase

Client

Factory Pattern生成処理をFactoryに委譲して問題領域を局所化

GitHub GitHubStore

GitHubDatabase

Client

Factory

Factory Pattern生成処理をFactoryに委譲して問題領域を局所化

GitHub GitHubStore

GitHubDatabase

Client

Factory

Get

Factory PatternFactoryがGitHubDatabaseを生成する

GitHub GitHubStore

GitHubDatabase

Client

Factorynew

Factory PatternFactoryがGitHubDatabaseを生成する

GitHub GitHubStore

GitHubDatabase

Client

Factory

Factory PatternClientを実装の詳細から隠蔽する

GitHub GitHubStore

GitHubDatabase

Client

Factory

Inject

Factory PatternClientを実装の詳細から隠蔽する

GitHub GitHubStore

GitHubDatabase

Client

Factory

new GitHub(Factory.getGitHubStore(type, flag));

Inject

FACTORYの問題

Factoryの問題

Factoryの問題

• 多くの生成処理を委譲されて肥大化する

Factoryの問題

• 多くの生成処理を委譲されて肥大化する

• 生成順序, 構築方法に責任を持つ責務過多

Factoryの問題

• 多くの生成処理を委譲されて肥大化する

• 生成順序, 構築方法に責任を持つ責務過多

• Shared Object? Singleton?

Factoryの問題

• 多くの生成処理を委譲されて肥大化する

• 生成順序, 構築方法に責任を持つ責務過多

• Shared Object? Singleton?

• Lifecycle, Scopeの管理も必要

Factoryの問題

依然, 散在するコピペコード

Factoryの問題

依然, 散在するコピペコード

Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);Factory.getGitHubStore(type, flag);

解決策

DI FRAMEWORK

DI Framework

DI Framework• コピペコードの排除

DI Framework• コピペコードの排除

• 依存オブジェクトの管理を委譲

DI Framework• コピペコードの排除

• 依存オブジェクトの管理を委譲

GitHub GitHubStore

GitHubDatabase

Client

Factorynew

Inject

依存オブジェクト

DI Framework• コピペコードの排除

• 依存オブジェクトの管理を委譲

GitHub GitHubStore

GitHubDatabase

Client

new

DI FW.

Inject

依存オブジェクト

DAGGER2

Dagger2• for Java & Android

• Annotation Processingベースでデバッグが楽

• 高速

• コンパイル時に依存性の検証を行う

• Googleがメンテナ

Dagger2DI Frameworkの3ステップ

Dagger2DI Frameworkの3ステップ

1. 依存性の要求

Dagger2Request Dependency

GitHub

Dagger2DI Frameworkの3ステップ

1. 依存性の要求

2. 依存性の探索

Dagger2GitHub

Lookup Dependency

Dagger2DI Frameworkの3ステップ

1. 依存性の要求

2. 依存性の探索

3. 依存性の充足

Dagger2Inject Dependency

GitHub

依存性の要求

依存性の要求

依存オブジェクトをどのように要求するのか?

Request Dependency

GitHub

依存性の要求

依存オブジェクトは @Inject で要求する

Dagger2

GitHub

GitHubStore

依存性の要求

依存オブジェクトは @Inject で要求する

Dagger2

GitHub

GitHubStore@Inject GitHubStore store;

依存性の要求

依存オブジェクトは @Inject で要求する

Dagger2

GitHub

GitHubStore

Request Dependency

@Inject GitHubStore store;

依存性の探索

依存性の探索

依存性オブジェクトをどのように探索するのか?

Dagger2

Lookup Dependency

依存性の探索

Dagger2

GitHubDatabase

依存性の探索

Dagger2

GitHubDatabaseclass GitHubDatabase { @Inject GitHubDatabase() {...}}

依存性の探索

Dagger2

GitHubDatabase

lookup & new

class GitHubDatabase { @Inject GitHubDatabase() {...}}

依存性の探索

Dagger2

GitHubDatabase

GitHubDatabase

依存性の充足

依存性の充足

依存性をどのように充足するのか

Dagger2Inject Dependency

GitHub

Dagger2GitHub

依存性の充足

ソフトウェア全体の依存性を満たす

GitHubStore GitHubDatabase

Dagger2GitHub

依存性の充足

ソフトウェア全体の依存性を満たす

GitHubStore GitHubDatabaseDI

Dagger2GitHub

依存性の充足

ソフトウェア全体の依存性を満たす

GitHubDatabase GitHubDatabase

Dagger2

依存性の充足

ソフトウェア全体の依存性を満たす

GitHub

GitHubWebApi

GitHubDatabase GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2GitHub

GitHubWebApi GitHubWebApi

GitHubDatabase GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2GitHub

GitHubWebApi

DIGitHubDatabase

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2GitHub

GitHubDatabase

GitHubWebApi GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2RepositoryViewer

GitHub

GitHubDatabase

GitHubWebApi

GitHub

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2RepositoryViewer

GitHub

GitHubDatabase

GitHubWebApi

GitHub

lookup & newGitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2

GitHub

RepositoryViewer

GitHub

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2DI

RepositoryViewer

GitHub GitHub

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2RepositoryViewer

GitHub GitHub

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2RepositoryViewer

GitHub GitHublookup & new

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2

RepositoryViewer

GitHub

GitHubWebApi

GitHubDatabase

依存性の充足

ソフトウェア全体の依存性を満たす

Dagger2

GitHubDatabaseGitHubWebApi

GitHub

RepositoryViewer RepositoryViewer

GitHub

GitHubWebApi

GitHubDatabase

依存性の充足

Graph

GitHubDatabaseGitHubWebApi

GitHub

RepositoryViewer

続. 依存性の探索

依存性の探索

コンストラクタへの@Injectでは解決できないケース

依存性の探索

コンストラクタへの@Injectでは解決できないケース

• インタフェースの解決(具体化)

• 管理外クラスへの@Inject宣言

• オブジェクトの構築を伴う生成

PROVIDE DEPENDENCY

依存性の提供

• Dagger2に閉じて解決できない

特殊な依存性を解決するFactory methodの提供

依存性の提供

Instance Provider

provideGitHubStore

依存性の提供

@Provides

Instance Provider

provideGitHubStore

依存性の提供

@Provides

Instance Provider

provideGitHubStore

@Provides GitHubStore provideGitHubStore() { return new GitHubDatabase();}

依存性の提供

Dagger2GitHub

GitHubStore

Instance Provider

provideGitHubStoreGitHubDatabase

依存性の提供

Dagger2GitHub

GitHubStore

Instance Provider

provideGitHubStoreGitHubDatabase

RequestDependency

依存性の提供

Dagger2GitHub

GitHubStore

Instance Provider

provideGitHubStore

Resolved Dependency

GitHubDatabase

依存性の提供

Dagger2GitHub

GitHubStore

Instance Provider

provideGitHubStore

Call Factory Method

GitHubDatabase

依存性の提供

Dagger2GitHub

GitHubStore

Instance Provider

provideGitHubStore

newGitHubDatabase

依存性の提供

Dagger2GitHub

GitHubStore

Instance Provider

provideGitHubStore

GitHubDatabase

GitHubDatabase

依存性の提供

Dagger2GitHub

GitHubStore

DI

GitHubDatabase

Instance Provider

provideGitHubStoreGitHubDatabase

依存性の提供

GitHub

GitHubDatabase

Dagger2

GitHubDatabase

Instance Provider

provideGitHubStoreGitHubDatabase

依存性の提供

@Provides

Instance Provider

provideGitHubStore

@Provides GitHubStore provideGitHubStore() { return new GitHubDatabase();}

依存性の提供

@Provides

• インタフェースの解決(具体化)

• 管理外クラスへの@Inject宣言

• オブジェクトの構築を伴う生成

Instance Provider

provideGitHubStore

@Provides GitHubStore provideGitHubStore() { return new GitHubDatabase();}

依存性の提供

@Providesを持つクラスには @Module を付与する

@Moduleclass ApplicationModule { @Provides GitHubStore provideGitHubStore() { return new GitHubDatabase();

}

}

GRAPH

Graph

Building the GraphGraphは “Component” の単位で管理する

GitHubDatabaseGitHubWebApi

GitHub

RepositoryViewer

Dagger2

Component

Building the GraphGraphの設計図として@Componentを宣言する

@Component(modules=ApplicationModule.class)interface ApplicationComponent {...}

Building the GraphGraphの操作はComponentを通じて行われる

Client Component

ApplicationComponent component = DaggerApplicationComponent.builder() .applicationModule(new ApplicationModule()) .build();

俯瞰図

@InjectmemberField;

@Module

@Component / Dagger2

@Provide

Interface/ Unmanaged Class

Graph

CREATE / BUILD

PROVIDEFACTORY METHOD

DEPENDENCYINJECTION

@InjectConstructor()

CREATE

依存オブジェクト

依存性の充足

依存性の要求

TEST & ARCHITECTURE

Test & ArchitectureDIが促進するもの

• レイヤーをきれいに分離できる

• レイヤーが独立し, レイヤーの差し替えが容易

• テストが楽になる!

Test & Architectureテスタビリティの向上

• テストは検証用モジュールで実施

• テスト用レイヤに差し替え etc.

DEMO

Demo商用…WebAPIの結果を表示&Databaseへ永続化

検証用…WebAPIの結果を表示&オンメモリキャッシュ

シナリオ:

通常は商用環境で試験.

ただし, AWS Device Farmでは検証用で試験.

Demo商用モジュールで実施

Dagger2

GitHubDatabase

GitHubWebApi

GitHub

RepositoryViewer

GitHubStore

Dagger2

Demo商用モジュールで実施

DIGitHubWebApi

GitHub

RepositoryViewer

GitHubStore

GitHubDatabase

Demo商用モジュールで実施

GitHubWebApi

GitHub

RepositoryViewer

GitHubDatabase

Dagger2

GitHubDatabase

Demo検証用モジュールで実施

Dagger2

GitHubMemcacheGitHubWebApi

GitHub

RepositoryViewer

GitHubStore

Demo検証用モジュールで実施

Dagger2

GitHubMemcache

DIGitHubWebApi

GitHub

RepositoryViewer

GitHubStore

Demo

Dagger2

GitHubMemcacheGitHubWebApi

GitHub

RepositoryViewer

GitHubMemcache

検証用モジュールで実施

Sample codeGitHub - Dagger2Sample

https://github.com/YukiMatsumura/Dagger2Sample

まとめ

Dependency Injection• オブジェクトの生成は依存性を生む

• オブジェクトの生成はコスト/責務である

• おまえが呼ぶな, おれが呼ぶ

• DIがレイヤーの独立性を高める

• DIがテスタビリティを高める

以降, 補足

Graphの操作

ComponentでGraphの操作を定義

• Graphが属するScopeの宣言

• 依存性注入のポイントを外部公開

• 他Componentへの依存

Scope依存オブジェクトのライフサイクルを指定

• Application単位のSingleton性を持たせる

• Activity単位のSingleton性を持たせる etc.

Qualifier• 依存性の注入先に識別子を付ける

• 同じ型の依存性解決に使用される

Lazy Injection• オブジェクト取得時に依存性を解決・注入する

• 遅延初期化

Provider Injection• 依存性注入の都度newするnon-cached指定

Subcomponent• ComponentAとComponentBに親子関係を持たせる

• ComponentA+ComponentBのGraphをつくる

dependencies• ComponentAとComponentBに使用関係を持たせる

• ComponentA+ComponentBのGraphをつくる

以上

ご清聴ありがとうございました