Ders 01 / 13
Yapılandırma Yerine Sözleşme
Kursun ölçü ekseni bir değerin hangi isteğe kadar kaldığıdır. Ders çekirdeği koşturur, on üç gözlemi dört kalışa ayırır ve şablon adının yazılmadığını, adlardan türetildiğini ve kalışının bu istek olduğunu gösterir.
Yayın Güncelleme
Bu derste
- Bir istek döngüsünde bir değerin kalışını dört sınıftan biriyle adlandırır
- Çekirdeğin on üç gözleminin dağılımını ölçer ve kalış kuralının neyi okuduğunu gösterir
- Şablon adının yazılmadığını, denetleyici ve eylem adından türetildiğini ölçer
- Sözleşmenin yapılandırmayı kaldırdığını ama değeri saklamadığını gösterir
İçindekiler
Önceki kurs, bildirimsel arayüzlerle kapandı: alan :fiyat, 0 bir çağrıydı ve sınıfa yöntem ekliyordu. Bir web çerçevesi bu tür satırların üstüne kurulur, ama yeni bir şey sorar. Web’de her istek sıfırdan başlar: denetleyici yeni kurulur, parametreler boştur, isteğe özgü depo boştur. Buna rağmen bazı değerler isteğin bitiminde kaybolmaz. Bir bildirim mesajı (flash) sonraki sayfada görünür, giriş yapmış kullanıcının kimliği her sayfada vardır, kaydedilen ürünü başka bir tarayıcı da okur.
Kurs her düzeneği tek bir soruyla ölçer: bu değer hangi isteğe kadar kalıyor? Cevabın kategori adı kalıştır ve dört değeri vardır. Bu istek: değer yalnız yazıldığı istekte okunur. Sonraki istek: aynı istemcinin yalnız bir sonraki isteğinde okunur. Oturum: aynı istemcinin bütün sonraki isteklerinde okunur, başka istemcide okunmaz. Kalıcı: başka bir istemcinin isteğinde de okunur. Çıktıda bu dört ad ASCII yazılır: bu_istek, sonraki, oturum, kalici.
Model, Görünüm, Denetleyici
Model–görünüm–denetleyici (model–view–controller, MVC), bir uygulamayı üç sorumluluğa böler. Model veriyi ve veriyle ilgili kuralları taşır. Görünüm (view) veriyi kullanıcının göreceği çıktıya çevirir. Denetleyici (controller) isteği alır, modele sorar, sonucu görünüme verir. Dördüncü bir parça üçünün önünde durur: yönlendirme (routing), gelen yolu hangi denetleyicinin hangi eylemine (action) gideceğine çevirir.
Bir isteğin yolu bu sırayı izler: yönlendirme, denetleyici, model, görünüm. Kursun çekirdeğinde dört parçanın her biri küçük bir karşılıkla bulunur: yonlendir, UrunlerDenetleyicisi, Urun ve sablon_derle. Bu çekirdek bir çerçeve değildir, bir çerçevenin modelidir. Ölçülen her blok saf Ruby’dir; hiçbir hazır çerçeve kullanılmaz.
Çekirdek
Çekirdek iki dosyadır. İlki kalis.rb, küçük bir istek döngüsü ve kalışı sınıflandıran bir yardımcıdır. Bu blok kaynaktır; çıktısı yoktur, diske yazılır ve öteki dosyalar onu require_relative ile çeker.
# kalis.rb — kucuk bir istek dongusu ve bir degerin hangi istege kadar kaldigini siniflandiran yardimci
# Flash: bu istekte yazilan deger yalniz bir sonraki istege tasinir.
class Flash
def initialize(gelen)
@gelen = gelen
@yeni = {}
end
def [](anahtar) = @yeni.fetch(anahtar) { @gelen[anahtar] }
def []=(anahtar, deger)
@yeni[anahtar] = deger
end
def birakilan = @yeni
end
# Her istek yeni bir baglam alir: params, flash ve istek deposu her istekte bostur.
class Baglam
attr_reader :uygulama, :params, :oturum, :flash, :istek_deposu
def initialize(uygulama, oturum)
@uygulama = uygulama
@params = {}
@oturum = oturum
@flash = Flash.new(oturum.delete(:flash) || {})
@istek_deposu = {}
end
end
# Uygulamanin deposu butun isteklerde ve butun istemcilerde aynidir;
# oturum istemciye gore secilir.
class Uygulama
attr_reader :depo
def initialize
@oturumlar = Hash.new { |h, istemci| h[istemci] = {} }
@depo = { urunler: {}, sema: { urunler: %i[ad fiyat] }, sablonlar: {},
kuyruk: [], onbellek: {}, sayac: 0 }
end
def istek(istemci)
baglam = Baglam.new(self, @oturumlar[istemci])
sonuc = yield baglam
baglam.oturum[:flash] = baglam.flash.birakilan unless baglam.flash.birakilan.empty?
sonuc
end
end
# Kural: istek 1'de (istemci A) deger yazilir; istek 2 ve 3 (A) ile istek 4 (B) okur.
# B goruyorsa kalici; A'nin ucuncu istegi goruyorsa oturum; yalniz ikinci istek
# goruyorsa sonraki; hicbiri gormuyorsa bu_istek.
def kalis(yaz, oku)
uygulama = Uygulama.new
ilk = uygulama.istek("A") { |b| yaz.call(b); oku.call(b) }
raise ArgumentError, "deger yazildigi istekte okunamadi" if ilk.nil?
gorulen = [["A", 2], ["A", 3], ["B", 4]].map do |istemci, sira|
uygulama.istek(istemci) { |b| oku.call(b) }.nil? ? "--" : "#{istemci}#{sira}"
end
tur = if gorulen[2] != "--" then "kalici"
elsif gorulen[1] != "--" then "oturum"
elsif gorulen[0] != "--" then "sonraki"
else "bu_istek"
end
[tur, ["A1", *gorulen].join(" "), ilk]
end
Dört sınıf birlikte çalışır. Baglam her istekte yenidir ve params, flash ile istek_deposu‘nu boş alır. Uygulama bütün isteklerde ortak olan depo‘yu ve istemciye göre seçilen oturumu tutar. Flash, yazılan değeri isteğin sonunda oturuma bırakır; sonraki istek onu alır ve siler. kalis kuralı koşturur: istek 1’de istemci A değeri yazar ve okur, istek 2 ve 3’te A, istek 4’te istemci B yalnız okur. B görüyorsa değer kalıcıdır, A’nın üçüncü isteği görüyorsa oturum, yalnız ikinci istek görüyorsa sonraki istek, hiçbiri görmüyorsa bu istektir. ÇY1. Kalıcı depo burada bellektedir ve süreç bitince gider. Gerçek bir uygulamada bu yer veritabanı, önbellek ya da kuyruktur; kural yalnız tek süreç içinde anlamlıdır.
On Üç Gözlem
İkinci dosya degerler.rb, çerçevenin küçük modellerini ve on üç gözlemi taşır. Her gözlem bir yaz ve bir oku lambda’sından oluşur. Dosya sonunda hepsini kalis’e verir ve tabloyu basar.
# degerler.rb — on uc gozlem: bu deger hangi istege kadar kaliyor
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
# Sozlesme: sablon adi yazilmaz, denetleyici ve eylem adindan turetilir.
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"
# Nesne-iliski esleyici: bir satir bir kayda, tablo bir modele karsilik gelir.
module Urun
def self.olustur(b, nitelikler)
hatalar = []
hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
return { hatalar: } unless hatalar.empty?
tablo = b.uygulama.depo[:urunler]
id = tablo.size + 1
tablo[id] = nitelikler.merge(id:)
{ id:, hatalar: }
end
# Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
def self.bul(b, id)
sorgular = (b.istek_deposu[:sorgular] ||= {})
sorgular.fetch([:urunler, id]) do
b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
end
end
end
def goc(b, tablo, sutun) = b.uygulama.depo[:sema][tablo] << sutun
# Denetleyici: her istekte yeni bir ornek; filtre eylemden once calisir.
class UrunlerDenetleyicisi
def self.once(*adlar) = (@filtreler ||= []).concat(adlar)
def self.filtreler = @filtreler || []
once :urunu_bul
def initialize(baglam)
@baglam = baglam
end
def calistir(eylem)
self.class.filtreler.each { |filtre| send(filtre) }
public_send(eylem)
end
def goster = @urun
private
def urunu_bul
@urun = Urun.bul(@baglam, @baglam.params[:id])
end
end
def denetleyici(b) = b.istek_deposu[:denetleyici] ||= UrunlerDenetleyicisi.new(b)
# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&").gsub("<", "<").gsub(">", ">").gsub('"', """)
def sablon_derle(b, ad, kaynak)
b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
end
end
def kuyruga_al(b, is_adi, *argumanlar) = b.uygulama.depo[:kuyruk] << [is_adi, argumanlar]
def onbellekle(b, anahtar) = b.uygulama.depo[:onbellek][anahtar] ||= yield
def csrf_belirteci(b) = b.oturum[:csrf] ||= "belirtec-#{b.uygulama.depo[:sayac] += 1}"
kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
gozlemler = {
"sablon adi" => [->(b) { b.params.merge!(yonlendir("/urunler/7")) },
->(b) { b.params[:eylem] && sablon_adi(b.params) }],
"yol parametresi" => [->(b) { b.params.merge!(yonlendir("/urunler/7")) },
->(b) { b.params[:id] }],
"flash" => [->(b) { b.flash[:bildirim] = "urun kaydedildi" },
->(b) { b.flash[:bildirim] }],
"derlenen sablon" => [->(b) { sablon_derle(b, "urunler/goster", "<h1>{{ad}}</h1>") },
->(b) { b.uygulama.depo[:sablonlar]["urunler/goster"]&.call(ad: "<b>kalem</b>") }],
"kayit" => [kalem, ->(b) { b.uygulama.depo[:urunler][1]&.fetch(:ad) }],
"sema sutunu" => [->(b) { goc(b, :urunler, :stok) },
->(b) { b.uygulama.depo[:sema][:urunler].find { |s| s == :stok } }],
"dogrulama hatasi" => [->(b) { b.istek_deposu[:kayit] = Urun.olustur(b, ad: "", fiyat: 5) },
->(b) { b.istek_deposu.dig(:kayit, :hatalar, 0) }],
"sorgu onbellegi" => [->(b) { kalem.call(b); Urun.bul(b, 1) },
->(b) { b.istek_deposu.dig(:sorgular, [:urunler, 1], :ad) }],
"kullanici kimligi" => [->(b) { b.oturum[:kullanici_id] = 42 },
->(b) { b.oturum[:kullanici_id] }],
"kuyruktaki is" => [->(b) { kuyruga_al(b, "fatura_gonder", 1) },
->(b) { b.uygulama.depo[:kuyruk].first }],
"parca onbellegi" => [->(b) { onbellekle(b, "urun/1") { "<li>kalem</li>" } },
->(b) { b.uygulama.depo[:onbellek]["urun/1"] }],
"denetleyici degiskeni" => [->(b) { kalem.call(b); b.params[:id] = 1; denetleyici(b).calistir(:goster) },
->(b) { denetleyici(b).instance_variable_get(:@urun)&.fetch(:ad) }],
"csrf belirteci" => [->(b) { csrf_belirteci(b) }, ->(b) { b.oturum[:csrf] }],
}
adet = { "bu_istek" => 0, "sonraki" => 0, "oturum" => 0, "kalici" => 0 }
printf("%-22s%-14s%-13s%s\n", "gozlem", "kalis", "kanit", "deger")
gozlemler.each do |ad, (yaz, oku)|
tur, kanit, deger = kalis(yaz, oku)
adet[tur] += 1
printf("%-22s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end
puts
adet.each { |tur, n| printf("%-14s%d\n", tur, n) }
puts "toplam gozlem: #{adet.values.sum}"
gozlem kalis kanit deger sablon adi bu_istek A1 -- -- -- "urunler/goster" yol parametresi bu_istek A1 -- -- -- 7 flash sonraki A1 A2 -- -- "urun kaydedildi" derlenen sablon kalici A1 A2 A3 B4 "<h1><b>kalem</b></h1>" kayit kalici A1 A2 A3 B4 "kalem" sema sutunu kalici A1 A2 A3 B4 :stok dogrulama hatasi bu_istek A1 -- -- -- "ad bos olamaz" sorgu onbellegi bu_istek A1 -- -- -- "kalem" kullanici kimligi oturum A1 A2 A3 -- 42 kuyruktaki is kalici A1 A2 A3 B4 ["fatura_gonder", [1]] parca onbellegi kalici A1 A2 A3 B4 "<li>kalem</li>" denetleyici degiskeni bu_istek A1 -- -- -- "kalem" csrf belirteci oturum A1 A2 A3 -- "belirtec-1" bu_istek 5 sonraki 1 oturum 2 kalici 5 toplam gozlem: 13
On üç gözlemin 5’i bu istekte, 1’i sonraki istekte, 2’si oturumda kaldı; 5’i kalıcıdır. Kanıt sütunu dört isteğin dökümüdür: değeri gören istek adıyla, görmeyen -- ile yazılır. Bu istekte kalanların hepsi isteğin bağlamında doğar: şablon adı, yol parametresi, doğrulama hatası, sorgu önbelleği ve denetleyicinin örnek değişkeni. Bağlam her istekte yenilendiği için hiçbiri A2’ye ulaşmaz. Kalıcı olanlar Uygulama#depo’dadır: kayıt, şema sütunu, derlenmiş şablon, kuyruktaki iş ve parça önbelleği. Bunlar gerçekte beş ayrı yerdir, ama kural onları ayırmaz, çünkü davranışları aynıdır: başka bir istemci de okur. ÇY2. Kural değerin davranışını okur, saklandığı yeri okumaz. Aynı sınıfa düşmeleri onların aynı şey olduğunu söylemez; ilerleyen derslerde bu cümlenin iki yüzü de ölçülür.
Bu derste tablonun ilk satırı incelenir.
Şablon Adı Yazılmaz
sablon adi satırı bu istek sınıfına düştü ve değeri "urunler/goster" oldu. Bu ad hiçbir yerde yazılmadı. yonlendir("/urunler/7") denetleyici ve eylem adını verdi, sablon_adi da ikisini bir yola çevirdi. Bu bir sözleşmedir (convention): şablon, denetleyici adıyla eylem adının birleşimi olarak beklenir ve bunu söyleyen bir yapılandırma satırı yoktur. Yapılandırma yerine sözleşme (convention over configuration) ilkesi budur: yaygın durumun cevabı varsayılandır, yalnız sapma yazılır.
Aynı yardımcıyı farklı adlarla çağırmak türetmenin işlediğini gösterir. degerler.rb koşarken tabloyu basar, bu yüzden bu ölçüm ayrı bir dosyada kendi tanımıyla koşar; sablon_adi satırı degerler.rb’dekinin birebir kopyasıdır.
# sozlesme.rb — sablon adinin turetilmesi
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"
p sablon_adi(denetleyici: "urunler", eylem: "goster")
p sablon_adi(denetleyici: "urunler", eylem: "duzenle")
p sablon_adi(denetleyici: "siparisler", eylem: "goster")
"urunler/goster" "urunler/duzenle" "siparisler/goster"
Eylem adı değişince şablon adı onunla değişti. Üçüncü satır sözleşmenin sınırını gösterir: siparisler adında bir denetleyici çekirdekte yok, ama sablon_adi bir ad üretti. Sözleşme adı üretir; böyle bir şablonun var olup olmadığına bakmaz. ÇY3. Bu yalnız çekirdeğin tek satırlık sablon_adi’si için ölçüldü. Gerçek bir çerçevenin dosya aramasının, eksik şablon karşısında ne yaptığının hiçbiri bu derste sınanmadı.
Bu soru, M08/K06’nın sorusundan ayrıdır. Orada bir davranışın kaynaktaki sebebi ölçüldü: çağrı mı, açıklama mı, varsayılan mı. Şablon adı oradaki dille bir varsayılandır. Burada sebep sorulmaz, kalış sorulur: ad varsayılandan türese de değeri yalnız bu istekte okunur.
Sözleşme Eksik Girdiyle Ne Üretir
Sözleşme bir kuraldır; kural girdiyi doğrulamaz. Adı üreten tek satırın eksik parametreyle ne verdiğine bakmak, sözleşmenin nerede bittiğini gösterir.
# eksik.rb — eksik parametreyle uretilen ad
require_relative "kalis"
# Sozlesme: sablon adi yazilmaz, denetleyici ve eylem adindan turetilir.
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"
p sablon_adi(denetleyici: "urunler", eylem: "goster")
p sablon_adi(denetleyici: "urunler")
p sablon_adi({})
p sablon_adi(denetleyici: "urunler", eylem: "")
"urunler/goster" "urunler/" "/" "urunler/"
Eylem eksik olduğunda hata çıkmadı; ad urunler/ oldu. Hiç parametre verilmeyince ad yalnızca / oldu. Boş dizgi ile eksik anahtar aynı sonucu verdi, çünkü dizge içine nil de boş dizgi olarak girer. Sözleşme adı kurar, adın anlamlı olup olmadığına bakmaz. Bu yüzden eksik bir ad, hata olarak değil, derlenmiş şablon deposunda bulunamayan bir anahtar olarak ortaya çıkar: arama sessizce nil döner. Hatayı yakalamak isteyen kod, adı üretmeden önce denetleyici ve eylemin dolu olduğunu kendisi sınamalıdır; sözleşme bu sınamayı üstlenmez.
Sınırlayıcı Ölçüm
Sözleşme yapılandırmayı kaldırır, değeri saklamaz. Bunu iki biçimde ölçmek mümkündür: aynı ad bir sonraki istekte yok mudur, ve yönlendirme hiç çalışmayan bir istekte ne olur.
# sinir.rb — turetilen adin kalisi
require_relative "kalis"
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"
yaz = ->(b) { b.params.merge!(denetleyici: "urunler", eylem: "goster") }
oku = ->(b) { b.params[:eylem] && sablon_adi(b.params) }
p kalis(yaz, oku)
uygulama = Uygulama.new
p uygulama.istek("A") { |b| oku.call(b) }
p uygulama.istek("A") { |b| b.params.merge!(denetleyici: "urunler", eylem: "goster"); oku.call(b) }
p uygulama.depo[:sablonlar]
["bu_istek", "A1 -- -- --", "urunler/goster"]
nil
"urunler/goster"
{}
İlk satır tablodaki sonucun aynısıdır: yazan istek adı görür, sonraki üç istek görmez. İkinci satır yönlendirmenin çalışmadığı isteği gösterir. params[:eylem] boş olduğu için oku nil verdi; yani ad hesaplanmadı, hesaplanabilecek bir kaynak da yoktu. Üçüncü satırda yönlendirme aynı isteğe yeniden yazıldı ve ad aynı çıktı. Son satır adın hiçbir yere bırakılmadığını söyler: uygulamanın şablon deposu hâlâ boş. Ad her istekte, parametrelerden yeniden hesaplanır. ÇY4. Bu ölçüm tek bir parametre çiftiyle yapıldı; ad üretmenin kalışı farklı bir sözleşme için ayrıca ölçülmelidir.
Özet
- Bir değerin kalışı, hangi isteğe kadar okunabildiğidir: bu istek, sonraki istek, oturum ya da kalıcı.
- Çekirdekte on üç gözlemin 5’i bu istekte, 1’i sonraki istekte, 2’si oturumda kaldı; 5’i kalıcıdır.
- Kalış kuralı değerin davranışını okur; değerin saklandığı yeri okumaz.
- Şablon adı yazılmaz, denetleyici ve eylem adından türer; sözleşme adı üretir ama şablonun varlığını denetlemez.
- Bu dersin kalışı bu istektir: türetilen ad her istekte yeniden hesaplanır ve depoya bırakılmaz.
Sonraki Adım
Şablon adı, yönlendirmenin verdiği denetleyici ve eylem adından türedi. Peki yönlendirme o adları nereden aldı? Bir sonraki ders bir yolu parçalarına ayıran kalıbı ölçer ve yoldaki 7 sayısının bir sonraki isteğe geçip geçmediğine bakar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.