---
title: 'Şema Uyumluluk Denetimi'
source: 'https://academia.sh/tr/kurslar/tumlesim-testi/sema-uyumluluk-denetimi'
course: 'Tümleşim, Sözleşme ve Uçtan Uca Test'
language: tr
updated: '2026-08-17T18:10:50+00:00'
license: 'CC BY-SA 4.0'
---

# Şema Uyumluluk Denetimi

İki şema sürümünü karşılaştıran ve kırıcı değişikliği yayımdan önce durduran denetim: değişiklik türlerinin istek ve yanıt yönünde ayrı uyumluluk sınıflarına düşmesi, genişlet–daralt sırasının kapıyı yeşile çevirmesi ve denetimin uyumlu saydığı bir küme genişletmesinin tüketiciyi kırması.

Önceki iki denetim de koşarak çalıştı: bir süreç kalkıyor, istek gidiyor, yanıt
inceleniyordu. Sağlayıcının asıl sorusu ise değişikliği yayımlamadan önce sorulur ve
koşum gerektirmez. Bir alan eklemek, bir alan kaldırmak, bir türü daraltmak, isteğe bağlı
bir alanı zorunlu yapmak: bu dört değişiklik türünden hangisi eski tüketiciyi kırar?

Kırıcı değişikliğin **tanımı** Web API Tasarımı kursunda, **maliyetinin ölçümü** Uygulama
Katmanı ve Servis Etkileşimi kursunda kuruldu; ikisi de burada tekrarlanmaz. Bu dersin
konusu o bilginin otomatik bir denetime çevrilmesidir: iki şema sürümü karşılaştırılır,
her değişiklik bir türe ve bir uyumluluk sınıfına yerleştirilir, kırıcı sınıfta bir
değişiklik varsa takım kırmızı döner.

## Şema Sürümleri ve Öneriler

Yayımlanan şema kitap yanıtını ve ayırma isteğini birlikte tarif ediyor. Üç öneri var:
`A` dört değişiklik türünü bir arada taşıyor, `B` aynı ihtiyacı genişlet–daralt sırasının
birinci adımıyla karşılıyor, `C` yalnız bir değer kümesini genişletiyor.

```js
// sema.mjs — yayimlanan sema ve uc oneri; her alan tip, zorunluluk ve deger kumesi tasir
const A = (tip, zorunlu, kume) => (kume ? { tip, zorunlu, kume } : { tip, zorunlu });

export const YAYIN = {
  yanit: {
    isbn: A("dizgi", true), baslik: A("dizgi", true), raftaAdet: A("sayi", true),
    durum: A("dizgi", true, ["rafta", "oduncte", "kayip"]),
    oduncGun: A("sayi", true), etiketler: A("dizi", false),
  },
  istek: { uyeNo: A("dizgi", true), sube: A("dizgi", false), adet: A("sayi", false) },
};

// A: dort degisiklik turu bir arada. B: genislet adimi. C: yalniz kume genisler.
export const ONERI = {
  A: {
    yanit: { ...YAYIN.yanit, sonGun: A("dizgi", false) },
    istek: { ...YAYIN.istek, sube: A("dizgi", true), adet: A("tamsayi", false) },
  },
  B: { yanit: { ...YAYIN.yanit, sonGun: A("dizgi", false) }, istek: { ...YAYIN.istek } },
  C: {
    yanit: { ...YAYIN.yanit, durum: A("dizgi", true, ["rafta", "oduncte", "kayip", "rafta-yeni"]) },
    istek: { ...YAYIN.istek },
  },
};
delete ONERI.A.yanit.etiketler;                       // alan kaldirma
```

Denetimin kendisi iki parçadır: farkı türlere ayıran bir karşılaştırma ve türleri sınıflara
bağlayan bir tablo. Tablonun iki sütunu olması bu dersin ana ayrımıdır — aynı değişiklik
türü istek yönünde ve yanıt yönünde farklı sınıfa düşer.

```js
// uyumluluk.mjs — iki sema surumu arasindaki degisiklikleri turlerine ve siniflarina ayirir
const DAR = { sayi: "tamsayi" };

export const SINIF = {
  "istege bagli alan ekleme": { yanit: "uyumlu", istek: "uyumlu" },
  "zorunlu alan ekleme": { yanit: "uyumlu", istek: "kirici" },
  "alan kaldirma": { yanit: "kirici", istek: "uyumlu" },
  "tur daraltma": { yanit: "uyumlu", istek: "kirici" },
  "tur genisletme": { yanit: "kirici", istek: "uyumlu" },
  "zorunlu yapma": { yanit: "uyumlu", istek: "kirici" },
  "istege bagli yapma": { yanit: "kirici", istek: "uyumlu" },
  "kumeye deger ekleme": { yanit: "uyumlu", istek: "kirici" },
  "kumeden deger cikarma": { yanit: "kirici", istek: "uyumlu" },
};

export function fark(eski, yeni) {
  const d = [];
  for (const [ad, k] of Object.entries(eski)) {
    const y = yeni[ad];
    if (y === undefined) { d.push({ ad, tur: "alan kaldirma" }); continue; }
    if (y.tip !== k.tip) d.push({ ad, tur: DAR[k.tip] === y.tip ? "tur daraltma" : "tur genisletme" });
    if (y.zorunlu && !k.zorunlu) d.push({ ad, tur: "zorunlu yapma" });
    if (k.zorunlu && !y.zorunlu) d.push({ ad, tur: "istege bagli yapma" });
    if ((y.kume ?? []).some((v) => !(k.kume ?? []).includes(v))) d.push({ ad, tur: "kumeye deger ekleme" });
    if ((k.kume ?? []).some((v) => !(y.kume ?? []).includes(v))) d.push({ ad, tur: "kumeden deger cikarma" });
  }
  for (const [ad, y] of Object.entries(yeni)) {
    if (ad in eski) continue;
    d.push({ ad, tur: y.zorunlu ? "zorunlu alan ekleme" : "istege bagli alan ekleme" });
  }
  return d;
}

// Bir oneriyi iki yonde de degerlendirir; donen listenin her satiri siniflandirilmis bir degisikliktir.
export const degerlendir = (yayin, oneri) => ["yanit", "istek"].flatMap((yon) =>
  fark(yayin[yon], oneri[yon]).map((x) => ({ yon, ...x, sinif: SINIF[x.tur][yon] })));
```

## Türlerin Sınıflara Dağılımı

```js
// dagilim.mjs — degisiklik turlerinin uyumluluk siniflarina dagilimi ve uc onerinin sayimi
import { YAYIN, ONERI } from "./sema.mjs";
import { SINIF, degerlendir } from "./uyumluluk.mjs";

console.log(`${"degisiklik turu".padEnd(26)}${"yanitta".padStart(9)}${"istekte".padStart(9)}`);
for (const [tur, s] of Object.entries(SINIF)) {
  console.log(`${tur.padEnd(26)}${s.yanit.padStart(9)}${s.istek.padStart(9)}`);
}

for (const [ad, o] of Object.entries(ONERI)) {
  const d = degerlendir(YAYIN, o);
  console.log(`\noneri ${ad}: ${d.length} degisiklik, ${d.filter((x) => x.sinif === "kirici").length} kirici`);
  for (const x of d) console.log(`  ${`${x.yon}.${x.ad}`.padEnd(17)}${x.tur.padEnd(26)}-> ${x.sinif}`);
}
```

```
degisiklik turu             yanitta  istekte
istege bagli alan ekleme     uyumlu   uyumlu
zorunlu alan ekleme          uyumlu   kirici
alan kaldirma                kirici   uyumlu
tur daraltma                 uyumlu   kirici
tur genisletme               kirici   uyumlu
zorunlu yapma                uyumlu   kirici
istege bagli yapma           kirici   uyumlu
kumeye deger ekleme          uyumlu   kirici
kumeden deger cikarma        kirici   uyumlu

oneri A: 4 degisiklik, 3 kirici
  yanit.etiketler  alan kaldirma             -> kirici
  yanit.sonGun     istege bagli alan ekleme  -> uyumlu
  istek.sube       zorunlu yapma             -> kirici
  istek.adet       tur daraltma              -> kirici

oneri B: 1 degisiklik, 0 kirici
  yanit.sonGun     istege bagli alan ekleme  -> uyumlu

oneri C: 1 degisiklik, 0 kirici
  yanit.durum      kumeye deger ekleme       -> uyumlu
```

Tablonun iki sütunu birbirinin aynası. Yanıtta bir alanı kaldırmak kırıcıdır, çünkü
tüketici onu okuyordur; istekte bir alanı kaldırmak kırıcı değildir, çünkü eski istemci
göndermeye devam eder ve alan yok sayılır. Aynı biçimde bir alanı zorunlu yapmak yanıtta
zarar vermez — alan zaten hep gelecektir — istekte ise eski istemcinin gönderdiği bütün
isteklerin reddedilmesine yol açar. Tür daraltmak yanıtta uyumludur, çünkü tamsayı hâlâ bir
sayıdır; istekte kırıcıdır, çünkü eski istemci ondalık gönderebilir.

Bu simetri, sekiz satırlık tablonun ezberlenmesi gereken bir liste olmadığını gösterir.
Tek bir soru vardır ve yön onu belirler: **değişiklikten sonra karşı tarafın elindeki eski
kod hâlâ geçerli mi.** Yanıtta karşı taraf okur, istekte yazar.

## Kabul Kapısı

Denetim, Kalite ve Test Temelleri kursunda tanımlanan **kabul kapısı** olarak çalışır:
kırıcı sınıfta bir değişiklik varsa yayın durur.

```js
// uyumluluk.test.mjs — yayin oncesi kapi: kirici degisiklik varsa takim kirmizi doner
// Kullanim: ONERI=A|B|C node --test uyumluluk.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";
import { YAYIN, ONERI } from "./sema.mjs";
import { degerlendir } from "./uyumluluk.mjs";

const AD = process.env.ONERI ?? "B";

test(`oneri ${AD}: yayimlanan semaya gore kirici degisiklik yok`, () => {
  const kirici = degerlendir(YAYIN, ONERI[AD]).filter((x) => x.sinif === "kirici");
  assert.equal(kirici.length, 0,
    `kirici degisiklik: ${kirici.map((x) => `${x.yon}.${x.ad} ${x.tur}`).join(", ")}`);
});
```

```bash
for o in A B C; do
  ONERI=$o node --test --test-reporter=tap uyumluluk.test.mjs |
    grep -E '^(ok|not ok)|kirici degisiklik:' | sed 's/^ *//'
done
```

```
not ok 1 - oneri A: yayimlanan semaya gore kirici degisiklik yok
kirici degisiklik: yanit.etiketler alan kaldirma, istek.sube zorunlu yapma, istek.adet tur daraltma
ok 1 - oneri B: yayimlanan semaya gore kirici degisiklik yok
ok 1 - oneri C: yayimlanan semaya gore kirici degisiklik yok
```

**Yakalanan hata sınıfı, sözleşmenin yayımdan önce kırılmasıdır.** Öneri A kırmızı döndü
ve üç kırıcı değişikliği yön bilgisiyle birlikte adlandırdı. Düzeltme, aynı ihtiyacı
genişlet–daralt sırasının birinci adımına çevirmektir: yeni alan eskilerin yanına eklenir,
kaldırma ve zorunlu yapma üçüncü adıma bırakılır. Öneri B tam olarak budur ve kapı yeşile
döner.

Bu denetimin önceki iki dersten farkı, kırılmayı **hiçbir tüketici çalışmadan** görmesidir.
Sözleşme testi kırılmayı gösterebilmek için tüketicinin beklentisini ve sağlayıcının
sürümünü birlikte koşturmak zorundaydı; buradaki denetim iki metni karşılaştırıyor.
Karşılığında bildiği şey daha azdır: hangi tüketicinin kırıldığını söyleyemez, yalnız
birinin kırılabileceğini söyler.

## Denetimin Uyumlu Saydığı Kırılma

Öneri C kapıdan geçti. Kitap durumu için tanımlı değer kümesine yeni bir değer eklendi:
depoya yeni giren ve rafta duran kitaplar artık `rafta-yeni` olarak bildiriliyor. Eski
yanıtların hepsi hâlâ geçerli, hiçbir alan kaybolmadı, hiçbir tür değişmedi.

```js
// kacan.mjs — denetimin uyumlu saydigi degisiklik tuketiciyi kiriyor
import { YAYIN, ONERI } from "./sema.mjs";
import { degerlendir } from "./uyumluluk.mjs";

// Odunc servisinin karar dali: bilinmeyen durum degeri "rafta degil" sayilir.
const oduncKarari = (k) => (k.durum === "rafta" && k.raftaAdet > 0
  ? { verilir: true, ileti: `${k.isbn} verildi` }
  : { verilir: false, ileti: `rafta yok (durum ${k.durum}, raftaAdet ${k.raftaAdet})` });

const d = degerlendir(YAYIN, ONERI.C);
console.log(`denetim: oneri C -> ${d.length} degisiklik, ${d.filter((x) => x.sinif === "kirici").length} kirici, kapi yesil`);

const RAF = { isbn: "978-0262033848", durum: "rafta", raftaAdet: 2 };
const YENI = { ...RAF, durum: "rafta-yeni" };          // yeni gelen raf: kitap rafta ve odunc verilebilir
for (const [ad, k] of [["eski deger", RAF], ["yeni deger", YENI]]) {
  const karar = oduncKarari(k);
  console.log(`${ad.padEnd(11)} -> verilir=${String(karar.verilir).padEnd(5)} ${karar.ileti}`);
}
console.log(`semadaki durum degeri ${ONERI.C.yanit.durum.kume.length}, tuketicinin odunc verilebilir saydigi 1`);
console.log(`birim degisikligi semayi degistirmez -> ${degerlendir(YAYIN, YAYIN).length} degisiklik`);
```

```
denetim: oneri C -> 1 degisiklik, 0 kirici, kapi yesil
eski deger  -> verilir=true  978-0262033848 verildi
yeni deger  -> verilir=false rafta yok (durum rafta-yeni, raftaAdet 2)
semadaki durum degeri 4, tuketicinin odunc verilebilir saydigi 1
birim degisikligi semayi degistirmez -> 0 degisiklik
```

**Kaçan hata sınıfı, biçimi bozmadan anlamı değiştiren değişikliktir.** Rafta duran bir
kitap ödünç verilemez oldu; şema açısından hiçbir kural çiğnenmedi. Kümeye değer eklemek
yanıt yönünde uyumlu sayılır, çünkü şema doğrulaması açısından öyledir — eski değerler
geçerliliğini korur. Kırılan şey şema değil, tüketicinin o kümeyi **tükettiğini varsayan**
kodudur. Bu varsayım hiçbir şemada yazılı değildir.

Son satır sınırı daha da net gösteriyor. Ödünç süresinin gün yerine hafta sayılmaya
başlaması, alan adını da türünü de değiştirmez; iki şema birebir aynı kalır ve denetim
sıfır değişiklik bildirir. Şemaya bakan hiçbir denetim birim, ölçek ya da anlam
değişikliğini göremez.

## Denetimin Bedeli

```js
// bedel.mjs — semaya bakan denetimin bedeli: sifir surec, sifir istek
import { createServer } from "node:http";
import { YAYIN, ONERI } from "./sema.mjs";
import { degerlendir } from "./uyumluluk.mjs";

const TEKRAR = 200, oneri = Object.keys(ONERI).length;
const t0 = performance.now();
for (let i = 0; i < TEKRAR; i += 1) for (const o of Object.values(ONERI)) degerlendir(YAYIN, o);
const t1 = performance.now();

// Karsilastirma noktasi: ayni makinede tek bir yerel istegin gidip gelmesi.
const sunucu = createServer((_, y) => y.end("{}"));
await new Promise((c) => sunucu.listen(0, "127.0.0.1", c));
const t2 = performance.now();
await (await fetch(`http://127.0.0.1:${sunucu.address().port}/`)).text();
const t3 = performance.now();
sunucu.close();

const alan = Object.keys(YAYIN.yanit).length + Object.keys(YAYIN.istek).length;
console.log(`sema ${alan} alan (yanit ${Object.keys(YAYIN.yanit).length}, istek ${Object.keys(YAYIN.istek).length}), ${oneri} oneri`);
console.log(`denetim ${TEKRAR * oneri} degerlendirme, 0 surec, 0 istek, 0 test verisi`);
console.log(`bir degerlendirme tek bir yerel istekten kisa : ${(t1 - t0) / (TEKRAR * oneri) < t3 - t2}`);
console.log(`oran en az elli kat                           : ${(t3 - t2) / ((t1 - t0) / (TEKRAR * oneri)) >= 50}`);
```

```
sema 9 alan (yanit 6, istek 3), 3 oneri
denetim 600 degerlendirme, 0 surec, 0 istek, 0 test verisi
bir degerlendirme tek bir yerel istekten kisa : true
oran en az elli kat                           : true
```

Mutlak süreler makineye bağlıdır; oranın yönü değişmez. Bu denetim hiçbir süreç ayağa
kaldırmıyor, hiçbir istek yollamıyor ve hiçbir test verisi hazırlamıyor: girdisi iki metin,
çıktısı bir sınıflandırılmış liste. Tek bir yerel isteğin süresinde yüzlerce değerlendirme
yapılabilir; bu yüzden denetim her değişiklik önerisinde koşturulabilir.

Koşumdan bağımsız bedel iki yerdedir. Birincisi, şemanın **yayımlanan** sürümünün bir
yerde saklanması gerekliliğidir: karşılaştırma iki metin ister, tek metinle uyumluluk
sorusu sorulamaz. İkincisi, sınıf tablosunun bakımıdır — dokuz satır elle yazılmıştır ve
şema diline yeni bir kısıt eklendiğinde tabloya da satır eklenmelidir. Eksik satır sessiz
kalır ve sessiz kalan bir denetim, olmayan bir denetimden ayırt edilemez.

## Özet

- Şema uyumluluk denetimi iki şema sürümünü karşılaştırır, farkı değişiklik türlerine
  ayırır ve her türü bir uyumluluk sınıfına yerleştirir.
- Aynı tür istek ve yanıt yönünde farklı sınıfa düşer; tek soru, değişiklikten sonra karşı
  taraftaki eski kodun hâlâ geçerli olup olmadığıdır.
- Dört değişiklik türünü bir arada taşıyan öneri üç kırıcı değişiklikle kırmızı döndü;
  aynı ihtiyaç genişlet–daralt sırasının birinci adımına çevrildiğinde kapı yeşile döndü.
- Kaçan sınıf biçimi bozmadan anlamı değiştiren değişikliktir: değer kümesine eklenen bir
  değer uyumlu sayıldı, oysa rafta duran kitap ödünç verilemez oldu. Birim değişikliğinde
  iki şema birebir aynı kalır ve denetim sıfır değişiklik bildirir.
- Bedel: sıfır süreç, sıfır istek, sıfır test verisi; karşılığında yayımlanan şemanın
  saklanması ve dokuz satırlık sınıf tablosunun elle bakımı gerekir.
- Denetim hangi tüketicinin kırıldığını söyleyemez; yalnız birinin kırılabileceğini söyler.

## Sonraki Adım

Üç denetim de sağlayıcının hazır olduğunu varsaydı: şema denetimi canlı servise istek
yolladı, sözleşme testi sağlayıcının sürümünü ayağa kaldırdı, uyumluluk denetimi yayımlanan
şemayı okudu. Oysa sözleşmenin en çok işe yaradığı an, sağlayıcının **henüz yazılmadığı**
andır. Tüketici takımı beklemek zorunda değildir, çünkü sözleşme bellidir. Sonraki ders
sözleşmeden yanıt üreten bir sahte sunucu kurar, ödünç servisini ona karşı geliştirir ve
tek bir soruyu ölçer: sahte sunucu gerçeğinden kaç noktada sapıyor ve bu sapmaların kaçını
buraya kadar kurulan denetimler kapatabiliyor.
