
みなさまこんにちは〜!
メモリアインクのすだです。
本日は、
Androidアプリ開発における「クリーンアーキテクチャ」の基本について、実際のコードを用いて解説していきます!
この記事を読んでわかること…
・クリーンアーキテクチャについて
・クリーンアーキテクチャでの実装方法
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
クリーンアーキテクチャとは?
クリーンアーキテクチャとは、ソフトウェア開発において「依存関係を内側に向かわせ、ビジネスロジックを守る構造」のことです。
「データ処理」「表示処理」「外部サービス」などを明確に“仕切り”で分けることで、
・変更に強く
・試しやすく
・他のアプリでも使い回せる
という設計になるのがクリーンアーキテクチャです。
MVPやMVVMが「UIの構造に関する設計パターン」であるのに対し、
クリーンアーキテクチャは「アプリ全体の依存関係と責務分離のための原則」ということになります。


画像引用元:https://github.com/ESchouten/CleanArchitecture
これだけだと少しわかりづらいかと思いますので
より詳細に説明します。↓
特徴①:依存関係を“内側”に向かわせる
クリーンアーキテクチャでは、「重要なロジック(ビジネス処理)を一番内側に置く」という考え方があります。
[ UI / API / DB ] ← 外側(変更されやすい)
↓
[ UseCase(ユースケース) ] ← 中間
↓
[ Domain(ビジネスロジック) ] ← 内側(絶対に守りたい)
これは、外側の変更(UIのデザインが変わる、APIの仕様が変わる)に内側が影響されないようにするためです。
(内側はアプリの“本質”なので、簡単に壊れない構造にすることが重要 という考え)
内側に行くほど“純粋な処理”があり、外側に行くほど技術的な依存(Android, Retrofit, Roomなど)を含みます。
特徴②: プレゼンテーション(表示)とドメイン(中身)を分離
クリーンアーキテクチャは、以下のように 主に3レイヤー(Presentation, Domain, Data)に分けて実装します。


画像引用元:https://qiita.com/koutalou/items/07a4f9cf51a2d13e4cdc
<外側>Presentationレイヤー:
・View(ActivityやFragment)
はユーザーと直接関わる画面部分。
・Presenter
はAndroid依存を減らすための中間役(MVVMならViewModel)
<中心>Domainレイヤー:
・UseCase
はアプリの振る舞いそのもの。
・Model(Translator)
はData層から取得したEntityをView向けに加工する場合に使用される中間変換役
<外側>Dataレイヤー:
・Repository
はDomainが依存する唯一の外部接点。
・DataStore
はAPIやDBなど具体的な実装場所。Entity
はアプリ内部で共通して扱う純粋なデータ型
MVVMやMVPを取り込むパターンも
クリーンアーキテクチャは 以下のように
UI構造の設計にMVVMやMVPというアーキテクチャを取り入れるケースもあります。
MVP / MVVM(UI設計の話)
└─ View
└─ Presenter / ViewModel(UIロジック)
クリーンアーキテクチャ(アプリ全体の構造)
├─ Presentation Layer(MVPやMVVMを内包)
├─ Domain Layer(ビジネスロジック)
└─ Data Layer(API・DB・キャッシュなど)
MVVMやMVPの設計に関しては、以下の記事で詳細を説明しておりますので、ぜひ合わせてご覧ください!




クリーンアーキテクチャを採用するメリットとデメリット
クリーンアーキテクチャを採用するにあたり、メリットとデメリットが以下のように存在します。
メリット
・責任が明確になり、大規模開発でもコードが破綻しにくい
・UIとビジネスロジックの分離によりテストがしやすい
・Android固有技術(Activity, Retrofit, Room等)に依存しない純粋なロジックが書ける
デメリット
・ファイルやクラスが増えるので設計の理解が必要
・小規模アプリだと構造が大げさになることがある
→これらのことから、クリーンアーキテクチャの採用に向いているのは、
機能や画面数が多い「中〜大規模な開発」、かつ長期的な運用を前提としたアプリであると言えるでしょう。
サンプル処理で学ぶMVP実装(Kotlin)
それでは、クリーンアーキテクチャを採用して「画面にあるボタンを押すとユーザー名を取得して表示するる」という簡単な処理を作ってみます。
図でまとめると以下のような構成になるようにします。
Presentation Layer
├─ MainActivity (View)
└─ MainViewModel (ViewModel)
Domain Layer
├─ GetUserNameUseCase (UseCase)
└─ UserModelTranslator (Model / Translator)
Data Layer
├─ UserRepository (interface)
├─ UserRepositoryImpl (実装)
├─ UserDataStore (APIやDBのような実装)
└─ UserEntity (Entity)
Presentation Layer(View / ViewModel)
Activity
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 依存関係を手動で注入(本来はDIを使う)
val dataStore = UserDataStore()
val repository = UserRepositoryImpl(dataStore)
val translator = UserModelTranslator()
val useCase = GetUserNameUseCase(repository, translator)
viewModel = MainViewModel(useCase)
val button = findViewById<Button>(R.id.button)
val textView = findViewById<TextView>(R.id.textView)
button.setOnClickListener {
viewModel.loadUserName()
}
viewModel.userName.observe(this) { name ->
textView.text = name
}
}
}
↑ここでユーザーのアクションをクリックリスナーで検知し、ボタン押下後の処理をViewModelに委ねます。
LiveData(ViewModel)を監視して、「データの変更があれば行う処理」を記述します。
ViewModel
class MainViewModel(
private val useCase: GetUserNameUseCase
) : ViewModel() {
private val _userName = MutableLiveData<String>()
val userName: LiveData<String> get() = _userName
fun loadUserName() {
val model = useCase.execute()
_userName.value = model.displayName
}
}
↑ViewModelはUIの状態(トーストメッセージ)をLiveDataで持ちます。
ボタンが押されたらModelからデータを取得し、Viewに通知します。
Domain Layer(UseCase&モデル変換)
Model(UI表示用データ)
data class UserModel(val displayName: String)
ModelTranslator
class UserModelTranslator {
fun translate(entity: UserEntity): UserModel {
return UserModel(displayName = "ユーザー名:${entity.name}")
}
}
UseCase
class GetUserNameUseCase(
private val repository: UserRepository,
private val translator: UserModelTranslator
) {
fun execute(): UserModel {
val entity = repository.getUser()
return translator.translate(entity)
}
}
↑取得したデータ(Entity)を、表示用の形式(UserModel)に変換して返している処理です。
Data Layer(データ層)
Entity
data class UserEntity(val id: Int, val name: String)
DataStore
class UserDataStore {
fun fetchUser(): UserEntity {
// 本来はAPIやDB呼び出しなどをここに
return UserEntity(id = 1, name = "太郎")
}
}
Repository
interface UserRepository {
fun getUser(): UserEntity
}
RepositoryImpl
class UserRepositoryImpl(
private val dataStore: UserDataStore
) : UserRepository {
override fun getUser(): UserEntity {
return dataStore.fetchUser()
}
}
まとめ
おつかれさまでした。いかがでしたでしょうか!
用意するclassが多くて大変と感じる人は、最初は「UseCase」「ViewModel」「Repository」の3層だけでも十分です。
大切なのは、ドメインを“ビジネスロジックの中心”とする考えを持つということ。
Viewがロジックを持たないだけでもアプリの質は大きく上がるので、ぜひ開発に取り入れてみてください!



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント