エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

Androidアーキテクチャことはじめ ― 選定する意味と、MVP、Clean Architecture、MVVM、Fluxの特徴を理解する

Androidアプリの開発において悩ましいアーキテクチャの選定。本記事では選定する意味を改めて整理し、 MVP・Clean Architecture・MVVM・Fluxといった最新の実例を紹介します。

industry icons over white background. vector illustration

はじめまして。Androidエンジニアの藤原聖(ふじわら・さとる/@satorufujiwaraです。
現在は株式会社サイバーエージェントで、エンジニアリングマネージャーを兼任しています。2017年で35歳になり、定年を迎えました(プログラマの定年については「体型を支える技術」などを参照)

Androidアプリ開発には2010年から携わっていますが、今現在の関心事は何といっても公式開発言語に採用されたKotlin。そしてもう一つが、Androidのアーキテクチャ(設計)です。私もこれまで次のような記事を公開してきました。

RxJava + Flux (+ Kotlin)によるAndroidアプリ設計 - Qiita
Kotlin + Architecture Component + Dagger2によるAndroidアプリ設計 - Qiita

Android開発において、アーキテクチャを考えることはとても重要です。上記のエントリーでも述べたように、アーキテクチャに絶対の正解はなく、アプリケーションの規模やチームによってさまざまな形が考えられます。

また、Androidのアーキテクチャについては国内海外を問わず、これまでさまざまな議論が行われてきましたが、2016年にアーキテクチャのサンプルである「Android Architecture Blueprints」がGoogleによって公開され、さらにGoogle I/O 2017では「Android Architecture Components」が発表されました。

これによって、Androidにおけるアーキテクチャの議論はいったん収束の時期を迎えているように思います。

本記事では、Androidにおいてアーキテクチャを選定する意味を改めて整理し、現状のAndroidのアーキテクチャの実例を紹介します。これからAndroidのアーキテクチャを考える方々の参考になれば幸いです。

アーキテクチャにおけるデザインパターン

Androidも含めたモバイルアプリケーションにおいてアーキテクチャといえば、MVC(Model View Controller)やMVP(Model View Presenter)といったものが思い浮かぶかと思います。これらは、言葉を省略せずに言うなら「アーキテクチャにおけるデザインパターン」のことです(例えば、MVCはユーザーインタフェースを持つアプリケーションソフトウェアを実装するデザインパターン)

デザインパターンというとGoFによるものが有名ですが、Wikipediaの「デザインパターン」の項目では次のように説明されています。

デザインパターン(略)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

すなわち、アーキテクチャは「名前のついた、再利用するための設計ノウハウ」のことだと言えます。本記事では、この「設計ノウハウ」のことをアーキテクチャと呼ぶことにします。

アーキテクチャはなぜ必要か?

アーキテクチャはデザインパターンのことだと書きましたが、再びWikipediaの「デザインパターン」でGoFに関する記述を参照すると、こう書かれています。

コンピュータのプログラミングで、素人と達人の間では驚くほどの生産性の差があり、その差はかなりの部分が経験の違いからきている。達人は、さまざまな難局を、何度も何度も耐え忍んで乗り切ってきている。そのような達人たちが同じ問題に取り組んだ場合、典型的にはみな同じパターンの解決策に辿り着く。これがデザインパターンである(GoF)

すなわち、デザインパターンとは、達人が編み出した典型的な問題解決の手段ということができます。

冒頭で書いたように、アーキテクチャに絶対の正解はなく、アプリケーションの規模やチームによってさまざまな型(デザインパターン)が考えられます。なぜならアプリケーションの規模やチームによって解決すべき問題が異なるからです。

問題を解決するためにアーキテクチャのデザインパターンが必要であり、問題の内容に応じてデザインパターンは異なります。したがって、アーキテクチャを選定する際には「解決したい問題が何か?」を考える必要があります。

アーキテクチャによって解決すべき問題とは?

アーキテクチャを用いて解決するべき問題とは、どのような問題なのでしょうか?

アプリケーションの規模やチームによって抱える問題は異なるものの、Androidアプリケーションを作成する上で、多くの人が似たような問題に直面すると思います。まずはそうした一般的な問題について考えてみましょう。

MVCやClean Architectureなど、Androidで用いられているアーキテクチャの多くは、もともとWebアプリケーションで用いられていました。したがって、Androidで解決すべき問題にも、Webアプリケーションと同一のものが存在します。

ここでは、Webアプリケーションでも存在する問題と、Android固有の問題とに分けて考えることにします。

余談になりますが、名前をつけて再利用することがアーキテクチャなのだとしたら、AndroidにおいてはWebアプリケーションで用いられている名前とは少し別のものをつけてサンプルなどを公開した方がよいのかもしれません。例えばMVCアーキテクチャは、Androidにおいては実装者によってそれぞれ別の仕上がりになるように思います。Clean ArchitectureはAndroid-CleanArchitectureとしてサンプルが公開されているので、同じ実装が想像できる良い例です。

Webアプリケーションでも存在する問題

まずは、Webアプリケーションにおける問題のうち、Androidにも持ち込めそうなものについて考えます。

関心の分離

最初の問題は、ビジネスロジックの関心事と、技術的な関心事が適切に分離されないことです。

特に、Androidのようなクライアントアプリケーションにおいては、UI(ユーザインタフェース)の表示ロジックと、その他のビジネスロジックが適切に分離されていなければ、クラスの肥大化などの問題が発生します。

テスティング

AndroidアプリケーションはJava(もしくはKotlin)で記述されていますが、Androidフレームワークに依存している部分は、JUnitなどによるJavaのユニットテストを実施することができません。

また、表示されているUIが正しいかどうかのテストでは、状態管理処理とUI表示の処理とを適切に分離しなければ、 その表示が正しく行われているかのテストを書くことは難しくなります。

複数人による開発

複数人で開発すると、どうしても個人による癖などが出やすくなります。「どの処理をどのクラスに書くべきか」といったことが、コードレビューの際に議論になることも多いでしょう。

また、GitHubなどでソース管理をしている際に、同じクラスを同時に編集したりしてコンフリクトが発生し、開発速度が低下するなどの問題もあるでしょう。

Android固有の問題

前節では一般的なWebアプリケーションでも起こりうる問題を検討しましたが、ここではAndroidフレームワークに起因する問題について考えてみます。

Activityの肥大化

Androidにおいては、Activity(Androidアプリ画面)が多くの役割を担うことができます。MVCアーキテクチャをAndroidに適用すれば、ViewとController両方の役割をこのActivityが担っていると考えることもできます。

小規模なアプリケーションならばActivityにそれらの処理を記述すればよいのですが、アプリケーションが大規模になればなるほどActivityクラスへの記述量が増え、いわゆるActivityの肥大化という問題が発生します。

肥大化したActivityは、開発者がソースコードから処理を追うのが難しく、開発速度の低下や、バグの発生要因にもなり得ます。

Activityのライフライクル

Activityは独自のライフサイクルを持っており、画面を回転したりすると再生成されます。その際に、Activityのクラスのインスタンスは一度破棄されて再び作られますが、この再生成を超えてデータや状態を保持する必要があります。

Activityには、そのための仕組みonSaveInstanceStateが備わっていますが、データの保存と復元の処理を適切に書く必要があり、コードが複雑になりがちです。

また、破棄される前のActivityへの参照を保持している場合や、コールバックなどで非同期処理を待っている場合は、それらがメモリリークの原因になり得ます。

Activity間のデータの同期

それぞれのActivityで実装された画面同士は独立しているため、それぞれのActivityが保持しているデータ間の同期を行う必要がある場合は、少し面倒です。

例えば、SNSアプリケーションでタイムラインから遷移した先の詳細画面で「いいね」ボタンが押されたときに、その「いいね」をタイムラインにも反映させる必要がある場合などです。

アーキテクチャをどのように選定するか?

これまで挙げたような問題が、これから開発するAndroidアプリケーションにどの程度存在するか? もしくは存在しないのか? そういったことを明確にして、アーキテクチャを選択する必要があります。例えば、1人で開発するプロジェクトであれば、複数人で開発するような問題は起きません。

また、導入しようとしているアーキテクチャがそれらの問題をどのように解決してくれるのか? についても、ある程度考えておく必要があります。

さらに、Clean Architectureのようなレイヤーが細分化されたアーキテクチャでは、関心の分離が適切になされますが、小規模なアプリケーションでは細分化を過剰に感じるかもしれません。複数の候補のなかから、自身のAndroidアプリケーションの問題を最もシンプルに解決できるアーキテクチャを選ぶのがよいでしょう。

したがってアーキテクチャを選定する際には、現在どのようなものが主に用いられているかを知る必要があります。次のセクションでは、現在のAndroidにおいて主流なアーキテクチャについて解説します。

現在の主流なAndroidアーキテクチャ

現在のAndroidアーキテクチャについて知るには、まず前述した「Android Architecture Blueprints」を見るとよいでしょう。Googleによって公開されているこのドキュメントでは、TODOアプリを題材に、さまざまなアーキテクチャによる実装サンプルが掲載されています。

ここでは、こういったドキュメントを題材にしつつ、まず現在のAndroidアーキテクチャ全体の傾向について説明し、 そこからMVP、Clean Architecture、MVVM、そしてFluxについて解説します。

現在のAndroidアーキテクチャの傾向

現在のAndroidアーキテクチャには、DDD(ドメイン駆動開発)の「レイヤードアーキテクチャ」、そして「リポジトリパターン」というデザインパターンが少なからず入っているように思います。これらのパターンを導入することによって、ビジネスロジックの関心事と技術的な関心事が分離されます。

その上で、MVPにおいてはPresenterというモジュールを追加したり、MVVMにおいてはViewModelというモジュールを追加したりして、それぞれさらなる問題を解決しています。

レイヤードアーキテクチャ

レイヤードアーキテクチャは、以下の図で表されるアーキテクチャです。「どのようなデータベースを使うか」「どのようにAPI通信を行うか」といった技術的関心事は、主にInfrastructure層へと分離されます。

f:id:blog-media:20171227012734p:plain

Clean Architectureにおいては、UI層をpresentation、Domain層をdomain、Infrastructure層をdataとそれぞれパッケージを作り、レイヤー化している実例が多くなっています。

また、その他のアーキテクチャに関しても、UI層をuiパッケージに、Infrastructure層をdataパッケージにレイヤー化した上で、MVVMやFluxなどのアーキテクチャを実現しているものが多いように思います。

DDDに関してここでは詳しく解説しませんが、次の記事が分かりやすいように思います。

リポジトリパターン

リポジトリパターンは、DDD以前に『Patterns of Enterprise Application Architecture』(Martin Fowler、2002年)というデザインパターンの書籍邦訳『エンタープライズアプリケーションアーキテクチャパターン』で提唱されたものです。

Androidにおいてのリポジトリとは、永続化(SQLiteなど)や外部データ(Retrofitなどを用いたAPI通信)への問い合わせを担うモジュールのことです。「Android Architecture Components」のリファレンスにも以下のように書かれています。

Repository modules are responsible for handling data operations. They provide a clean API to the rest of the app. They know where to get the data from and what API calls to make when data is updated. You can consider them as mediators between different data sources (persistent model, web service, cache, etc.).

(リポジトリモジュールはデータ操作に対して責任を負います。 それらはアプリケーションのその他のモジュールに対してクリーンなAPIを提供します。 データをどこから取得するべきか、データを更新する際にどのAPIを呼び出せばよいか、を知っています。 リポジトリは異なるデータ・ソース(永続化されたモデル、ウェブサービス、キャッシュ、など)間の仲介者とみなすことができます。)

「Android Architecture Blueprints」においても「Android Architecture Components」のサンプルにおいても、またその他の例に出てくるリポジトリクラスも、すべてここで定義された役割を担っています。

なお、DDDおよび『Patterns of Enterprise Application Architecture』のリポジトリパターンの詳細は、それぞれ以下の記事を参考にしてください。

[ 技術講座 ] Domain-Driven Designのエッセンス 第2回|オブジェクトの広場
「Repositories(リポジトリ)パターン」のセクションを参照
Repository
Martin Fowler氏のエントリーの日本語訳

MVPアーキテクチャ

「Android Architecture Blueprints」において、MVP(Model-View-Presenter)は最も基本となるアーキテクチャとして紹介されています。

MVPアーキテクチャは以下のような構成になっています。

f:id:blog-media:20171227012749p:plain

GitHub - googlesamples/android-architecture at todo-mvpより)

MVPアーキテクチャでは、PresenterとContractという2つのモジュールが登場します。ContractはPresenterとActivity/Fragmentとの間のやりとりを定義したもので、以下のようにインタフェースとして定義します。

interface TasksContract {

  interface View : BaseView<Presenter> {

    fun showTasks(tasks: List<Task>)

    fun showAddTask()

    fun showTaskDetailsUi(taskId: String)

    //略...
  }

  interface Presenter : BasePresenter {

    fun loadTasks(forceUpdate: Boolean)

    fun addNewTask()

    fun openTaskDetails(requestedTask: Task)

    //略...
  }
}

TasksContract.Viewが、Activity/FragmentなどのViewモジュールが実装すべきインタフェースで、Presenterから呼ばれることが想定される関数が定義されています。それらの関数の役割は主にUIの操作です。

逆にTasksContract.Presenterは、Presenterモジュールが実装すべきインタフェースで、Activity/Fragmentから呼ばれることが想定される関数が定義されています。 それらの関数はこれまでActivity/Fragmentがやっていたような処理のうち、UIの操作以外、すなわちデータの取得処理を呼び出す、データの保存処理を呼び出すなどの処理です。

そして、Presenterモジュールは、Contract.Presenterインタフェースの実装クラスということになります。

このようにActivity/FragmentからUIの処理以外の処理をPresenterへと分離し、さらにContractインタフェースを介してやりとりすることによって、以下のような問題を解決しようとしています。

ActivityやFragmentが肥大化しやすい問題
Presenterに処理を移すことによって、その肥大化を防ぐ
テスト(特にUI)が書きづらい問題
mockitoというライブラリを用いて、Presenterのテストを実施する

Clean Architecture

Clean Architectureは、Uncle Bob氏のブログ記事によって提唱された、以下のような図で表されるアーキテクチャです。

f:id:blog-media:20171227012753j:plain

The Clean Architecture | 8th Lightより)

その後、Androidのアーキテクチャに応用され、以下のリポジトリが公開されました。

また、Android Clean Architecture Boilerplateというリポジトリでは、Clean ArchitectureのKotlinでの実装サンプルを見ることができます。

「Android Architecture Blueprints」においても、MVPとClean Architectureを使ったサンプルが公開されています。このサンプルは以下のような構成になっています。

f:id:blog-media:20171227012801p:plain

GitHub - googlesamples/android-architecture at todo-mvp-cleanより)

Clean ArchitectureはDDDの考えに基いており、Dataレイヤー、Domainレイヤー、Presentationレイヤーに分かれています。

このうち、Domainレイヤーにビジネスロジックが記述され、MVPアーキテクチャと同様に、Activity/Fragmentの肥大化を防ぎ、また、テストが記述しやすいアーキテクチャになっています。

MVVMアーキテクチャ

MVVMは、Model-View-ViewModelの略で、「Android Architecture Components」がそのクラス名にViewModelという名前を採用したことから、MVVMアーキテクチャというとこのViewModelを使ったアーキテクチャを指すことが多くなったように思います。本記事ではこのViewModelを使ったMVVMアーキテクチャについて解説します。

MVVMアーキテクチャを採用したサンプルには以下のようなものがあります。

Samples for Android Architecture Components
todo-mvvm-databinding(Android Architecture Blueprints)
todo-mvvm-live-kotlin(Android Architecture Blueprints)

ModelやViewといった名前のレイヤーは存在せず、レイヤードアーキテクチャのようにData層、UI層と分かれていて、そこにViewModelというモジュールが追加されたサンプルになっています。

「Android Architecture Components」におけるViewModelは、以下の役割を持つモジュールとして位置づけられています。

The view model acts as an intermediary between the view and the model, and is responsible for handling the view logic. Typically, the view model interacts with the model by invoking methods in the model classes. The view model then provides data from the model in a form that the view can easily use. The view model retrieves data from the model and then makes the data available to the view, and may reformat the data in some way that makes it simpler for the view to handle. The view model also provides implementations of commands that a user of the application initiates in the view. For example, when a user clicks a button in the UI, that action can trigger a command in the view model. The view model may also be responsible for defining logical state changes that affect some aspect of the display in the view, such as an indication that some operation is pending.

(ViewModelは、ViewとModelとの間の仲介者として機能し、viewロジックの処理を担当します。 通常、ViewModelはModelクラス内のメソッドを呼び出すことによって、Modelと対話し、ModelからのデータをViewが容易に使える形でViewへと提供します。 Modelからデータを取得し、Viewで使用できるようにし、Viewが取り扱いやすい様に再フォーマットすることもできます。 そして、Viewのなかでアプリケーションのユーザが始めるコマンドの実装も提供しています。 例えば、ユーザがUI上でボタンをクリックすると、そのアクションがViewModel内のコマンドのトリガーとなり得ます。 また、ある処理が実行中であることを指すような、View内の表示に何らかの影響を及ぼす論理状態を定義する責任を負うこともできます。)

The MVVM Patternより)

ViewModelに処理を委譲することによって、Activity/Fragmentの肥大化を防ぐこともできますが、このアーキテクチャはActivityの独特なライフサイクルによる問題を解決するためのものです。

ViewModelは次の図にあるように、独自のライフサイクルを持っています。画面の回転などによってActivityが再生成される場合でも、 そのライフサイクルを超えてインスタンスが生存し、状態を保持することができます。

f:id:blog-media:20171227012744p:plain

ViewModel | Android Developersより)

「Android Architecture Components」のViewModelに何を書くべきかについては、GoogleのLyla Fujiwara氏による以下の記事に詳しく書かれています。

MVVMアーキテクチャ(Android Architecture Components)を利用したアーキテクチャの具体的な実装に関しては、冒頭でも紹介した拙文をご覧ください。

Fluxアーキテクチャ

Fluxアーキテクチャは、Facebookが提唱したWebアプリケーションで用いられているアプリケーション設計です。

Flux | Application Architecture for Building User Interfaces

Action、Store、Dispatcherという3つのモジュールを中心として、以下の図のような一方向のデータフローが特徴となっているアーキテクチャです。

f:id:blog-media:20171227012739p:plain

Flux | Application Architecture for Building User Interfacesより)

このFluxアーキテクチャをAndroidにも応用しようと、「Flux Architecture on Android」というブログ記事と、同じ作者によるandroid-flux-todo-appというリポジトリが2015年に公開されました。

現在のところ「Android Architecture Blueprints」にFluxアーキテクチャによるサンプルはありませんが、日本国内においては株式会社AbemaTVが提供するAbemaTVアプリや、株式会社エウレカが提供するPairsアプリで、Fluxアーキテクチャが採用されています。

Flux de Relax :) // Speaker Deck

FluxアーキテクチャのAction、Store、Dispatcher、Viewという4つのモジュールは、それぞれ以下のような役割を担っています。

モジュール 役割
Action 特定の要求(Viewからのユーザ入力など)を処理し、その結果となるデータをStoreへと伝達する
Dispatcher ActionをStoreへと伝達する
Store Dispatcherから送られたActionおよびそれが含むデータに応じて自身の状態を変更し、保持する
View Storeの状態に応じて結果などを画面に表示する

Viewモジュールは、Androidにおいては、ActivityやFragmentがその役割を担うことになります。

  • Action→Dispatcher→Store→Viewという単一方向にデータが流れること
  • Storeのみがアプリケーションの状態を保持するということ

この2点がFluxアーキテクチャの特徴で、これには以下のようなメリットがあります。

  • モジュールごとに責務が明確に分かれており、特定の処理をどこに書くか明確になる
  • 状態の管理がStoreへと一元化されるため、Activity/Fragmentから複雑な状態管理ロジックを分離することができる

まとめ

アーキテクチャとはデザインパターンであり、どのパターンを選択するかには絶対の正解はなく、それぞれ解決しようとしている問題が異なります。アーキテクチャを選定する際は、まずはアプリケーションの規模やチームの状況を鑑みて、解決すべき問題について考えてみるとよいでしょう。

また、現在主流となっているアーキテクチャはそれぞれ解決している問題が異なり、それらのアーキテクチャを選ぶ前に、どのような問題を解決しているのか、そして解決できていない問題点は何なのかについて把握しましょう。

アーキテクチャはAndroidアプリケーションを作る上での問題を解決してくれる強力なツールです。チームメンバーや、気が置けないエンジニア同士でより活発に議論を重ねることで、さらに良いものへと昇華させることができるでしょう。本記事がその議論の一助となれば幸いです。

参考資料

ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か[DDD] - little hands' lab
Guide to App Architecture | Android Developers
AndroidではMVCよりMVPの方がいいかもしれない - Konifar's WIP
持続可能な開発を目指す ~ ドメイン・ユースケース駆動(クリーンアーキテクチャ) + 単方向に制限した処理 + FRP - Qiita
大規模Webアプリケーションにおける複雑性とアーキテクチャ設計に関する一考察 - Qiita
ドメインモデル中心のアーキテクチャ | DevTab - 成長しつづけるデベロッパーのための情報タブロイド
FluxとDDD(レイヤードアーキテクチャ)について考えてみた - embryo
なぜDDD初心者はググり出してすぐに心がくじけてしまうのか - little hands' lab

執筆者プロフィール

藤原 聖(ふじわら・さとる) / satorufujiwara / satorufujiwara

名前
サイバーエージェントのAndroidエンジニア。
ペコリ、Amebaアプリ、FRESH!のAndroidアプリやエンジニアリーダーなどを担当し、現在はエンジニアリングマネージャーとしてエンジニアの組織作りを担当。
共訳書に『Kotlinイン・アクション』(マイナビ出版、2017年)。近著に『Android アプリ設計パターン入門』(TechBooster、2018年)。

関連記事