---
title: 'Kullanımdan Kaldırma Politikası'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/kullanimdan-kaldirma-politikasi'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:44+00:00'
license: 'CC BY-SA 4.0'
---

# Kullanımdan Kaldırma Politikası

Kullanımdan kaldırmanın yanıtın kendisinde duyurulması, tüketici başına sürüm telemetrisinin toplanması ve geçiş süresi sonunda eski sürümde kalan tüketici ile trafik oranının hesaplanması.

Fark alıcı yedi kırıcı değişiklik saptadı ve sürümü 2.0.0'a çıkardı. Sürüm numarasının
yükselmesi eski istemcileri kendiliğinden taşımaz; v1 hâlâ çalışır ve onu çağıran her
istemci çalışmaya devam eder. Kütüphanenin v1'i sonsuza kadar sürdürmesi de bir seçenek
değildir: iki sürüm demek, her hata düzeltmesinin iki kez yapılması ve her yeni alanın iki
biçimde üretilmesi demektir.

Aradaki yol **kullanımdan kaldırmadır**: sürüm önce kullanımdan kaldırılmış ilan edilir,
bir süre daha çalışır, sonra kapatılır. Bu ders üç şeyi kurar — duyurunun nereye
yazılacağı, kimin hâlâ eski sürümde olduğunun nasıl ölçüleceği ve kapanış tarihinin hangi
sayıya bakarak seçileceği.

## Duyurunun Yanıta Yazılması

Duyuru belgeye yazılırsa yalnızca belgeyi okuyanlar görür. Duyurunun ulaşması gereken
taraf, eski sürümü **çağıran koddur**; o kodu yazan kişinin belgeyi tam o hafta okuması
beklenemez. Bu yüzden duyuru yanıtın kendisine konur.

İki başlık bunu tanımlar. `Deprecation` kaynağın kullanımdan kaldırıldığı anı bildirir;
`Sunset` erişimin sonlanacağı anı. İkisi ayrı bilgidir: birincisi "artık bunu kullanmayın",
ikincisi "şu tarihte çalışmayacak". Yanına konan `Link` başlıkları da yerine geçen kaynağı
ve göç kılavuzunu gösterir; duyuru yalnızca "bu kalkacak" diyorsa hiçbir göçü hızlandırmaz.

```js
// sunucu.mjs — v1 kaynagini kullanimdan kaldirma duyurusuyla sunar
// Kullanim: node sunucu.mjs <bugun>   ornek: node sunucu.mjs 2026-04-10
import { createServer } from "node:http";

const BUGUN = new Date(process.argv[2] ?? "2026-04-10");
const DUYURU = new Date("2026-03-01T00:00:00Z");   // kullanimdan kaldirmanin ilan tarihi
const KAPANIS = new Date("2026-09-01T00:00:00Z");  // erisimin sonlanacagi tarih

const KAYIT = { id: "O-1", uye: "U-1001", isbn: "978-0262033848", iadeTarihi: "2026-03-20" };

// Telemetri: hangi tuketicinin hangi surumu kac kez cagirdigi.
const sayim = new Map();   // "tuketici|surum" -> istek sayisi

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

  if (yol === "/telemetri") {
    yanit.writeHead(200, { "content-type": "application/json; charset=utf-8" });
    return yanit.end(JSON.stringify([...sayim].map(([a, s]) => ({ tuketici: a.split("|")[0], surum: a.split("|")[1], istek: s }))));
  }

  const surum = /^\/v(\d+)\//.exec(yol)?.[1];
  const tuketici = istek.headers["api-tuketici"] ?? "bilinmiyor";
  if (surum) sayim.set(`${tuketici}|v${surum}`, (sayim.get(`${tuketici}|v${surum}`) ?? 0) + 1);

  if (yol === "/v2/odunc/O-1") {
    yanit.writeHead(200, { "content-type": "application/json; charset=utf-8" });
    return yanit.end(JSON.stringify({ id: KAYIT.id, uye: { kimlik: KAYIT.uye }, kalemler: [{ isbn: KAYIT.isbn }], sonTarih: KAYIT.iadeTarihi }));
  }

  if (yol === "/v1/odunc/O-1") {
    // Kapanis gecmisse kaynak artik yok: 410, kalici bir sonlanma bildirir.
    if (BUGUN >= KAPANIS) {
      yanit.writeHead(410, { "content-type": "application/problem+json; charset=utf-8", link: '</v2/odunc/O-1>; rel="successor-version"' });
      return yanit.end(JSON.stringify({
        type: "https://ornek.kutuphane/sorunlar/surum-kapandi", title: "Sürüm kapatıldı", status: 410,
        detail: `v1 ${KAPANIS.toUTCString()} tarihinde kapatıldı; /v2/odunc kullanılır.`, instance: "ol-0001",
      }));
    }
    // Duyuru: Deprecation kullanimdan kaldirma anini, Sunset erisimin bitecegi ani verir.
    yanit.writeHead(200, {
      "content-type": "application/json; charset=utf-8",
      deprecation: `@${Math.floor(DUYURU.getTime() / 1000)}`,
      sunset: KAPANIS.toUTCString(),
      link: '</v2/odunc/O-1>; rel="successor-version", </belgeler/goc-v2>; rel="deprecation"',
    });
    return yanit.end(JSON.stringify(KAYIT));
  }

  yanit.writeHead(404, { "content-type": "application/problem+json; charset=utf-8" });
  yanit.end(JSON.stringify({ type: "https://ornek.kutuphane/sorunlar/kaynak-yok", title: "Kaynak bulunamadı", status: 404, detail: `${yol} yok.`, instance: "ol-0002" }));
}).listen(8436, "127.0.0.1", () => console.log(`sunucu 127.0.0.1:8436 bugun=${BUGUN.toISOString().slice(0, 10)}`));
```

```bash
#!/usr/bin/env bash
# Kapanis oncesi/sonrasi ayni istek; ardindan telemetri dokumu.
for bugun in 2026-04-10 2026-09-02; do
  node sunucu.mjs "$bugun" & s=$!
  sleep 0.5
  echo "--- $bugun ---"
  curl -sS -D - -o /tmp/g -H 'Api-Tuketici: mobil-uygulama' http://127.0.0.1:8436/v1/odunc/O-1 \
    | grep -iE '^HTTP|^deprecation|^sunset|^link' | tr -d '\r'
  cat /tmp/g; echo
  if [ "$bugun" = "2026-04-10" ]; then
    curl -sS -o /dev/null -H 'Api-Tuketici: raf-terminali' http://127.0.0.1:8436/v1/odunc/O-1
    curl -sS -o /dev/null -H 'Api-Tuketici: mobil-uygulama' http://127.0.0.1:8436/v2/odunc/O-1
    echo "telemetri: $(curl -sS http://127.0.0.1:8436/telemetri)"
  fi
  kill "$s"; wait "$s" 2>/dev/null
done
```

```
sunucu 127.0.0.1:8436 bugun=2026-04-10
--- 2026-04-10 ---
HTTP/1.1 200 OK
deprecation: @1772323200
sunset: Tue, 01 Sep 2026 00:00:00 GMT
link: </v2/odunc/O-1>; rel="successor-version", </belgeler/goc-v2>; rel="deprecation"
{"id":"O-1","uye":"U-1001","isbn":"978-0262033848","iadeTarihi":"2026-03-20"}
telemetri: [{"tuketici":"mobil-uygulama","surum":"v1","istek":1},{"tuketici":"raf-terminali","surum":"v1","istek":1},{"tuketici":"mobil-uygulama","surum":"v2","istek":1}]
sunucu 127.0.0.1:8436 bugun=2026-09-02
--- 2026-09-02 ---
HTTP/1.1 410 Gone
link: </v2/odunc/O-1>; rel="successor-version"
{"type":"https://ornek.kutuphane/sorunlar/surum-kapandi","title":"Sürüm kapatıldı","status":410,"detail":"v1 Tue, 01 Sep 2026 00:00:00 GMT tarihinde kapatıldı; /v2/odunc kullanılır.","instance":"ol-0001"}
```

Üç ayrıntı önemlidir. Duyuru başlıkları **başarılı** yanıtla birlikte gelir; istek yerine
getirilmiştir, yalnızca bir uyarı eşlik eder. Kapanıştan sonra dönen kod 404 değil
**410**'dur: 404 "burada bir şey yok" der, 410 "burada bir şey vardı ve kalıcı olarak
kaldırıldı" der; ikincisi istemcinin yanlış adres kurduğunu sanmasını önler. Ve 410
yanıtında da `Link` başlığı durur, çünkü kapanış anı, göç bilgisine en çok ihtiyaç
duyulacak andır.

Telemetri satırı üçüncü parçadır. Sunucu her isteği tüketici kimliği ve sürümle sayar; bu
sayaç olmadan kapanış tarihi tahminle seçilir. Tüketici kimliği burada bir başlıkla
gelmektedir; kimlik doğrulama kurulduğunda bu bilgi belirteçten okunur ve istemcinin
bildirmesine bağlı kalmaz.

## Geçiş Süresinin Sonunda Kalan

Telemetri, kaç tüketicinin hangi hızda göç ettiğini verir. Bu hız kesime göre değişir:
etkin geliştirilen büyük tüketiciler hızlı, yıllardır dokunulmamış küçük tüketiciler yavaş
göç eder. Aşağıdaki program üç duyuru biçimi için geçiş süresi sonunda kalanı hesaplar.

```js
// gecis.mjs — gecis suresi sonunda eski surumde kalan tuketici ve trafik oranini hesaplar
// Model: her hafta, bir kesimin kalan tuketicilerinin sabit bir orani goc eder.

const KESIMLER = [
  { ad: "büyük", sayi: 4,   trafik: 0.60 },
  { ad: "orta",  sayi: 20,  trafik: 0.30 },
  { ad: "küçük", sayi: 176, trafik: 0.10 },
];

// Haftalik goc orani: duyurunun nasil yapildigina gore degisir.
const DUYURULAR = {
  "yalnız belgede":            { büyük: 0.05, orta: 0.03, küçük: 0.01 },
  "yanıt başlığında":          { büyük: 0.15, orta: 0.10, küçük: 0.04 },
  "başlık + doğrudan uyarı":   { büyük: 0.35, orta: 0.10, küçük: 0.04 },
};

const HAFTA = 26;   // 1 Mart - 1 Eylul arasi

const kalan = (oran, hafta) => (1 - oran) ** hafta;

console.log(`geçiş süresi: ${HAFTA} hafta   toplam tüketici: ${KESIMLER.reduce((t, k) => t + k.sayi, 0)}\n`);
console.log("duyuru biçimi              kalan tüketici  kalan trafik  büyük  orta  küçük");
for (const [ad, oranlar] of Object.entries(DUYURULAR)) {
  const kalanlar = KESIMLER.map((k) => kalan(oranlar[k.ad], HAFTA));
  const tuketici = KESIMLER.reduce((t, k, i) => t + k.sayi * kalanlar[i], 0);
  const trafik = KESIMLER.reduce((t, k, i) => t + k.trafik * kalanlar[i], 0);
  console.log(
    `${ad.padEnd(26)} ${Math.round(tuketici).toString().padStart(14)}  ${(trafik * 100).toFixed(1).padStart(11)}%  ` +
    kalanlar.map((o, i) => Math.round(KESIMLER[i].sayi * o).toString().padStart(5)).join(" ")
  );
}

// Kapanis olcutu trafik payiysa: pencerenin ne kadar uzamasi gerekir?
const ESIK = 0.005;
console.log(`\nkalan trafik %${ESIK * 100} altına inene kadar gereken hafta:`);
for (const [ad, oranlar] of Object.entries(DUYURULAR)) {
  let h = 0;
  while (KESIMLER.reduce((t, k) => t + k.trafik * kalan(oranlar[k.ad], h), 0) > ESIK && h < 500) h++;
  console.log(`  ${ad.padEnd(26)} ${String(h).padStart(3)} hafta (${(h / 4.35).toFixed(1)} ay)`);
}

// Kesim basina: kalan trafigin hangi kesimden geldigi
console.log("\n'yanıt başlığında' duyuruda kalan trafiğin kesimlere dağılımı:");
const o = DUYURULAR["yanıt başlığında"];
const kalanTrafik = KESIMLER.map((k) => k.trafik * kalan(o[k.ad], HAFTA));
const toplam = kalanTrafik.reduce((a, b) => a + b, 0);
KESIMLER.forEach((k, i) =>
  console.log(`  ${k.ad.padEnd(6)} ${(kalanTrafik[i] * 100).toFixed(2)}%  (kalan trafiğin %${((kalanTrafik[i] / toplam) * 100).toFixed(0)}'i)`));
```

```
geçiş süresi: 26 hafta   toplam tüketici: 200

duyuru biçimi              kalan tüketici  kalan trafik  büyük  orta  küçük
yalnız belgede                        146         37.1%      1     9   136
yanıt başlığında                       62          6.3%      0     1    61
başlık + doğrudan uyarı                62          5.4%      0     1    61

kalan trafik %0.5 altına inene kadar gereken hafta:
  yalnız belgede             299 hafta (68.7 ay)
  yanıt başlığında            75 hafta (17.2 ay)
  başlık + doğrudan uyarı     74 hafta (17.0 ay)

'yanıt başlığında' duyuruda kalan trafiğin kesimlere dağılımı:
  büyük  0.88%  (kalan trafiğin %14'i)
  orta   1.94%  (kalan trafiğin %31'i)
  küçük  3.46%  (kalan trafiğin %55'i)
```

Tablo üç sonuç veriyor.

Birincisi, duyurunun yanıta yazılmasının etkisi büyüktür: yalnız belgede duyurulduğunda
altı ay sonunda trafiğin yüzde 37'si hâlâ eski sürümdeyken, yanıt başlığıyla duyurulduğunda
bu oran yüzde 6,3'e iner. Aradaki fark bir politika değişikliği değil, duyurunun ulaştığı
yerin değişmesidir.

İkincisi, büyük tüketicilere doğrudan gidilmesi toplam trafiği yüzde 6,3'ten yalnız 5,4'e
indiriyor. Doğrudan uyarı büyük tüketicilerin göçünü belirgin biçimde hızlandırır, ama
onların payı zaten altı ay içinde erimiştir; kalan trafiğin yüzde 55'i küçük tüketicilerden
gelir ve onlara tek tek ulaşmanın maliyeti yüksektir. **Kalan trafiği azaltmanın yolu, en
büyük tüketicilerle konuşmaktan geçmez.**

Üçüncüsü ve en önemlisi, iki sayının birbirinden ayrılmasıdır. Yanıt başlığıyla duyuruda
geçiş süresi sonunda **62 tüketici** hâlâ eski sürümdedir ama bunlar trafiğin yalnız yüzde
6,3'ünü üretir. "Kaç tüketici kaldı" sorusuna verilen yanıt kapanışı erteletir, "trafiğin
ne kadarı kaldı" sorusuna verilen yanıt kapanışı mümkün kılar. İkisi aynı gerçekliğin iki
ölçüsüdür ve karar hangisine bakıldığına göre değişir.

## Kapanış Tarihinin Seçilmesi

Son tablo, ölçütün eşik olarak konmasının sonucunu gösteriyor: kalan trafiği yüzde 0,5'in
altına indirmek, yanıt başlığıyla duyuruda 75 hafta — on yedi ay — sürer. Altı aylık pencere
bu eşiği tutturamaz.

Buradan üç seçenek çıkar ve üçü de meşrudur. Pencere on yedi aya uzatılır ve iki sürüm o
kadar süre birlikte yaşar. Eşik gevşetilir; yüzde 6,3 kabul edilebilir sayılır ve kalan
tüketiciler kapanışta 410 alır. Ya da kapanış kademelendirilir: erişim önce belirli
saatlerde kesilir, sonra tümüyle kapatılır — böylece göç etmemiş tüketiciler kapanışı
üretim ortamında fark eder, ama tam kapanma anında hazırlıksız yakalanmazlar.

Seçimi belirleyen, kalan tüketicilerin **kim** olduğudur. Kalan yüzde 6,3 kütüphanenin
kendi raf terminalleriyse pencere kısa tutulur, çünkü göçü yaptıracak taraf da
kütüphanedir. Dışarıdaki ilçe kütüphanelerinin yazılımıysa pencere uzatılır. Telemetrinin
tüketici başına toplanmasının nedeni tam olarak budur: toplam oran bu kararı vermeye
yetmez.

Pencerenin takvimle değil **yayınla** ölçülmesi ayrıca yararlıdır: "bir ana sürüm boyunca"
ifadesi, yayın hızı değişse bile anlamını korur. Ama kapanış anı yanıtta bildirilecekse bir
tarihe çevrilmek zorundadır; `Sunset` başlığı bir tarih taşır, yayın numarası değil. İkisi
birlikte kullanılır: politika yayınla yazılır, duyuru tarihe çevrilir.

## Özet

- Duyurunun ulaşması gereken yer belge değil, eski sürümü çağıran koddur; bu yüzden
  `Deprecation` ve `Sunset` başlıkları başarılı yanıtla birlikte gönderilir.
- `Deprecation` kullanımdan kaldırma anını, `Sunset` erişimin sonlanacağı anı bildirir;
  yanlarındaki `Link` başlıkları yerine geçen kaynağı ve göç kılavuzunu gösterir.
- Kapanıştan sonra 404 değil 410 döner ve göç bağlantısı 410 yanıtında da durur.
- Duyurunun yanıta yazılması, altı ay sonunda eski sürümde kalan trafiği yüzde 37'den
  yüzde 6,3'e indirir.
- Kalan trafiğin yarıdan fazlası küçük tüketicilerden gelir; en büyük tüketicilere doğrudan
  gitmek toplam kalan trafiği yalnız yüzde 0,9 puan azaltır.
- Kalan tüketici sayısı ile kalan trafik oranı ayrı ölçülerdir ve kapanış kararı hangisine
  bakıldığına göre değişir; telemetrinin tüketici başına toplanması bu ayrımı yapabilmek
  içindir.

## Sonraki Adım

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 ve hepsi bugüne kadar
düzyazıyla anlatıldı. Düzyazı sözleşmenin iki sorunu vardır: makineye verilemez ve
gerçeklikle karşılaştırılamaz. Fark alıcı bir şema üzerinde çalışıyordu; o şema nereden
geliyor ve sunucunun gerçekten ürettiği yanıtla aynı olduğu nasıl bilinir? Sonraki ders
sözleşmeyi makine okunur bir tanıma çevirir, o tanımdan bir doğrulayıcı üretip gerçek
istekleri sınar ve şema ile gerçek yanıtın ayrıştığı durumu yakalar.
