Ders 22 / 34
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ı.
İçindekiler
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.
// 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)}`));
#!/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.
// 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
DeprecationveSunsetbaşlıkları başarılı yanıtla birlikte gönderilir. Deprecationkullanımdan kaldırma anını,Sunseterişimin sonlanacağı anı bildirir; yanlarındakiLinkbaş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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.