---
title: 'Makine Okunur Belgeler'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/makine-okunur-belgeler'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:44+00:00'
license: 'CC BY-SA 4.0'
---

# Makine Okunur Belgeler

Sözleşmenin şema olarak yazılması, aynı tanımdan hem doğrulayıcı hem insan okur belge üretilmesi ve sunucunun ürettiği yanıt ile tanımın ayrıştığı durumun çalışma anında yakalanması.

Buraya kadar kurulan her şey — hata biçimi, alan düzeyi doğrulama, sürümler, kırıcılık
kuralları, kullanımdan kaldırma penceresi — sözleşmenin parçasıdır. Sözleşmenin nerede
yazılı olduğu ise açık kalmıştır. Düzyazıyla yazılırsa iki şey yapılamaz: makineye
verilemez ve gerçeklikle karşılaştırılamaz.

Fark alıcı zaten bir şema üzerinde çalışıyordu. Bu ders o şemayı sözleşmenin **tek
kaynağı** hâline getirir: aynı tanımdan doğrulayıcı ve belge üretilir, sonra sunucunun
gerçekten ürettiği yanıt aynı tanıma karşı sınanır.

## Sözleşmenin Şema Olarak Yazılması

Gövde şemaları için **JSON Schema** kullanılır; tip, zorunluluk, izin verilen değerler ve
desen bu sözcük dağarcığıyla ifade edilir. Şemaları saran yol/yöntem/yanıt düzeni ise
arayüz tanım biçimlerinin (OpenAPI) yaptığı işi yapar: hangi yolun hangi yöntemi
desteklediğini ve her durum kodu için hangi gövdenin döneceğini bildirir.

```js
// tanim.mjs — odunc servisinin makine okunur tanimi
// Govde semalari JSON Schema sozcukleriyle yazilmistir; disindaki sarmal, arayuz
// tanim bicimlerinin (OpenAPI) yol/yontem/yanit duzenini yansitir.

const ODUNC_ISTEK = {
  type: "object",
  required: ["uye", "kalemler"],
  additionalProperties: false,
  properties: {
    uye: { type: "string", pattern: "^U-\\d{4}$" },
    kalemler: {
      type: "array",
      items: { type: "object", required: ["isbn"], additionalProperties: false,
               properties: { isbn: { type: "string", pattern: "^97[89]-\\d{10}$" } } },
    },
    sube: { type: "string", enum: ["merkez", "sahil", "tepe"] },
  },
};

const ODUNC_YANIT = {
  type: "object",
  required: ["id", "uye", "kalemler", "iadeTarihi", "durum"],
  additionalProperties: false,
  properties: {
    id: { type: "string", pattern: "^O-\\d+$" },
    uye: { type: "string" },
    kalemler: { type: "array", items: { type: "object", required: ["isbn"], additionalProperties: false,
                properties: { isbn: { type: "string" } } } },
    iadeTarihi: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$" },
    durum: { type: "string", enum: ["acik", "kapali"] },
  },
};

const SORUN = {
  type: "object",
  required: ["type", "title", "status", "detail", "instance"],
  properties: {
    type: { type: "string" }, title: { type: "string" }, status: { type: "integer" },
    detail: { type: "string" }, instance: { type: "string" },
  },
};

export const TANIM = {
  "POST /odunc": { istek: ODUNC_ISTEK, yanit: { 201: ODUNC_YANIT, 422: SORUN } },
  "GET /odunc/{id}": { istek: null, yanit: { 200: ODUNC_YANIT, 404: SORUN } },
};
```

Tanımın hata yanıtlarını da kapsadığına dikkat edilmeli. Sözleşme yalnız başarı gövdesini
tanımlarsa, istemci hata durumunda ne bekleyeceğini yine bilemez; ilk derste kurulan
problem ayrıntısı biçimi de burada şema olarak yazılır ve böylece denetlenebilir hâle
gelir.

## Tanımdan Doğrulayıcı Üretmek

Şemanın makine okunur olması, ondan bir doğrulayıcı **üretilebilmesi** demektir. Aşağıdaki
program JSON Schema'nın kullanılan alt kümesini yorumlar ve her şema için bir doğrulama
işlevi döndürür.

```js
// dogrulayici.mjs — JSON Schema alt kumesinden dogrulayici uretir
// Desteklenen sozcukler: type, required, properties, items, enum, pattern, additionalProperties

const tipi = (d) =>
  d === null ? "null" : Array.isArray(d) ? "array"
    : typeof d === "number" ? (Number.isInteger(d) ? "integer" : "number") : typeof d;

function sina(sema, deger, yol, hatalar) {
  const t = tipi(deger);
  if (sema.type && t !== sema.type && !(sema.type === "number" && t === "integer")) {
    hatalar.push(`${yol || "/"}: ${sema.type} bekleniyordu, ${t} geldi`);
    return;
  }
  if (sema.enum && !sema.enum.includes(deger)) hatalar.push(`${yol}: ${JSON.stringify(deger)} izin verilen değerlerde yok`);
  if (sema.pattern && !new RegExp(sema.pattern).test(String(deger))) hatalar.push(`${yol}: ${JSON.stringify(deger)} desene uymuyor`);

  if (sema.type === "object") {
    for (const ad of sema.required ?? []) if (!(ad in deger)) hatalar.push(`${yol}/${ad}: zorunlu alan yok`);
    if (sema.additionalProperties === false) {
      for (const ad of Object.keys(deger)) {
        if (!(ad in (sema.properties ?? {}))) hatalar.push(`${yol}/${ad}: tanımda olmayan alan`);
      }
    }
    for (const [ad, alt] of Object.entries(sema.properties ?? {})) {
      if (ad in deger) sina(alt, deger[ad], `${yol}/${ad}`, hatalar);
    }
  }
  if (sema.type === "array" && sema.items) deger.forEach((o, i) => sina(sema.items, o, `${yol}/${i}`, hatalar));
}

// Semadan bir dogrulayici islev uretir: (deger) -> hata listesi
export const dogrulayiciUret = (sema) => (deger) => {
  const hatalar = [];
  sina(sema, deger, "", hatalar);
  return hatalar;
};
```

Hata iletilerinin gövde yolunu taşıdığına dikkat edilmeli: doğrulama hataları dersinde
kurulan alan adlandırma sözleşmesi burada kendiliğinden korunur, çünkü yol şemanın
gezilmesi sırasında üretilir. Şema tek kaynak olduğunda, alan adları da tek kaynaktan
gelir.

Aynı tanımdan insan okur belge de üretilir. Belge ile doğrulayıcının aynı dosyadan gelmesi,
belgelendirme sapmasını yapısal olarak imkânsız kılar.

```js
// belge.mjs — dogrulayiciyi ureten tanimdan insan okur belge uretir
import { TANIM } from "./tanim.mjs";

const nitelik = (s) => [
  s.type,
  s.enum ? `değerler: ${s.enum.join("|")}` : null,
  s.pattern ? `desen: ${s.pattern}` : null,
].filter(Boolean).join(", ");

function yaz(sema, girinti = "  ") {
  if (sema.type === "object") {
    for (const [ad, alt] of Object.entries(sema.properties ?? {})) {
      const zorunlu = (sema.required ?? []).includes(ad) ? "zorunlu" : "isteğe bağlı";
      console.log(`${girinti}${ad.padEnd(12)} ${zorunlu.padEnd(12)} ${nitelik(alt)}`);
      if (alt.type === "object" || alt.type === "array") yaz(alt.type === "array" ? alt.items : alt, girinti + "  ");
    }
    if (sema.additionalProperties === false) console.log(`${girinti}(tanımda olmayan alan kabul edilmez)`);
  }
}

const SUZGEC = process.argv[2];   // istege bagli: yalniz bu anahtar yazilir

for (const [anahtar, t] of Object.entries(TANIM)) {
  if (SUZGEC && anahtar !== SUZGEC) continue;
  console.log(`\n## ${anahtar}`);
  if (t.istek) { console.log(" istek gövdesi:"); yaz(t.istek); }
  for (const [kod, sema] of Object.entries(t.yanit)) { console.log(` yanıt ${kod}:`); yaz(sema); }
}
```

```bash
node belge.mjs "POST /odunc"
```

```
## POST /odunc
 istek gövdesi:
  uye          zorunlu      string, desen: ^U-\d{4}$
  kalemler     zorunlu      array
    isbn         zorunlu      string, desen: ^97[89]-\d{10}$
    (tanımda olmayan alan kabul edilmez)
  sube         isteğe bağlı string, değerler: merkez|sahil|tepe
  (tanımda olmayan alan kabul edilmez)
 yanıt 201:
  id           zorunlu      string, desen: ^O-\d+$
  uye          zorunlu      string
  kalemler     zorunlu      array
    isbn         zorunlu      string
    (tanımda olmayan alan kabul edilmez)
  iadeTarihi   zorunlu      string, desen: ^\d{4}-\d{2}-\d{2}$
  durum        zorunlu      string, değerler: acik|kapali
  (tanımda olmayan alan kabul edilmez)
 yanıt 422:
  type         zorunlu      string
  title        zorunlu      string
  status       zorunlu      integer
  detail       zorunlu      string
  instance     zorunlu      string
```

## Yanıtın Tanıma Karşı Denetlenmesi

İsteği doğrulamak yaygın bir uygulamadır. Asıl sapma, sunucunun **ürettiği** yanıtta
oluşur: bir alan eklenir, bir değer kümesi genişler, bir alan bazı durumlarda dönmemeye
başlar. Bunların hiçbiri isteği doğrulayarak yakalanmaz.

Aşağıdaki sunucu yanıtı yazan tek bir noktaya sahiptir ve o noktada yanıtı da tanıma karşı
sınar. Sapmalar bir listeye kaydedilir ve `/denetim` yolundan okunur. İçine bilerek bir
sapma konmuştur: `O-1` kaydı için tanımda olmayan bir alan ve tanımda olmayan bir durum
değeri üretilir.

```js
// sunucu.mjs — istegi tanimdan uretilen dogrulayiciyla siner, yaniti da tanima karsi denetler
import { createServer } from "node:http";
import { TANIM } from "./tanim.mjs";
import { dogrulayiciUret } from "./dogrulayici.mjs";

// Tanimdaki her sema icin bir kez dogrulayici uretilir.
const ISTEK_DOGRULAYICI = Object.fromEntries(
  Object.entries(TANIM).filter(([, t]) => t.istek).map(([a, t]) => [a, dogrulayiciUret(t.istek)]));
const YANIT_DOGRULAYICI = Object.fromEntries(
  Object.entries(TANIM).flatMap(([a, t]) => Object.entries(t.yanit).map(([k, s]) => [`${a} ${k}`, dogrulayiciUret(s)])));

const sapmalar = [];   // yanit ile tanimin ayristigi yerler

const oduncler = new Map([["O-1", { id: "O-1", uye: "U-1001", kalemler: [{ isbn: "978-0262033848" }], iadeTarihi: "2026-03-20", durum: "acik" }]]);
let sayac = 1;

const govdeOku = (istek) =>
  new Promise((coz) => { let v = ""; istek.on("data", (p) => (v += p)); istek.on("end", () => coz(v)); });

// Yanit yazan tek nokta: tanima karsi denetim burada yapilir.
const yanitla = (yanit, anahtar, kod, govde, tip = "application/json") => {
  const dogrula = YANIT_DOGRULAYICI[`${anahtar} ${kod}`];
  const hatalar = dogrula ? dogrula(govde) : ["tanımda bu yanıt kodu yok"];
  if (hatalar.length) sapmalar.push({ anahtar, kod, hatalar });
  yanit.writeHead(kod, { "content-type": `${tip}; charset=utf-8` });
  yanit.end(JSON.stringify(govde));
};

const sorun = (tur, baslik, kod, ayrinti) =>
  ({ type: `https://ornek.kutuphane/sorunlar/${tur}`, title: baslik, status: kod, detail: ayrinti, instance: `ol-${sayac++}` });

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const yol = istek.url.split("?")[0];

  if (yol === "/denetim") {
    yanit.writeHead(200, { "content-type": "application/json; charset=utf-8" });
    return yanit.end(JSON.stringify(sapmalar, null, 1));
  }

  if (istek.method === "POST" && yol === "/odunc") {
    const anahtar = "POST /odunc";
    const govde = JSON.parse((await govdeOku(istek)) || "{}");
    const hatalar = ISTEK_DOGRULAYICI[anahtar](govde);
    if (hatalar.length) {
      const g = sorun("dogrulama", "İstek gövdesi doğrulanamadı", 422, hatalar.join("; "));
      return yanitla(yanit, anahtar, 422, g, "application/problem+json");
    }
    const kayit = { id: `O-${oduncler.size + 1}`, uye: govde.uye, kalemler: govde.kalemler, iadeTarihi: "2026-04-15", durum: "acik" };
    oduncler.set(kayit.id, kayit);
    return yanitla(yanit, anahtar, 201, kayit);
  }

  if (istek.method === "GET" && yol.startsWith("/odunc/")) {
    const anahtar = "GET /odunc/{id}";
    const kayit = oduncler.get(yol.slice("/odunc/".length));
    if (!kayit) return yanitla(yanit, anahtar, 404, sorun("kaynak-yok", "Kaynak bulunamadı", 404, `${yol} yok.`), "application/problem+json");
    // SAPMA: gecikmis kayitlar icin tanimda olmayan alan ve deger uretiliyor.
    const cikti = kayit.id === "O-1"
      ? { ...kayit, durum: "gecikmis", gecikmeGunu: 12 }
      : kayit;
    return yanitla(yanit, anahtar, 200, cikti);
  }

  yanit.writeHead(404, { "content-type": "application/problem+json; charset=utf-8" });
  yanit.end(JSON.stringify(sorun("kaynak-yok", "Kaynak bulunamadı", 404, `${yol} yok.`)));
}).listen(8437, "127.0.0.1", () => console.log("sunucu 127.0.0.1:8437"));
```

```bash
#!/usr/bin/env bash
# Gecerli istek, gecersiz istek ve tanimla ayrisan yanit; sonra denetim dokumu.
node sunucu.mjs & s=$!
sleep 0.5

echo "--- gecerli istek ---"
curl -sS -w '  [%{http_code}]\n' -X POST -H 'content-type: application/json' \
  -d '{"uye":"U-1002","kalemler":[{"isbn":"978-0201896831"}],"sube":"sahil"}' http://127.0.0.1:8437/odunc

echo "--- gecersiz istek ---"
curl -sS -w '  [%{http_code}]\n' -X POST -H 'content-type: application/json' \
  -d '{"uye":"1002","kalemler":[{"isbn":"0201896831","adet":2}],"sube":"deniz","not":"acele"}' http://127.0.0.1:8437/odunc

echo "--- tanimla ayrisan yanit ---"
curl -sS -w '  [%{http_code}]\n' http://127.0.0.1:8437/odunc/O-1

echo "--- denetim ---"
curl -sS http://127.0.0.1:8437/denetim

kill "$s"; wait "$s" 2>/dev/null
```

```
sunucu 127.0.0.1:8437
--- gecerli istek ---
{"id":"O-2","uye":"U-1002","kalemler":[{"isbn":"978-0201896831"}],"iadeTarihi":"2026-04-15","durum":"acik"}  [201]
--- gecersiz istek ---
{"type":"https://ornek.kutuphane/sorunlar/dogrulama","title":"İstek gövdesi doğrulanamadı","status":422,"detail":"/not: tanımda olmayan alan; /uye: \"1002\" desene uymuyor; /kalemler/0/adet: tanımda olmayan alan; /kalemler/0/isbn: \"0201896831\" desene uymuyor; /sube: \"deniz\" izin verilen değerlerde yok","instance":"ol-1"}  [422]
--- tanimla ayrisan yanit ---
{"id":"O-1","uye":"U-1001","kalemler":[{"isbn":"978-0262033848"}],"iadeTarihi":"2026-03-20","durum":"gecikmis","gecikmeGunu":12}  [200]
--- denetim ---
[
 {
  "anahtar": "GET /odunc/{id}",
  "kod": 200,
  "hatalar": [
   "/gecikmeGunu: tanımda olmayan alan",
   "/durum: \"gecikmis\" izin verilen değerlerde yok"
  ]
 }
]
```

Geçersiz istek beş ayrı ihlalle reddedildi ve hiçbir kural elle yazılmadı; hepsi tanımdan
geldi. Asıl önemli olan üçüncü satırdır: sunucu tanımda olmayan bir alan ve tanımda olmayan
bir durum değeri içeren bir yanıt üretti, istemci bu yanıtı **200 kodu ve tam gövdeyle**
aldı, ama sapma kayda geçti.

Bu, bir önceki dersteki kırıcılık sınıflandırmasının çalışma anındaki karşılığıdır.
`gecikmis` değerinin eklenmesi, yanıt yönünde değer kümesinin genişlemesidir ve hoşgörü
bildirimi yoksa kırıcıdır. Fark alıcı bunu iki şema karşılaştırıldığında bulur; buradaki
denetim ise şema hiç güncellenmeden kodun değiştiği durumu bulur. İkisi farklı kaçakları
kapatır: biri bilinçli değişikliği sınıflandırır, diğeri bilinçsiz değişikliği görünür
kılar.

## Denetimin Engelleyici Olmaması

Yanıt tanımdan saptığında iki davranış seçilebilir. Denetim **engelleyici** olursa sunucu
kendi yanıtını reddedip 500 üretir; **kaydedici** olursa yanıtı gönderir ve sapmayı
kütüğe yazar. Yukarıdaki sunucu ikincisini yapar.

Seçim, sapmanın kime zarar verdiğine bakar. Tanımda olmayan bir alanın eklenmesi çoğu
istemci için zararsızdır; o yanıtı engellemek, çalışan bir işlevi kullanılamaz hâle
getirir. Buna karşılık geliştirme ve sınama ortamlarında engelleyici kip yerindedir: sapma
üretim ortamına ulaşmadan kırılır. Aynı denetim, ortama göre iki farklı davranışla
çalıştırılır.

Kaydedici kipin bir koşulu vardır: sapma listesinin okunması. Kimsenin bakmadığı bir kütüğe
yazılan sapma, hiç saptanmamış sapmayla aynı şeydir.

## Özet

- Sözleşme düzyazıyla yazıldığında makineye verilemez ve gerçeklikle karşılaştırılamaz;
  şema olarak yazıldığında ikisi de yapılabilir.
- Gövde şemaları JSON Schema ile, yol/yöntem/yanıt düzeni arayüz tanım biçimlerinin
  yaptığı işi yapan bir sarmalla yazılır ve hata yanıtlarını da kapsar.
- Aynı tanımdan hem doğrulayıcı hem insan okur belge üretildiğinde belgelendirme sapması
  yapısal olarak olanaksızlaşır; alan adları da tek kaynaktan gelir.
- Yalnız isteği doğrulamak bir boşluk bırakır: sapma çoğunlukla sunucunun ürettiği yanıtta
  oluşur ve ancak yanıt da tanıma karşı sınanırsa yakalanır.
- Yanıt denetimi, şema hiç güncellenmeden kodun değiştiği durumu bulur; fark alıcı ise
  bilinçli olarak değiştirilmiş iki şemayı sınıflandırır.
- Denetim üretim ortamında kaydedici, geliştirme ve sınama ortamında engelleyici
  çalıştırılır; kaydedici kip yalnızca sapma listesi okunuyorsa iş görür.

## Sonraki Adım

Tanım artık sunucunun gerçekten ne ürettiğini denetliyor, ama tek bir soruyu yanıtsız
bırakıyor: tüketicinin gerçekten **kullandığı** alanlar hangileri? Tanım `kalemler`
dizisinin her öğesinde `isbn` bulunduğunu söyler; raf terminali bu alanı okuyor mudur,
yoksa yalnız `id` ve `durum` mu ona yetiyor? Bu bilinmeden hangi değişikliğin kimi
bozacağı kestirilemez ve her değişiklik en kötü duruma göre planlanır. Sonraki ders
tüketici beklentilerini dosyaya yazar, sağlayıcıya karşı çalıştırır ve sağlayıcı kırıcı bir
değişiklik yaptığında hangi tüketicinin sınamasının düştüğünü gösterir.
