Ders 01 / 14
Katman Modeli
Katman modeli uygulamayı arayüz, alan ve veri katmanlarına böler; bağımlılık yalnız içe doğru gider. Ders bir okumanın yanıtının bellekten, diskten, ağdan ya da varsayılandan geldiğini ölçer.
Yayın Güncelleme
Bu derste
- Arayüz, alan ve veri katmanlarının sorumluluğunu birbirinden ayırır
- Bağımlılığın yalnız içe doğru gittiğini ve arayüzün veri katmanını zincir üzerinden gördüğünü açıklar
- Bir okumanın yanıtını bellek, disk, ağ ve varsayılan kökenlerinden birine sınıflar
- Bellekteki bayat değerin kural tarafından görülmediğini ölçer
İçindekiler
Önceki kurs Compose ile yazılan arayüzde verinin ağaçta hangi yöne gittiğini ölçtü. O kursun ölçü ekseni burada sürmez. Artık veri arayüz ağacında değil, uygulamanın katmanları arasında dolaşıyor ve her okuma için tek bir soru soruyoruz: bu okumanın yanıtı nereden geliyor? Yanıtın dört kökeni var: bellek, disk, ağ ve varsayılan.
Okumanın Kökeni
Veri katmanında (data layer) bir okuma bir anahtar için basamakları sırayla dener: önce bellek, sonra disk, sonra ağ. Hiçbiri anahtarı bilmiyorsa okuma kendi varsayılanını kullanır. Zincirin her halkasına basamak diyoruz. Aşağıdaki çekirdek, bir okumanın yanıtını hangi basamağın verdiğini sınıflar; boş dizgi de bir yanıttır, yani basamak anahtarı boş dizgiyle bilse bile yanıt o basamaktan gelir.
// Origin.kt — bir okumanin yanitinin hangi basamaktan geldigini siniflandirir
class Tier(val origin: String, private val entries: Map<String, String>) {
init {
require(origin in listOf("bellek", "disk", "ag")) { "gecersiz basamak: $origin" }
}
fun lookup(key: String): String? = entries[key]
}
class Read(val label: String, val key: String, chain: List<Tier>, fallback: String) {
private val hit: Tier? = chain.firstOrNull { it.lookup(key) != null }
val value: String = hit?.lookup(key) ?: fallback
val origin: String = hit?.origin ?: "varsayilan"
fun show() {
println("$label: anahtar=$key | deger=$value -> $origin")
}
}
fun countOrigins(reads: List<Read>) {
for (name in listOf("bellek", "disk", "ag", "varsayilan")) {
println("$name: ${reads.count { it.origin == name }}")
}
}
Tier bir basamaktır ve yalnız üç adı kabul eder. Read zinciri baştan sona dolaşır, ilk yanıtı alır. countOrigins dört kökeni sayar. Kural yalnız sıraya bakar: değerin yeni ya da eski olduğunu görmez.
Zincirin sıralı olmasının nedeni maliyettir, doğruluk değildir. Bellek en hızlı ve en kısa ömürlü basamaktır; disk süreç sonlansa da kalır; ağ en uzaktadır. Okuma ucuz olandan pahalı olana doğru ilerler ve ilk yanıtta durur. Bu düzen hız kazandırır, ama ucuz basamak eskimişse eski değer yanıt olur. Bu derste bunu iki kez ölçeceğiz.
Üç Katman
Katman modeli uygulamayı sorumluluğa göre en az iki, bu derste üç parçaya böler. Arayüz katmanı (UI layer) ekranı çizer ve kullanıcının olayını alır. Alan katmanı (domain layer) isteğe bağlıdır ve ikisinin arasında durur; iş kurallarını taşır, ham veriyi ekranın istediği biçime çevirmek burada yapılır. Alan katmanı olmayan uygulamada arayüz doğrudan veri katmanına bağlanır. Veri katmanı verinin nereden okunduğunu ve nereye yazıldığını bilir: bellek, disk ve ağ yalnız bu katmanda görünür.
Bağımlılık yalnız içe doğru gider: arayüz alana, alan veriye bağlıdır; veri katmanı arayüzü bilmez. Platformda bu iskelet şöyle görünür.
// Platform kodu: CartScreen
import androidx.lifecycle.ViewModel
class CartViewModel(private val getCart: GetCartUseCase) : ViewModel() {
val items = getCart()
}
class GetCartUseCase(private val repository: CartRepository) {
operator fun invoke() = repository.items
}
class CartRepository(private val local: CartLocalSource, private val remote: CartRemoteSource) {
val items = local.items
}
Arayüz tarafı yalnız CartViewModel’i tanır; CartRemoteSource’u görmez. Bu parça platform kodudur ve burada çıktı, süre ya da dosya yolu iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir. Modelde katmanlar sınıf değil, bir okumanın geçtiği basamaklardır.
Yönün içe doğru olmasının pratik bir sonucu vardır: veri katmanında disk ya da ağ değişirse alan ve arayüz katmanı aynı kalır, çünkü onlar yalnız okumanın sonucunu ister. Tersi de doğrudur: ekran değişirse veri katmanı bundan haberdar olmaz. Her katman kendi sorusuna yanıt verir. Arayüz “ne gösterilecek”, alan “hangi kurala göre”, veri “nereden alınacak” sorusunu yanıtlar. Bu kurs üçüncü soruyu ölçer.
Sekiz Okuma, Dört Köken
Bir alışveriş uygulaması açılıyor. Ağdan gelen ham veri iki işlevle yayılır: syncTo ağdaki değerleri diske kopyalar, promote diskteki ya da ağdaki değerleri belleğe taşır. İkisi de aldığı eşlemi günceller ve onu döndürür; ölçülen basamaklar bu dönüşlerdir.
// Main.kt
fun syncTo(disk: MutableMap<String, String>, network: Map<String, String>, keys: List<String>): Map<String, String> {
for (key in keys) network[key]?.let { disk[key] = it }
return disk
}
fun promote(memory: MutableMap<String, String>, source: Map<String, String>, keys: List<String>): Map<String, String> {
for (key in keys) source[key]?.let { memory[key] = it }
return memory
}
fun main() {
val network = mapOf(
"profil.ad" to "Deniz",
"urun.fiyat" to "120",
"urun.stok" to "7",
"sepet.adet" to "3",
)
val disk = syncTo(mutableMapOf("ayar.tema" to "koyu", "ayar.dil" to "tr"), network, listOf("sepet.adet", "profil.ad"))
val memory = promote(mutableMapOf("urun.fiyat" to "100"), disk, listOf("profil.ad"))
println("disk anahtarlari: ${disk.keys.sorted()}")
println("bellek anahtarlari: ${memory.keys.sorted()}")
val chain = listOf(Tier("bellek", memory), Tier("disk", disk), Tier("ag", network))
val reads = listOf(
Read("profil-adi", "profil.ad", chain, "misafir"),
Read("urun-fiyati", "urun.fiyat", chain, "0"),
Read("tema", "ayar.tema", chain, "acik"),
Read("dil", "ayar.dil", chain, "tr"),
Read("sepet-adedi", "sepet.adet", chain, "0"),
Read("stok", "urun.stok", chain, "0"),
Read("kampanya", "kampanya.kod", chain, "yok"),
Read("bildirim", "bildirim.acik", chain, "kapali"),
)
reads.forEach { it.show() }
countOrigins(reads)
}
disk anahtarlari: [ayar.dil, ayar.tema, profil.ad, sepet.adet] bellek anahtarlari: [profil.ad, urun.fiyat] profil-adi: anahtar=profil.ad | deger=Deniz -> bellek urun-fiyati: anahtar=urun.fiyat | deger=100 -> bellek tema: anahtar=ayar.tema | deger=koyu -> disk dil: anahtar=ayar.dil | deger=tr -> disk sepet-adedi: anahtar=sepet.adet | deger=3 -> disk stok: anahtar=urun.stok | deger=7 -> ag kampanya: anahtar=kampanya.kod | deger=yok -> varsayilan bildirim: anahtar=bildirim.acik | deger=kapali -> varsayilan bellek: 2 disk: 3 ag: 1 varsayilan: 2
Sekiz okuma dört köken verdi: bellek 2, disk 3, ağ 1, varsayılan 2. İki ayrıntı kuralı gösterir. urun-fiyati bellekte 100 bulur; ağda 120 durduğu hâlde okuma belleğe döner, çünkü kural sırayı görür, tazeliği görmez. stok ağdan gelir: syncTo yalnız sepet.adet ve profil.ad’ı kopyaladı, urun.stok diske hiç inmedi. kampanya ve bildirim hiçbir basamakta yok; okumaların kendi varsayılanları yanıt oldu.
Alan Katmanı Kökeni Değiştirmez
Alan katmanı ağdan gelen ham değeri arayüzün istediği biçime çevirir. Ağ fiyatı kuruş olarak verir; ekran lira ister. Dönüştürme işlevi yeni bir ad taşır ve veri katmanının zincirine yalnız çevrilmiş eşlemi verir.
// Domain.kt — alan katmani: agdan gelen kurus degerini arayuz bicimi olan liraya cevirir
fun toDisplay(raw: Map<String, String>): Map<String, String> =
raw.mapValues { (key, value) -> if (key.endsWith(".fiyat")) (value.toInt() / 100).toString() else value }
Arayüz yalnız zinciri görür: değerin ham mi çevrilmiş mi olduğunu bilmez, hangi basamaktan geldiğini ise okumanın kökeninden öğrenir. Bellek promote dönüşüyle kurulur: promote yalnız kampanya kodunu belleğe taşımak ister, ama ağ yanıtında o anahtar yoktur, bu yüzden bellek boş kalır. Ağ basamağı çevrilmiş eşlemi taşır.
// Main.kt — alan katmani degeri cevirir, koken yine ag
fun promote(memory: MutableMap<String, String>, source: Map<String, String>, keys: List<String>): Map<String, String> {
for (key in keys) source[key]?.let { memory[key] = it }
return memory
}
fun main() {
val raw = mapOf("urun.fiyat" to "12000", "urun.stok" to "7")
val network = toDisplay(raw)
println("ham ag verisi: urun.fiyat=${raw["urun.fiyat"]}")
println("arayuz bicimi: urun.fiyat=${network["urun.fiyat"]}")
val memory = promote(mutableMapOf(), network, listOf("kampanya.kod"))
val chain = listOf(Tier("bellek", memory), Tier("disk", emptyMap()), Tier("ag", network))
val reads = listOf(
Read("urun-fiyati", "urun.fiyat", chain, "0"),
Read("stok", "urun.stok", chain, "0"),
Read("kampanya", "kampanya.kod", chain, "yok"),
)
reads.forEach { it.show() }
countOrigins(reads)
}
ham ag verisi: urun.fiyat=12000 arayuz bicimi: urun.fiyat=120 urun-fiyati: anahtar=urun.fiyat | deger=120 -> ag stok: anahtar=urun.stok | deger=7 -> ag kampanya: anahtar=kampanya.kod | deger=yok -> varsayilan bellek: 0 disk: 0 ag: 2 varsayilan: 1
Ham değer 12000, arayüz biçimi 120. İki ürün okuması ağdan geldi: bellek boştu, disk boştu. Alan katmanı değeri çevirdi ama yanıtı veren basamağı değiştirmedi.
UM1. Model dönüştürmeyi ağ basamağının girdisine uygular. Platformda dönüştürme okumadan sonra, alan katmanında da yapılabilir; bu da kökeni değiştirmez, ama modelde bu ayrım kurulmaz.
Sınırlayıcı Ölçüm
Aynı anahtarı belleğe taşıyalım ve sonra ağdaki değeri değiştirelim. İlk okumada promote yalnız stoğu taşımak ister; ağda stok olmadığından bellek boş kalır ve okuma ağdan gelir. promote fiyatı belleğe taşıyınca ağda fiyat 150 olsa bile ikinci okuma belleğe döner ve eski değeri okur.
// Main.kt — ayni anahtar bellege tasinirsa ayni okuma bellek olur
fun promote(memory: MutableMap<String, String>, source: Map<String, String>, keys: List<String>): Map<String, String> {
for (key in keys) source[key]?.let { memory[key] = it }
return memory
}
fun main() {
val before = toDisplay(mapOf("urun.fiyat" to "12000"))
val emptyMemory = promote(mutableMapOf(), before, listOf("urun.stok"))
val first = Read("fiyat-ilk", "urun.fiyat", listOf(Tier("bellek", emptyMemory), Tier("ag", before)), "0")
first.show()
val memory = promote(mutableMapOf(), before, listOf("urun.fiyat"))
val after = toDisplay(mapOf("urun.fiyat" to "15000"))
val second = Read("fiyat-sonra", "urun.fiyat", listOf(Tier("bellek", memory), Tier("ag", after)), "0")
second.show()
println("ag simdi: ${after["urun.fiyat"]}")
countOrigins(listOf(first, second))
}
fiyat-ilk: anahtar=urun.fiyat | deger=120 -> ag fiyat-sonra: anahtar=urun.fiyat | deger=120 -> bellek ag simdi: 150 bellek: 1 disk: 0 ag: 1 varsayilan: 0
İki okumanın anahtarı aynı, değeri aynı, kökenleri farklı. İlk okuma ağdan gelir; promote değeri belleğe alınca ikinci okuma bellekten gelir. Ağ artık 150 söylüyor ama okuma 120 buluyor. Kural hâlâ yalnız sırayı görür: bellek ilk basamak olduğu için eski değer yanıt olur.
UM2. Model değerin ne zaman bayatladığını bilmez; bellek basamağı kendiliğinden boşalmaz. Platformda bir geçerlilik süresi ya da geçersiz kılma olabilir; modelde yok.
Özet
- Bu dersin kökeni: ağ. Alan katmanı fiyatı kuruştan liraya çevirdi, ama yanıtı veren basamak ağdı: bellek ve disk boştu.
- Dersin ölçütü, bir okumanın yanıtını hangi basamağın verdiği: bellek, disk, ağ ya da varsayılan. Sekiz okumada sayılar bellek 2, disk 3, ağ 1, varsayılan 2 çıktı.
- Arayüz, alan ve veri katmanı arasında bağımlılık içe doğru gider; arayüz basamakları değil, yalnız zincirin yanıtını görür.
- Kural sırayı görür, tazeliği görmez: ağda 120 dururken bellekteki 100 okundu.
- Aynı anahtar belleğe taşınınca aynı okuma ağdan belleğe döndü; ağdaki yeni değer okunmadı.
Sonraki Adım
Bu derste bellek, promote ile elle doldurulan bir eşlemdi. Platformda belleği dolduran, ekran döndüğünde onu koruyan bir sahip var. Sıradaki ders, Görünüm Modeli, o sahibin ekran döndükten sonra okumanın kökenini nasıl değiştirdiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.