Kotlinにどっぷりハマっている岩下です。最近チーム内で採用することが多くなっているClean ArchitectureをAndroidアプリにも採用しよう!という流れに乗って、色々と模索を重ねながら1つのアプリを作り終えたところです。その中で色々と見えてきたものを、今回から複数回に分けてサンプルアプリCleanRecordsの実装を通じてご紹介していきたいと思います。実装をベースに紹介しますので、Clean Architecture自体をご存知ない方にもわかりやすい内容になっている(はず)です。ご参考になれば幸いです。

※ Clean Architectureとは何ぞや?という詳しい話は本稿ではいたしませんので、Uncle Bobのページや他の記事を見ていただければと思います。

キーワード:AndroidClean ArchitectureKotlin

はじめに

まず、第1回となる今回はソースコードを出さずに、CleanRecordsの全体構造およびそれぞれの役割をご紹介させていただきます。

CleanRecordsは、CleanStoreというiOS(Swift)向けサンプルアプリを参考に、Android(Kotlin)向けならどうなるか?と考えて作ったものになります。こちらのCleanStoreを参考にした大きな理由は、Clean ArchitectureにおけるPresentation層とDomain層をSceneというくくりでひとまとめにすることで、画面遷移が多くなりがちなネイティブアプリによりマッチしていると感じたためです。

構造

早速ですが、今回作成したCleanRecordsの構造について紹介したいと思います。構造は下図のようになっています。

Clean-Records-5

全体は、複数のSceneProviderModelServiceから構成されています。

また、Clean Architectureの説明の上では欠かせない例の図を下記に掲載します。

CleanArchitecture

この図において最低限抑えておくことは、「円の内側のものは外側のものを知ってはいけない」というルールがあることです。

以下、それぞれの構成要素について説明していきます。

Scene

SceneはFragment(またはActivity)・PresenterInteractorRouterModelsの5つからなり、1Sceneはユーザーから見た1つの場面に相当します。1Scene=1画面ではなく、1画面が複数のSceneを持っているイメージになります。また、Fragment(Activity)の代わりにServiceを持たせることで、画面のないバックグラウンドサービスを実現する場合にも適用可能です。

※ CleanStoreでは前述の5つに加えWorkerが存在しますが、必須ではないため省略しています。

Fragment (Activity)

iOSアプリでのViewControllerに相当します。通常のAndroidアプリには必ずActivityが1つ以上必要ですが、Activityは値の受け渡しや起動方法にクセがあるため、基本的に1Activity、多Fragmentで構成するのが良いと思います。この1Activityが共通部としての処理を実施し、後述のRouterが各Scene(Fragment)を切り替えるイメージになります。

※ 以降、単にFragmentと記載します。

FragmentはControllerとしての役割とViewの操作の大きく2つの役割を持ちます。この2つが混在するとFragmentが複雑化するため、後者をDisplayLogicとして分離し、Fragmentがその実装を持つ形になります。

また、BusinessLogicRoutingLogicおよびDataPassingへの参照を持っており、Controllerとしてそれぞれビジネスロジックの発火やScene切替のトリガーを引く役割を担います。

例えば、「ユーザーがボタンを押したらデータを外部から取得して表示更新」というような処理を実現する場合、

Fragment -> Interactor(BusinessLogic) -> Provider(Service) -> Interactor(BusinessLogic) -> Presenter(PresentationLogic) -> Fragment(DisplayLogic)

という風に、FragmentからFragmentへぐるっと一周するような流れになります。FragmentはViewを持っていますが、直接Viewの操作はせずに、View操作のためのInterface(DisplayLogic)を用意してPresenterがそれを叩く、といったところが、いわゆる普通に作った場合と大きく異なる部分になります。

FragmentがSceneの核となるため、Fragmentの生成時にPresenter・Interactor・Routerを生成し、それぞれに依存関係を注入します。

Presenter

PresenterはPresentationLogicの実装であり、DisplayLogicへの参照を持っています。Interactorから受け取ったデータをDisplayLogic用に整形し、View操作のトリガーを引くのが主な役割となります。

Interactor

InteractorはBusinessLogicDataStoreの実装で、PresentationLogicへの参照を持っています。また、Providerの実体を持っており、BusinessLogicの中で適宜Providerを経由してデータのやり取りを行い、結果をPresentationLogicに返します。

BusinessLogicは名前の通りビジネスロジックに該当する部分です。DataStoreは、Service内のDataStoreとは異なり、Sceneのライフサイクルと同じタイミングで生成・消去される一時的なものです。BusinessLogicでの結果格納や、別Sceneとの値の受け渡しなどに使用します。

Router

RouterはRoutingLogicDataPassingの実装で、FragmentとDataStoreへの参照を持っています。別Sceneへの遷移とデータを渡す役割を担います。

Scene遷移の際はFragmentからRoutingLogicがコールされ、別SceneのFragmentを生成します。データを渡す際は、そのFragmentのDataPassingを経由してDataStoreにアクセスします。

Models

ModelsはScene内で用いるデータモデル群で、ユースケースごとにRequest Response ViewModelの定義を持っています。それぞれ、下記でのやり取りに使われます。

  • Request: Fragment -> BusinessLogic
  • Response: Interactor -> PresentationLogic
  • ViewModel: Presenter -> DisplayLogic

1つのユースケースが、この一連のやり取りをまとめたものに相当します。

Provider

Providerは、Clean Architectureにおいて、Adapter層のGatewayに相当するものです。
ProviderはRepositoryパターンを実現するためにSceneとServiceの中間に位置し、StoreInterfaceへの参照を持ちます。これは、InteractorによってDIされます。

CleanRecordsでは、1 Providerが1 StoreInterfaceを持つようにしていますが、1 Providerが複数のStoreInterfaceを持っても良いと思います。また、Serviceによって同期・非同期が変わるのをどこで吸収するのかも考えどころですが、CleanRecordsではProviderの処理を「同期を前提とする場合は同期」とある程度決め打ちしてしまっています。

※ CleanStoreではWorkerという名前ですが、より役割が明確になるようProviderに名前を変えています。

Model

Modelは、Clean Architectureの円で一番内側のEntityに相当します。主にPresenter-Interactor-Provider-Service間のやり取りに使われるデータのモデルになります。

Service

Serviceは、Clean Architectureの円で一番外側のFramework層に位置するもの全般に相当します。ただし、今回のAndroidアプリのUIのように、差し替えを考慮しないFrameworkについては必ずしもこの層に位置する必要はありません。CleanRecordsでは、ServiceはStoreInterfaceの実装で、データベースやWebAPI、デバイス情報など、外部から取得するすべてのデータとのやり取りを行います。

おわりに

今回は、CleanStoreをベースにした、Android版のClean Architectureの一例の全体構造について紹介しました。今回はざっくりとした話になりましたが、次回からはサンプルアプリCleanRecordsのコードを見ながら、実際の処理がどのような流れで行われるのかを説明していきたいと思います。

CleanRecordsのソースコードはこちら

© 2018. SuperSoftware Co., Ltd. All Rights Reserved.