---
title: 'Yapılandırma Yerine Sözleşme'
source: 'https://academia.sh/tr/kurslar/mvc-cercevesi/yapilandirma-yerine-sozlesme'
course: 'Model–Görünüm–Denetleyici Çerçevesi'
language: tr
updated: '2026-10-21T05:03:41+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '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'
---

# 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.

Ö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 **eylemi**ne (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.

```ruby
# 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.

```ruby
# 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("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

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>&lt;b&gt;kalem&lt;/b&gt;</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.

```ruby
# 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.

```ruby
# 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.

```ruby
# 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 istek**tir: 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.
