#はじめに
この記事は、シリーズ作です。
今回のDomain Layer編は、元々の分量が少ないです。
スクロール数は元と変わらないかもですが、文字数は 1 / 3 くらいにまで縮められたと思います。
#目次
- Domain Layerとは?
- Domain Layerのメリット
- 他の責務との関係
- 呼び出し
- ライフサイクル
- スレッド周り
- Domain Layerの使われ方
##Domain Layerとは?
https://qiita.com/reo-androider/items/1ca168e290568a578cd6#domain-layer
##Domain Layerのメリット
Domain Layerに責務を切り分けるメリットは
- コードの重複を回避
- ViewModelの肥大化を回避
- アプリのテスタビリティが向上
- 大規模なクラスを回避
##他の責務との関係
Domain Layerは、ViewModelとRepositoryの間に入ります。
Domainクラスは、単一の機能に対して責務を持ます。
また、再利用可能なので、あるユースケースが他のユースケースに依存することもあります。
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository
) { /* ... */ }
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
##呼び出し
ユースケースは、オーバーロードを用いることで関数であるかのように呼び出すことができます。
Kotlinでオーバーロードを定義するには、operator
キーワードを用います。
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の責務なので混在しないようにしましょう。
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を用いたロジックになっています。
下記のユースケースが入ると思うと、かなり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
}
}
#おわりに
いかがだったでしょうか。
これにて、全ての項目を読了いたしました。
お疲れ様でした。
#参考
https://developer.android.com/jetpack/guide/domain-layer