---
title: "Şema Evrimi: Sözleşmeyi Kırmadan Değiştirmek"
description: "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"
url: https://sade.dev/tr/journal/sema-evrimi-sozlesmeyi-kirmadan/
lang: tr
author: "Muhammet Şafak"
published: 2026-06-17
updated: 2026-09-06
section: Journal
tags: ["architecture","schema","queue","opinion"]
summary: "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."
---

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

> 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/notes/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/journal/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.

## 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.


## 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
