4
6

More than 1 year has passed since last update.

[Android]忙しい人のためのアーキテクチャガイド~Domain Layer編~

Posted at

はじめに

この記事は、シリーズ作です。

今回のDomain Layer編は、元々の分量が少ないです。
スクロール数は元と変わらないかもですが、文字数は 1 / 3 くらいにまで縮められたと思います。

目次

  • Domain Layerとは?
  • Domain Layerのメリット
  • 他の責務との関係
  • 呼び出し
  • ライフサイクル
  • スレッド周り
  • Domain Layerの使われ方

Domain Layerとは?

Domain Layerのメリット

Domain Layerに責務を切り分けるメリットは

  • コードの重複を回避
  • ViewModelの肥大化を回避
  • アプリのテスタビリティが向上
  • 大規模なクラスを回避

他の責務との関係

Domain Layerは、ViewModelとRepositoryの間に入ります。
Domainクラスは、単一の機能に対して責務を持ます。
また、再利用可能なので、あるユースケースが他のユースケースに依存することもあります。

document
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) { /* ... */ }
document
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }

呼び出し

ユースケースは、オーバーロードを用いることで関数であるかのように呼び出すことができます。
Kotlinでオーバーロードを定義するには、operatorキーワードを用います。

document
class FormatDateUseCase(userRepository: UserRepository) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance()
        val todaysDate = formatDateUseCase(today)
        /* ... */
    }
}

ライフサイクル

ユースケースは、自身のライフサイクルを持たず、呼び出し元のスコープで生存します。
また、可変データを取る設計をしないので(すべきではない)、毎回新しいインスタンスを呼び出します。

スレッド周り

ユースケースのロジックは、長時間操作の可能性があるので、メインスレッドから安全に呼び出せるようにしないといけません。
キャッシュなど多くのリソースを消費する計算処理は、Data Layerの責務なので混在しないようにしましょう。

document
class MyUseCase(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {

    suspend operator fun invoke(...) = withContext(defaultDispatcher) {
        // Long-running blocking operations happen on a background thread.
    }
}

Domain Layerの使われ方

同じことの繰り返しになりますが、念の為。

再利用性

ViewModelで同じようなロジックが定義されている場合、ユースケースに集約させることで再利用性と個別のテストを実現します。
また、集約されているので、変更時は一箇所のみで済みます。

複数のRepositoryが絡むロジック

図のGetLatestNewsWithAuthorsUseCaseは、二つのRepositoryを用いたロジックになっています。

image.png

下記のユースケースが入ると思うと、かなりViewModelがスッキリしますね。

/**
 * This use case fetches the latest news and the associated author.
 */
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend operator fun invoke(): List<ArticleWithAuthor> =
        withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews()
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            // This is not parallelized, the use case is linearly slow.
            for (article in news) {
                // The repository exposes suspend functions
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

おわりに

いかがだったでしょうか。
これにて、全ての項目を読了いたしました。

お疲れ様でした。

参考

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6