概要
モバイルアプリに対してクリーンアーキテクチャを適用した具体例に関する記事が少ないなぁと感じておりました。そのため、本記事では個人的に思うMVVM×クリーンアーキテクチャの詳細を記載します。
前提
-
モバイルアプリ開発におけるアーキテクチャを想定
- 特に、AndroidかつJetpackComposeの使用を想定
- DIの利用も想定
-
APIサーバを持ち通信を行ってデータを取得する
-
アプリ内のローカルなデータストアも利用する
結論
MVVMにクリーンアーキテクチャを組み込んだ図が次のようになることを想定しています。
グルーピングされたものが、パッケージとして分ける想定です。
(矢印は依存方向を示しており、点線はインターフェースに対する具体実装を示しています。)
設計意図
テスタブルなコード
Model内にあるものをinterfaceを使って表現しているため、それぞれのパッケージのテストがしやすくなります。
UIプレビューの表示ができるようになる
JetpackComposeではUIのプレビュー機能がありますが、UIが変な依存関係を持っていることでプレビューがエラーを起こすケースが多々あります。これは Repository層が具体実装に依存するため であるケースがほとんどなのですが、本アーキテクチャではそれをダミーに差し替えやすい構造になっているため、プレビューの表示が簡単になります。
メンテナンスがしやすい
パッケージの粒度を割と細かくしているため、メンテナンスや変更は比較的に行いやすいと思います。
構成要素
APP
パッケージ間のDI(Dependency Injection) をするために必要です。あくまでDIをするためだけに留めておくと良いです。
UI
言わずもがな。ViewとViewModelによる構成となります。
Core
CoreにはUsecaseとIRepository(Repositoryのインターフェース)を定義しています。IResourceとILocalDataStoreをこちらに入れているのは依存性の逆転を行うことで、Domain層が最上位となるようにするためです。
Repository
ここでいうRepositoryとはデータがある場所全般を示しています。つまりアプリ内で扱うデータでもAPIで取得するデータも関係なく、UseCaseから見ればここから全て持ってくるようになります。
-
Resource
Repositoryの中における、外部からとってくるデータ を示します。つまり、API通信によるデータを保持するパッケージとなります。 -
LocalDataStore
Repositoryの中における、内部からとってくるデータ を示します。つまり、デバイスで保持する永久的なデータはここからとってきます。
サンプルコード
KMP(Kotlin Multiplatform) を使ったアプリなので初めての方からすると多少複雑ですが実際に同じコードで両OSストアに出している簡易アプリのソースを公開しています。CompleteOnboardingUseCase というファイルが存在するのでそれを追っていくと理解しやすいかと思います。
- iOS:https://apps.apple.com/jp/app/%E8%B3%BC%E5%85%A5%E8%A8%BA%E6%96%AD/id6755901351
- Android: https://play.google.com/store/apps/details?id=site.gritup.buyornot&hl=ja
小規模な個人アプリなのでこれまでの説明と違う点としては,
- UIパッケージは存在せず、 composeAppがAPP&UIパッケージを担っている
- いくつかのUseCaseはServiceを参照している
- APIを使用していないので、Rsourcesは存在しない
予告なく上記のレポジトリはプライベートにするかもしれません。
設計におけるよくある疑問
ViewModelとUsecaseのちがいは?
・ViewModelはあくまで画面の操作、文字変換などそのScreen特有のUIに関するロジックと状態管理
・UsecaseはRepositoryから受け取るデータ加工、調整、計算等
Usecaseを作るのは冗長になるだけでは?
冗長になるケースは確かに多いが、分けておくべきだと思います。
- あとから同じUsecaseの内容を使う事が出てきたときに、同じロジックがViewModelにコピペで量産されることになりメンテナンスが大変になるためです。
さらにいうと、冗長なのは実はViewModelの方だと思っています。(後述あり)
UseCaseが他のUsecaseを使って良いのか?
個人的にはYes。ただし、代替として、HogeSeerviceなどさらに上位のものを作ってそれをつかうのも良いと思います。
ViewModelが複数のUsecaseを使って良いのか?
Yes。必要になるケースがあるので、むしろその時にこのアーキテクチャの強みが生かされると思います。
UsecaaeはRepositoryを複数取ってくるのは良いのか?
Yes。複数のRepositoryをとってくるケースの方が多い印象です。
DIはパッケージ的にどこで行う?
可能な限り、同一パッケージ内、パッケージを跨ぐ場合は全てを包括する(依存する)APPパッケージで行います。ただし、そのためにわざわざAPPを作るのも手間な場合はUIパッケージが全てに依存してその役割を担っても良いと思います。
気になっていること
実はViewModelいらない説
これは宣言的UIを使って上記のアーキテクチャに沿って作成すると、ViewModelはほとんどすることがないことに気づくと思います。
ViewModelの責務は"Viewの状態を管理する" と思っていますが、宣言的UIのフレームワークを使う場合は大した実装にはならないため、これを分ける意義があまりないようにも感じています。
(ちなみに私は個人開発のアプリではviewModelを使っていません。)
こちらの記事で言及されている内容に近いのかなぁと思っています。
Domain層とSerivceの違い
一般的な(?)クリーンアーキテクチャに則るのならば,UsecaseはService層に該当すると思います。つまりDomain層にはEntityが,Service層にはUsecaseが該当すると思いますが、モバイルアプリがビジネス的なDomainを持っているのか? と考えた時にこれはYesとは言えない気がします。
そのため、Android Develop公式においてもDomainの中にはEntityなどが記載されておらず、Usecaseとしてひっくるめられているのかと思っています。
よって、今回紹介したアーキテクチャは、あえてDomainとSrviceを分けることなくDomainと一括りにしてUsecaseを入れています。
まとめ
MVVMでクリーンアーキテクチャによる設計がベターだと思っています。一方で、ViewModelに関しては実はなくても問題なさそうな気はしています。
参考文献
- ドメイン駆動設計入門
- Android Develop公式のドメインレイヤについて
- nowinadnroidのレポジトリ
- ソフトウェアアーキテクチャの基礎
- Clean Architecture 達人に学ぶソフトウェアの構造と設計
変更履歴
- 2025/12/6: パッケージ依存図の変更, 参考コード参照先を追加
