57
Android Clean Architecture ことはじめ Mercari, Inc. Tomoaki Imai 08/08/2015 Android All Stars @Shibuya dots.

Android cleanarchitecture

Embed Size (px)

Citation preview

Android Clean Architecture ことはじめMercari, Inc. Tomoaki Imai

08/08/2015 Android All Stars @Shibuya dots.

Tomoaki ImaiMercari, Inc.

twitter: @tomoaki_imai

github: tomoima525

今日はみなさんに質問が あります

胸に手を当てて振り返って みてください

キレイな設計、できてますか?

Androidにおける設計の難しさ• Life Cycle(画面再生成 etc)

• 数多く存在するApi

• OSごとのバージョン管理

みんな似たようなことで 悩んでる

ライフサイクル 考えるのめんどくさい

画面回転が入ると面倒

Activity, Fragmentが どんどん肥大化する

ディレクトリ構成が プロジェクトでまちまち

Unit/UI Testが難しい

“Android Clean Architecture” という考え方

The Clean Architecture by Uncle Bob

http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

“Android Clean Architecture” という考え方

The Clean Architecture (Enterprise Application)

(Web Service)+ =

Android Clean Architecture

“Android Clean Architecture” という考え方

http://www.slideshare.net/shinnosukekugimiya/ss-50705888

https://github.com/android10/Android-CleanArchitecture

今日みなさんに持って返って もらいたいもの

• Android Clean Architectureの思想をさっくり理解する

• Android Clean Architectureの実際的な実装方法を理解する

• 既存の設計と比べてどういうメリットがあるか理解する

標準的なWeb設計の指針

“MVC Architecture”

MVC Architecture

Modelアプリケーションデータ ビジネスロジック

View Modelをレンダリングする

Controller ユーザーイベントをハンドルする

View

Model

Controller

User

usessees

Life Cycle

Data source

Frameworks

Android Frameworks

Activity Life Cycle

MVC Architecture on Android

Model

アプリケーションデータ (http, DB, memory色々) ビジネスロジック

ViewModelをレンダリングする (端末の状態を意識しながら)

Controllerユーザーイベントをハンドルする (端末状態をハンドルする) (色んなViewもハンドルする)

View

Model

Controller

User

usessees

MVCでアプリ

Sample App• GithubからFollowerを取ってくるアプリ

• Followerのrepository見れる

• 選択したFollowerはメモリにキャッシュし、2回目以降はトーストで通知する

https://github.com/tomoima525/CleanArchitectureSample

/app配下 -> MVC で実装

/cleanarchitecture配下 -> Clean Architecture で実装

private GithubApi mApi;private UserMemoryCache mUserMemoryCache;…

mApi = createApi();mUserMemoryCache = UserMemoryCache.getInstance();…public void HogeTask(…){ //ビジネスロジック… mApi.listFollowersAsync(…); mUserMemoryCache.put(…); }

Models

• Modelのデータ元は多様

• ModelとControllerのインタラクション(ビジネスロジック)がテストしにくい

public class MainActivity { @Override protected void onCreate(Bundle savedInstanceState) { // create views... }

@Override protected void onResume(){ // resume/update views when system state is resumed }

@Override protected void onPause(){ // pause views when system state is paused }}

Views

• Viewの状態管理が必要

public class MainActivity { // User events public void onClick(){} public void afterTextChanged(){}

// System events public void onSaveInstanceState(Bundle outState){} public void onRestoreInstanceState(Bundle inState){}

// View events public void gotoUserDetailActivity(Context context){}}

Controllers

• ViewとControllerがActivity上に混在している

• 画面再生成などのシステムの状態もハンドルする必要がある

MVC Architecture on Android における課題

View(の状態管理)とControllerがActivity上に混在している

ModelとController間のインタラクション(ビジネスロジック)が システム状態(LifeCycle)に影響を受ける/テストしにくい

Modelのデータ・ソースが多様

各層の依存関係が強い

Clean Way…?

MVC Architecture on Android における課題

View(の状態管理)とControllerがActivity上に混在している

ModelとController間のインタラクション(ビジネスロジック)が システム状態(LifeCycle)に影響を受ける/テストしにくい

Modelのデータ・ソースが多様

各層の依存関係が強い

MVP Architecture

MVP Architecture

• Presenterを介してModelを制御する設計

View

Presenterからの情報を レンダリングする -> 端末の状態は配慮不要になる

Presenter

- Activityから分割 - ユーザーイベントを ハンドルする - Modelの結果をViewに返すAndroid Frameworks

Activity

View

Model

Presenter

Userusessees

public interface ShowUserListView { // UI として表示したい処理を定義する void showLoading(); void hideLoading(); void showNoResultCase(); void hideNoResultCase(); void showResult(Collection<User> usersCollection);}

Views

public abstract class Presenter { // Activity LifeCycle public abstract void initialize(); public abstract void resume(); public abstract void pause(); public abstract void destroy();}

Presenters

public class ShowUserListPresenter extends Presenter { public void setShowUserListView(ShowUserListView view){ mShowUserListView = view; //Viewを監視できるようにする }

public void getFollowerList(String user){ mShowUserListView.showLoading(); // Activity側にCallbackする }}

Activitypublic class MainActivity implements ShowUserListView{

@Override public void onCreate(){ mShowUserListPresenter = new ShowUserListPresenter(…); mShowUserListPresenter.setShowUserListView(this); } @Override public void showLoading() { //実際のUI処理はActivityで実行できる! mListView.setVisibility(View.GONE); mProgress.setVisibility(View.VISIBLE); } @Override public void showResult(Collection<User> usersCollection) { mListView.setVisibility(View.VISIBLE); mUserAdapter.refresh(usersCollection); }}

MVC Architecture on Android における課題

MVP Architecture

ModelとController間のインタラクション(ビジネスロジック)が システム状態(LifeCycle)に影響を受ける/テストしにくい

Modelのデータ・ソースが多様

各層の依存関係が強い

MVC Architecture on Android における課題

MVP Architecture

ModelとController間のインタラクション(ビジネスロジック)が システム状態(LifeCycle)に影響を受ける/テストしにくい

Modelのデータ・ソースが多様

各層の依存関係が強い

Domain Layer

• Domain Layerにビジネスロジック(interactorまたはuse casesと呼ぶ)を集約する

• Modelとの処理はDomain経由で行う

• 処理は非同期で実行する

• Pure Java

Domain Layer

Domain

Android Frameworks

Activity

View

Model

Presenter

Userusessees

Domain Layer

UseCase <<abstract>> ベースとなるスレッド処理を実装

ConcreteUseCaseUseCaseを継承 ロジック処理の実行、Presenterへのコールバック

PostExecutionThread (UIThread) スレッド実行後の処理

• Domain層は大まかにUseCase, Threadにより成立

• 設計方法は多くの手法がある

public abstract class UseCase<T> { // スレッドをキュー処理できるようにする private ExecutorService mExecutorService

= Executors.newSingleThreadExecutor(); public void start(final T params) { mExecutorService.submit(new Runnable() { @Override public void run() { call(params); } }); } //スレッド内でcallされるメソッド abstract protected void call(T params);}

UseCase

https://github.com/kgmyshin/Android-archを参考

public class FollowerListUseCase extends UseCase<String> { @Override public void execute(String user, FollowerListUseCaseCallback callback){ mCallback = callback; this.start(user); // (1) ExecutorServiceのスレッド経由でcall()が呼ばれる }

@Override protected void call(String user) { // (2) Modelからデータ取得、バリデーション等のロジック処理。 // …省略 // (3) 処理後はUIThread( PostExecutionThread )にCallbackを返す mPostExecutionThread.post(() -> {mCallback.onUserListLoaded(usersCollection);}); } // UIThreadに返すCallbackのinterface interface FollowerListUseCaseCallback { onUserListLoaded(final Collection<User> usersCollection); onError(); }}

ConcreteUseCase

Presenterpublic class ShowUserListPresenter extends Presenter implements FollowerListUseCaseCallback { private FollowerListUseCase mFollowerListUseCase; public void getFollowerList(String user){ // (1) usecaseを実行 -> 別スレッドへ mFollowerListUseCase.execute(user, this); }

// (2) FollowerListUseCaseCallback経由で処理結果を受け取る @Override onUserListLoaded(final Collection<User> usersCollection){ // Viewへ結果を渡す } @Override onError(){ // error handling }}

MVC Architecture on Android における課題

MVP Architecture

Domain Layer

Modelのデータ・ソースが多様

各層の依存関係が強い

MVC Architecture on Android における課題

MVP Architecture

Domain Layer

Modelのデータ・ソースが多様

各層の依存関係が強い

Data Layer

Android Frameworks

Data Layer

• データをEntity(メソッドやデータ構造体のかたまり)として扱う

• Domain Layerにデータ元を意識させないためにRepository パターンで実装する

Domain

Android Frameworks

Activity

View

Data

Presenter

Userusessees

Repository Pattern• ドメイン駆動開発で利用されるデザインパターン

• UseCaseで必要なデータソースを集約したRepositoryクラスを実装する

Data

UserRepository DocsRepository XXRepository

public class UserRepository { private GithubApi mApi; // Github API用のインスタンス private UserMemoryCache mUserMemoryCache; //メモリキャッシュ用インスタンス

public UserRepository(){ mUserMemoryCache = UserMemoryCache.getInstance(); mApi = createGithubApi(); }

public void getFollowers(String user,UserListCallback callback) { mApi.listFollowersAsync( ... // Networkから取得 }

public void putUser(User user){ mUserMemoryCache.put(… // メモリにキャッシュ }}

Repository

MVC Architecture on Android における課題

MVP Architecture

Domain Layer

Data Layer(Repository Pattern)

各層の依存関係が強い

MVC Architecture on Android における課題

MVP Architecture

Domain Layer

Data Layer(Repository Pattern)

各層の依存関係が強い

Dependency Inversion Principle

Dependency Inversion Principle

• 依存関係逆転の原則

• 上位モジュールは下位モジュールに依存しない

例) PresenterはDomainの実装がどのようになってても影響を受けない

• interfaceを各層の間に置く

Android Frameworks

Domain

Android Frameworks

Activity

View

Data

Presenter

Userusessees

interface

interface

public interface UserRepository { // Domain層(UseCase)側はこの interfaceの仕様だけ知っていればOK void getFollowers(String userId, UserRepositoryCallback callback); void getUser(String userId, UserRepositoryCallback callback); void putUser(User user);}

Dependency Inversion

//実際の実装は interfaceを継承する public class UserRepositoryImpl implements UserRepository { @Override public void getFollowers(String userId, UserRepositoryCallback callback){ //データ取得 … } @Override public void putUser(User user) { } //…}

MVC Architecture on Android における課題

MVP Architecture

Domain Layer

Data Layer(Repository Pattern)

Dependency Inversion Principle

どう変わった?

こう変わった!

Android Frameworks

Domain

Android Frameworks

Activity

View

Data

Presenter

Userusessees

interface

interface

UIがビジネスロジックから分離して 見通しが良くなった

ビジネスロジックがAndroidのライフサイクルから分離され、テストが容易に

Modelの仕様変更に柔軟に 対応可能になった

モジュールの置き換えが 容易になった

なんか難しい…?

少しずつ手をつけると よいかも

どこから手を付けるべきか

手法 実現難易度

MVP Architecture ☆

Domain Layer ☆☆☆

Data Layer(Repository Pattern) ☆☆

Dependency Inversion Principle ☆☆

Next step…• Dependency Injection

• 簡潔に書ける

• モジュールの載せ替えが容易

Next step…• Implementing Observer Pattern

• Domain Layerの煩雑なスレッド処理, Callback地獄からの開放

参考例: https://github.com/android10/Android-CleanArchitecture

– Clean Architecture, Uncle Bob

Architecture is about Intents, not Frameworks

– Clean Architecture, Uncle Bob

設計とは(良いコードが書きたいっていう)

意志である!

フレームワークなんて関係ねぇ!(意訳)