の続きです。
用語整理
(通常想像するドメインだと例えばニュースアプリではArticleクラスとかが入っているやつを想像すると思うんですが、ここではUseCaseが入っているっぽいです)
ドメインレイヤーとは?
The domain layer is an optional layer that sits between the UI layer and the data layer.
UIとデータレイヤーの間にある任意のレイヤー。
https://developer.android.com/jetpack/guide/domain-layer?hl=en より
A domain layer provides the following benefits:
It avoids code duplication.
It improves readability in classes that use domain layer classes.
It improves the testability of the app.
It avoids large classes by allowing you to split responsibilities.
メリット
- コードの重複を防ぐ
- ドメインレイヤーのクラスを使うクラスの可読性の向上
- アプリのテスト可能性の向上
- 責任を分割することにより大きなクラスを防ぐ
In a typical app architecture, use case classes fit between ViewModels from the UI layer and repositories from the data layer. This means that use case classes usually depend on repository classes, and they communicate with the UI layer the same way repositories do—using either callbacks (for Java) or coroutines (for Kotlin). To learn more about this, see the data layer page.
UseCaseのクラスはViewModelとRepositoryの間に入るため、Repositoryに依存し、ViewModelとやり取りをする。coroutinesなどを使う。
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository
) { /* ... */ }
複雑な複雑な処理をしたり再利用性を高める必要がある場合など、ビジネスロジックを使いまわしたりカプセル化したりしたい場合にのみ、ドメインレイヤーを作成する
The domain layer is responsible for encapsulating complex business logic, or simple business logic that is reused by multiple ViewModels. This layer is optional because not all apps will have these requirements. You should only use it when needed-for example, to handle complexity or favor reusability.
なぜ?
すべてのアプリではこのカプセル化や再利用性が必要がないため。
各UseCaseは単一な機能のみに責任を持つ
To keep these classes simple and lightweight, each use case should only have responsibility over a single functionality, and they should not contain mutable data. You should instead handle mutable data in your UI or data layers.
なぜ
クラスをシンプルかつ軽量に保つため。
各UseCaseは可変データを持ってはならず、代わりにデータレイヤーやUIで可変データを持つ
To keep these classes simple and lightweight, each use case should only have responsibility over a single functionality, and they should not contain mutable data. You should instead handle mutable data in your UI or data layers.
なぜ
クラスをシンプルかつ軽量に保つため。
UseCaseはUseCaseから再利用してもよい
Because use cases contain reusable logic, they can also be used by other use cases. It's normal to have multiple levels of use cases in the domain layer. For example, the use case defined in the example below can make use of the FormatDateUseCase use case if multiple classes from the UI layer rely on time zones to display the proper message on the screen:
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
// 他のUseCaseに依存
private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
https://developer.android.com/jetpack/guide/domain-layer?hl=en より
なぜ?
UseCaseは再利用可能なロジックが入っているため。ドメインレイヤー内に複数レベルのUseCaseがあることは一般的であるため。
UseCaseのライフサイクルは使う側のスコープとなる
Use cases don't have their own lifecycle. Instead, they're scoped to the class that uses them. This means that you can call use cases from classes in the UI layer, from services, or from the Application class itself. Because use cases shouldn't contain mutable data, you should create a new instance of a use case class every time you pass it as a dependency.
なぜ?
UseCaseはUIレイヤーのServiceやApplicationクラスなどどこからでも呼び出せるため。
UseCaseはメインセーフ(MainThreadから呼び出しても安全)である必要がある
Use cases from the domain layer must be main-safe; in other words, they must be safe to call from the main thread. If use case classes perform long-running blocking operations, they are responsible for moving that logic to the appropriate thread. However, before doing that, check if those blocking operations would be better placed in other layers of the hierarchy. Typically, complex computations happen in the data layer to encourage reusability or caching. For example, a resource-intensive operation on a big list is better placed in the data layer than in the domain layer if the result needs to be cached to reuse it on multiple screens of the app.
なぜ?
UseCaseで長い時間がかかるロジックを行う場合に適切なスレッドに移行させる責務がUseCaseにはあるため。
通常ドメインレイヤー(UseCase)ではなくデータレイヤーでスレッドの変更を行う
Use cases from the domain layer must be main-safe; in other words, they must be safe to call from the main thread. If use case classes perform long-running blocking operations, they are responsible for moving that logic to the appropriate thread. However, before doing that, check if those blocking operations would be better placed in other layers of the hierarchy. Typically, complex computations happen in the data layer to encourage reusability or caching. For example, a resource-intensive operation on a big list is better placed in the data layer than in the domain layer if the result needs to be cached to reuse it on multiple screens of the app.
なぜ?
通常、複雑な計算処理はデータレイヤーで行われるため。
UIに存在する繰り返し使われるビジネスロジックをUseCaseにカプセル化すべき
Reusable simple business logic
You should encapsulate repeatable business logic present in the UI layer in a use case class. This makes it easier to apply any changes everywhere the logic is used. It also allows you to test the logic in isolation.
Consider the FormatDateUseCase example described earlier. If your business requirements regarding date formatting change in the future, you only need to change code in one centralized place.
なぜ?
ロジックが使われるどこからでも変更を適応ができる。テストができ、ロジックを独立させられる。
例えば前のFormatDateUseCaseの例ではUIに書いてあると色んな場所で直さなくてはいけなくなる。
UtilよりもUseCaseを好む
Note: In some cases, logic that can exist in use cases could instead be part of static methods in Util classes. However, the latter is discouraged because Util classes are often hard to find and their functionality is hard to discover. Furthermore, use cases can share common functionality such as threading and error handling in base classes that can benefit larger teams at scale.
なぜ?
Utilクラスは見つけにくくなることがある。またUseCaseはベースクラスを使ってスレッドやエラーハンドリングも共有できるため、大きなチームでは恩恵を受けやすい。
複数のRepositoryからデータをまとめるUseCaseを作っても良い
Because the logic involves multiple repositories and can become complex, you create a GetLatestNewsWithAuthorsUseCase class to abstract the logic out of the ViewModel and make it more readable. This also makes the logic easier to test in isolation, and reusable in different parts of the app.
なぜ?
複数のリポジトリを呼び出すものは複雑になりえるため。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
}
}
UseCaseはUI以外でも、他のプラットフォームでも共有して良い
Other consumers
Apart from the UI layer, the domain layer can be reused by other classes such as services and the Application class. Furthermore, if other platforms such as TV or Wear share codebase with the mobile app, their UI layer can also reuse use cases to get all the aforementioned benefits of the domain layer.
なぜ?
他の部分でもドメインレイヤーの前述のすべての利点を得ることができるため。
ドメイン層のテストではfakeのリポジトリを使うと良い。
General testing guidance applies when testing the domain layer. For other UI tests, developers typically use fake repositories, and it's good practice to use fake repositories when testing the domain layer as well.
なぜ?
fakeを使うのは良い習慣とされているため。