Lapisan domain adalah lapisan opsional yang berada di antara lapisan UI dan lapisan data.
Lapisan domain bertanggung jawab untuk mengenkapsulasi logika bisnis yang kompleks, atau logika bisnis sederhana yang digunakan kembali oleh beberapa ViewModels. Lapisan ini bersifat opsional karena tidak semua aplikasi akan memiliki persyaratan ini. Anda hanya boleh menggunakannya jika diperlukan, misalnya, untuk menangani kompleksitas atau mendukung penggunaan kembali.
Lapisan domain memberikan manfaat berikut:
- Menghindari duplikasi kode.
- Meningkatkan keterbacaan di class yang menggunakan class lapisan domain.
- Meningkatkan kemudahan pengujian aplikasi.
- Menghindari class yang besar dengan mengizinkan Anda memisahkan tanggung jawab.
Agar class ini tetap sederhana dan ringan, setiap kasus penggunaan hanya memiliki tanggung jawab atas satu fungsi, dan tidak boleh berisi data yang dapat berubah. Sebagai gantinya, Anda harus menangani data yang dapat berubah di lapisan data atau UI.
Konvensi penamaan dalam panduan ini
Dalam panduan ini, kasus penggunaan dinamai menurut tindakan tunggal yang menjadi tanggung jawabnya. Konvensinya adalah sebagai berikut:
kata kerja dalam bentuk masa kini + kata benda/apa (opsional) + UseCase.
Misalnya: FormatDateUseCase
, LogOutUserUseCase
,
GetLatestNewsWithAuthorsUseCase
, atau MakeLoginRequestUseCase
.
Dependensi
Dalam arsitektur aplikasi standar, class kasus penggunaan berada di antara ViewModel dari lapisan UI dan repositori dari lapisan data. Ini berarti bahwa class kasus penggunaan biasanya bergantung pada class repositori, dan berkomunikasi dengan lapisan UI sama seperti yang dilakukan repositori, yaitu menggunakan callback (untuk Java) atau coroutine (untuk Kotlin). Untuk mempelajari hal ini lebih lanjut, lihat halaman lapisan data.
Misalnya, di aplikasi, Anda mungkin memiliki class kasus penggunaan yang mengambil data dari repositori berita dan repositori penulis, lalu menggabungkannya:
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository
) { /* ... */ }
Karena kasus penggunaan berisi logika yang dapat digunakan kembali, kasus tersebut juga dapat digunakan oleh kasus
penggunaan lainnya. Memiliki beberapa tingkat kasus penggunaan di lapisan domain adalah hal yang wajar. Misalnya,
kasus penggunaan yang ditentukan dalam contoh di bawah dapat memanfaatkan
kasus penggunaan FormatDateUseCase
jika beberapa class dari lapisan UI bergantung pada zona
waktu untuk menampilkan pesan yang tepat pada layar:
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
Kasus penggunaan panggilan di Kotlin
Di Kotlin, Anda dapat membuat instance class kasus penggunaan yang dapat dipanggil sebagai fungsi dengan
menentukan fungsi invoke()
dengan pengubah operator
. Lihat contoh
berikut:
class FormatDateUseCase(userRepository: UserRepository) {
private val formatter = SimpleDateFormat(
userRepository.getPreferredDateFormat(),
userRepository.getPreferredLocale()
)
operator fun invoke(date: Date): String {
return formatter.format(date)
}
}
Dalam contoh ini, metode invoke()
di FormatDateUseCase
memungkinkan Anda untuk
memanggil instance class seolah-olah itu adalah fungsi. Metode invoke()
tidak dibatasi untuk tanda tangan tertentu. Metode ini dapat mengambil sejumlah parameter
dan menampilkan jenis apa pun. Anda juga dapat melebihi beban invoke()
dengan tanda tangan yang berbeda
di class Anda. Anda akan memanggil kasus penggunaan dari contoh di atas sebagai berikut:
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
init {
val today = Calendar.getInstance()
val todaysDate = formatDateUseCase(today)
/* ... */
}
}
Untuk mempelajari operator invoke()
lebih lanjut, lihat dokumen
Kotlin.
Siklus Proses
Kasus penggunaan tidak memiliki siklus prosesnya sendiri. Sebagai gantinya, kasus penggunaan disertakan ke class
yang menggunakannya. Ini berarti Anda dapat memanggil kasus penggunaan dari class di lapisan
UI, dari layanan, atau dari class Application
itu sendiri. Karena kasus penggunaan
tidak boleh berisi data yang dapat berubah, Anda harus membuat instance baru dari class kasus penggunaan
setiap kali Anda meneruskannya sebagai dependensi.
Threading
Kasus penggunaan dari lapisan domain harus berupa main-safe; dengan kata lain, kasus penggunaan harus aman untuk dipanggil dari thread utama. Jika class kasus penggunaan menjalankan operasi pemblokiran yang berjalan lama, class tersebut bertanggung jawab memindahkan logika itu ke thread yang sesuai. Namun, sebelum melakukannya, periksa apakah operasi pemblokiran tersebut akan lebih baik jika ditempatkan di lapisan hierarki lainnya. Biasanya, komputasi kompleks terjadi di lapisan data untuk mendorong penggunaan kembali atau caching. Misalnya, operasi yang menggunakan banyak resource pada daftar besar lebih baik ditempatkan di lapisan data daripada di lapisan domain jika hasilnya harus di-cache untuk digunakan kembali di beberapa layar aplikasi.
Contoh berikut menunjukkan kasus penggunaan yang bekerja di thread latar belakang:
class MyUseCase(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(...) = withContext(defaultDispatcher) {
// Long-running blocking operations happen on a background thread.
}
}
Tugas umum
Bagian ini menjelaskan cara melakukan tugas lapisan domain umum.
Logika bisnis sederhana yang dapat digunakan kembali
Anda harus mengenkapsulasi logika bisnis berulang yang ada di lapisan UI dalam class kasus penggunaan. Hal ini mempermudah penerapan setiap perubahan di mana pun logika tersebut digunakan. Alat ini juga memungkinkan Anda menguji logika secara terpisah.
Perhatikan contoh FormatDateUseCase
yang dijelaskan sebelumnya. Jika persyaratan bisnis Anda
terkait dengan perubahan pemformatan tanggal di masa mendatang, Anda hanya perlu
mengubah kode di satu tempat terpusat.
Menggabungkan repositori
Di aplikasi berita, Anda mungkin memiliki class NewsRepository
dan AuthorsRepository
yang menangani operasi data berita dan penulis. Class Article
yang ditampilkan oleh NewsRepository
hanya berisi nama penulis, tetapi Anda ingin
menampilkan informasi lebih lanjut tentang penulis di layar. Informasi penulis
dapat diperoleh dari AuthorsRepository
.
Karena logika melibatkan beberapa repositori dan dapat menjadi kompleks, Anda
membuat class GetLatestNewsWithAuthorsUseCase
untuk memisahkan logika dari
ViewModel dan membuatnya lebih mudah dibaca. Hal ini juga membuat logika lebih mudah untuk
diuji secara terpisah dan dapat digunakan kembali di berbagai bagian aplikasi.
/**
* 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
}
}
Logika memetakan semua item dalam daftar news
; jadi, meskipun lapisan data berupa
main-safe, operasi ini tidak akan memblokir thread utama karena Anda tidak tahu
jumlah item yang akan diproses. Itulah mengapa kasus penggunaan memindahkan pekerjaan ke thread
latar belakang menggunakan dispatcher default.
Konsumen lainnya
Selain lapisan UI, lapisan domain dapat digunakan kembali oleh class lain seperti
layanan dan class Application
. Selain itu, jika platform lain seperti TV
atau Wear berbagi codebase dengan aplikasi seluler, lapisan UI-nya juga dapat menggunakan kembali kasus
penggunaan untuk mendapatkan semua manfaat lapisan domain yang disebutkan di atas.
Pembatasan akses lapisan data
Salah satu pertimbangan lain saat menerapkan lapisan domain adalah apakah Anda masih harus mengizinkan akses langsung ke lapisan data dari lapisan UI, atau memaksa semuanya melalui lapisan domain.
Keuntungan dari membuat batasan ini adalah mencegah UI Anda mengabaikan logika lapisan domain, misalnya, jika Anda melakukan logging analisis pada setiap permintaan akses ke lapisan data.
Namun, terdapat kemungkinan kerugian yang signifikan karena ini memaksa Anda untuk menambahkan kasus penggunaan. Penambahan kecil sekalipun, seperti panggilan fungsi sederhana ke lapisan data, dapat menambah kompleksitas dengan sedikit manfaat.
Pendekatan yang baik adalah menambahkan kasus penggunaan hanya saat diperlukan. Jika Anda menemukan bahwa lapisan UI hampir selalu mengakses data melalui kasus penggunaan, akses ke data mungkin hanya dapat dilakukan dengan cara ini.
Pada akhirnya, keputusan untuk membatasi akses ke lapisan data bergantung pada codebase Anda, dan apakah Anda lebih memilih aturan yang ketat atau pendekatan yang lebih fleksibel.
Pengujian
Panduan pengujian umum berlaku saat Anda menguji lapisan domain. Untuk pengujian UI lainnya, developer biasanya menggunakan repositori palsu, dan praktik yang baik adalah menggunakan repositori palsu saat menguji lapisan domain.
Contoh
Contoh Google berikut menunjukkan penggunaan lapisan domain. Jelajahi untuk melihat panduan ini dalam praktik:
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Lapisan data
- Produksi Status UI