# Şema Evrimi: Sözleşmeyi Kırmadan Değiştirmek

> Servisler arası bir sözleşmeyi atomik değiştiremezsin. Kural: additive güvenli, kırıcı değişiklik yeni bir kimlik ister — ve önce tüketiciler yükseltir

- Tür: Denemeler
- Yayın: 2026-06-17
- Güncelleme: 2026-09-06
- Etiketler: architecture, schema, queue, opinion
- Okuma süresi: 3 dk okuma
- Kaynak: https://muhammetsafak.com/tr/muhendislik/denemeler/sema-evrimi-sozlesmeyi-kirmadan/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
> **Kısaca:** Servisler arası bir sözleşmeyi atomik değiştiremezsin; tek güvenli değişiklik eskiyle yeninin bir arada var olabildiğidir: gevşetmek güvenli, sıkılaştırmak değil. Opsiyonel alan eklemek ya da bir required kısıtını kaldırmak eski datayı geçerli bırakır; zorunlu alan eklemek, yeniden adlandırmak, tip değiştirmek ya da additionalProperties'i kapatmak bırakmaz. Kırıcıysa yeni bir kimlik üret ve önce tüketicileri yükselt.

Biri bir olaya zorunlu bir `customer_id` alanı ekledi ve üreticiyi deploy etti. Tüketicilerin yarısı henüz bu değişikliği shiplememişti. Yirmi dakika boyunca, eski tüketicilerin bir yıldır sorunsuz işlediği her mesaj birden validation'dan kalmaya başladı — ta ki rollout tamamlanana kadar. Hiçbir kod "yanlış" değildi. Bir sözleşme, ona bağlı olanların altından çekilip değiştirildi.

Bir mesajın `data` şekli bir API'dir. Response body yerine queue'da JSON olması onu daha az sözleşme yapmaz — *daha zor* bir sözleşme yapar, çünkü iki tarafı aynı anda değiştiremezsin.

## Dağıtık bir sözleşmeyi atomik değiştiremezsin

HTTP API'sinin kontrol ettiğin tek bir sunucusu vardır. Yayınlanan bir mesajın ise **bağımsız deploy edilen, kendi takvimlerinde olan birçok tüketicisi** vardır — ve at-least-once teslimatla, eski şekille üretilmiş yoldaki mesajlar, sen yenisini yayarken hâlâ geliyordur. "Herkesin yeni şemada olduğu" bir an yoktur. Yani tek güvenli değişiklikler, **eski ve yeninin bir arada var olabildiği** değişikliklerdir.

Bu tek kısıt sana tüm kural setini verir.

## Ne güvenli, ne değil?

Bir değişiklik, eski şema altında geçerli olan data yeni şema altında da geçerliyse backward-compatible'dır — böylece erken yükselten bir tüketici, henüz yükseltmemiş bir üreticinin yaydığı mesajları hâlâ kabul eder.

- **Opsiyonel bir alan ekle** — güvenli. Eski data onsuz olur, olur.
- **Bir `required` kısıtını kaldır** (alanı opsiyonel yap) — güvenli. Eski data daha gevşek kuralı zaten karşılar.
- **Bir `enum`'ı genişlet, bir `minimum`'u gevşet** — güvenli.

Ve ısıranlar:

- **Zorunlu bir alan ekle** ya da **opsiyonel bir alanı zorunlu yap** — kırıcı. Eski data onu içermez.
- **Bir alanı kaldır, yeniden adlandır ya da tipini değiştir** — kırıcı. Yeniden adlandırma, bir kaldırma artı bir eklemedir.
- **Kuralları sıkılaştır** — bir `enum` değerini düşür, bir `minimum`'u yükselt, `additionalProperties`'i kapat — kırıcı.

Asimetri asıl mesele: **gevşetmek güvenli, sıkılaştırmak değil.** Bir tüketici, beklediğinden daha hoşgörülü bir data'yı, artık talep ettiği bir şeyi eksik bir data'ya göre çok daha kolay tolere eder.

## Kırıcıysa, kimliği sürümle — değiştirme

Kırıcı bir değişiklikte içgüdü "şemayı güncelleyiver" demek. Deme. Mevcut bir kimliğin arkasındaki şekli değiştirmek, yukarıdaki rollout'u kıran şeyin ta kendisi. Bunun yerine **yeni bir kimlik üret** — yeni bir mesaj URN'i (`urn:babel:orders:created.v2`), yeni bir topic, yeni bir olay adı — ve ikisini paralel koştur:

1. Üreticiler `v1` yaymaya devam eder; sen `v2`'yi yanında yayınlarsın.
2. Tüketiciler kendi takvimlerinde `v2`'ye göç eder.
3. `v1`'in tüketicisi kalmayınca onu emekliye ayır.

Her adımın altındaki kural: **önce tüketiciler yükseltir.** Hiçbir deploy edilmiş tüketicinin anlamadığı bir sürümü asla yayma. (Bu, [bir mükerrer teslimat no-op olacak şekilde handler tasarlamanın](/tr/muhendislik/notlar/idempotency-ayni-mesaj-iki-kez/) aynı şekli — sırayı kontrol etmediğin için, değişikliği her sırada uygulanabilir kılarsın.)

## Kuralı mekanik hâle getir

Bunların hiçbiri, cuma akşamı 5'te bir code review'da yeniden türetmek isteyeceğin bir yargı değil. Yukarıdaki uyumluluk kuralları deterministik — eski ve yeni şema verildiğinde, bir araç sana "additive, ship et" ya da "kırıcı, yeni sürüm üret" diyebilir, hiçbir görüş katmadan. O yüzden ben bunu kapıya koyuyorum: sınırda iki şemayı diff'leyip kırıcı değişiklikte build'i kıran bir kontrol — aşağı tarafa ulaşmadan önce. Orada yakalamak bir PR yorumuna mal olur; production'da yakalamak yukarıdakine benzer bir rollout penceresine.

Bu, ["darboğaz aşağı kaydı"](/tr/muhendislik/denemeler/ai-darbogazi-kaldirmadi/) tezinin sıkıcı-altyapı ucu: şema ucuza değiştirilir ama *yanlış* değiştirmek pahalıdır, o yüzden bir parça araçla o ikinci maliyeti masadan kaldırırsın.

## Bu ne zaman önemsizleşir?

Bir mesajın tam olarak tek bir üreticisi ve birlikte deploy olan tek bir tüketicisi varsa — tek bir servisin iç queue'su — sözleşme aslında dağıtık değildir; iki tarafı aynı anda değiştirebilirsin. O zaman bu tören fazlalıktır. Kurallar, *ikinci*, bağımsız deploy edilen bir tüketici var olduğu an karşılığını vermeye başlar. İlk ayını aşan çoğu olay o noktaya ulaşır.

---

Bir şema sahip olduğun bir struct değildir; başka servislerin etrafında plan yaptığı bir sözdür. Onu, geri alamayacağın herhangi bir sözü nasıl evrimleştirirsen öyle evrimleştir — additive olarak ya da yeni bir ad altında. Asla eskisini sessizce yeniden tanımlayarak değil.

---

*İlgili:* [BabelQueue schema-validation spec'i](https://babelqueue.com/docs/spec/1.x/schema-validation) bu uyumluluk kurallarını yazıya döker; [babelqueue-registry](https://github.com/BabelQueue/babelqueue-registry) URN-başına şemaların yaşadığı yerdir — onun `bqschema` aracı (ve paketlenmiş Action'ı) kırıcı değişiklikte build'i kıran sınır kontrolüdür.

## Kaynaklar

- [JSON Schema Validation 2020-12: required, enum and minimum](https://json-schema.org/draft/2020-12/json-schema-validation) — JSON Schema
- [JSON Schema Core 2020-12: the applicator vocabulary and additionalProperties](https://json-schema.org/draft/2020-12/json-schema-core) — JSON Schema
- [Schema Evolution and Compatibility: compatibility types and upgrade order](https://docs.confluent.io/platform/current/schema-registry/fundamentals/schema-evolution.html) — Confluent

## Sık sorulanlar

### Hangi şema değişiklikleri backward-compatible sayılır?

Kuralları gevşetenler: opsiyonel alan eklemek, bir required kısıtını kaldırmak, enum'ı genişletmek, minimum'u gevşetmek. Eski şema altında geçerli olan data yeni şema altında da geçerli kalır; böylece erken yükselten bir tüketici, henüz yükseltmemiş bir üreticinin mesajlarını hâlâ kabul eder.

### Kırıcı bir değişiklikte önce kim yükseltir, üreticiler mi tüketiciler mi?

Tüketiciler. Hiçbir deploy edilmiş tüketicinin anlamadığı bir sürümü asla yayma. Üreticiler v1 yaymaya devam eder, v2 onun yanında yayınlanır, tüketiciler kendi takvimlerinde göç eder ve v1'in tüketicisi kalmayınca emekliye ayrılır.
