İçeriğe geç

Strangler Fig ve API Sürümleme — Monoliti Kimseyi Kırmadan Taşımak

Orta 10 dk Sık karşılaşılır

Önce şunu oku: Servis Sınırları — Bounded Context ve Database-per-Service , API Gateway ve Service Discovery — İstek Doğru Kapıyı Nasıl Bulur

30 saniyede özet

Monoliti bir gecede yeniden yazmak yerine önüne bir yönlendirici koyup yetenekleri tek tek taşırsın. Model değişirken eski istemciler eski şekli almaya devam eder: alan eklenir, sürüm açılır, eskisi duyurulup kapatılır.

Bir restoranın mutfağını yenilemen gerekiyor, ama restoran bir akşam bile kapanamaz. Masalar dolu, siparişler durmadan geliyor.

Restoran açık kalır, mutfak tezgâh tezgâh taşınır.
Adım adım oku
  1. Masalardan gelen her sipariş önce garsona uğrar; garson hangi tezgâha gideceğini bilir.
  2. Salata tezgâhı yeni mutfağa taşınır. Eski mutfak geri kalan her şeyi pişirmeye devam eder.
  3. Garson yalnızca salata siparişlerini yeni mutfağa götürür. Masalar bir şey fark etmez.
  4. Tezgâhlar birer birer taşınır; sonunda eski mutfak boşalır ve restoran hiç kapanmamıştır.
  1. Bayt: Monolit çok büyüdü. Bir hafta sonu oturup her şeyi mikroservis olarak baştan yazalım mı?

  2. Sen: Bir hafta sonu yeter mi ki?

  3. Bayt: Yetmez! O sırada eski uygulama da değişmeye devam eder, yenisi hep arkasından koşar.

  4. Bayt: Başka bir yol var: restoranı kapatmadan mutfağı tezgâh tezgâh taşımak.

Önde bir kapı, arkada adım adım taşınma

strangler figBir monoliti tek seferde yeniden yazmak yerine önüne bir yönlendirici koyup yetenekleri tek tek yeni servislere taşımak. Adını, konak ağacını yavaşça saran incir türünden alır.Sözlükte gör → deseninde monolitin önüne bir yönlendirici konur: bir facade, çoğu zaman bir API gateway. Başta bütün istekleri olduğu gibi monolite geçirir.

Sonra yetenekler tek tek yeni servislere taşınır. Facade yalnızca taşınan yeteneğin yolunu yeni adrese çevirir; istemciler hep aynı adresi çağırır. Bir adım ters giderse geri almak, o tek yolu eski yerine çevirmektir.

İlk taşınan yetenek genellikle az bağımlı ve kaybı küçük olandır, katalog gibi. Para taşıyan ödeme sona kalır.

Yeni servis eski modeli miras almaz. Arada bir anti-corruption layerYeni sistemle eski sistem arasında modelleri birbirine çeviren ince katman. Eski modelin kavramları yeni servisin içine sızmaz, eski istemciler de eski şekli görmeye devam eder.Sözlükte gör → durur ve iki modeli birbirine çevirir. Böylece monolitin eski tuhaflıkları yeni servise sızmaz.

Kafam karıştı, daha basit anlat

Önce kapıya bir garson koy. Sonra tezgâhları tek tek yeni mutfağa taşı ve garsona yalnızca o tezgâhın yeni yerini söyle. Masalar hiçbir şey fark etmez.

Hızlı kontrolOrta

Strangler fig yaklaşımıyla bir monolitten servis ayırmak nasıl işler?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Strangler fig taşımasında istemcilerin önündeki facade (ya da gateway) neden bu kadar önemli?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sözleşmeyi kırmadan değiştirmek

Taşırken modeller de değişir. Yeni sipariş servisi tutarı artık para birimiyle birlikte tutuyor.

Sipariş cevabındaki total alanı düz bir sayıydı; yeni serviste amount ve currency taşıyan bir nesne oldu. Web'i aynı gün güncelledin. Telefonlarda kalan eski mobil uygulama ne görür? Cevabı göster

Cevabı okuyamaz. Eski uygulama total’de bir sayı bekliyor, karşısına bir nesne çıkıyor. Kimse güncelleyemediği için bu kırılma, eski sürüm kullanıldıkça sürer.

Bir API’nin cevabı, onu çağıran herkesle yapılmış bir sözleşmedir. Kısa kural: eklemek güvenlidir; silmek, adını ya da şeklini değiştirmek kırar.

Eklemenin güvenli olması istemciye bağlıdır. tolerant readerCevaptan yalnızca ihtiyaç duyduğu alanları okuyan, tanımadığı alanları görmezden gelen istemci. Sunucunun alan eklemesi onu kırmaz.Sözlükte gör → yalnızca ihtiyacı olan alanları okur ve tanımadığını görmezden gelir. Tanımadığı alanda hata veren katı bir istemci, masum bir eklemede bile kırılır.

Şekli gerçekten değiştirmen gerekiyorsa yeni bir sürüm açar, eskisini bir süre yaşatırsın. İki yaygın yol var:

URI: /v2/ordersBaşlık: Accept-Version: 2
GörünürlükLog’da ve tarayıcıda hemen görünürİsteğin başlığına bakmak gerekir
ÖnbellekAdres farklı, kendiliğinden ayrılırVary başlığı gerekir
Başlıksız / eski istekEski yol eskisi gibi çalışırHangi sürümü alacağı senin kararın

İkisi de işe yarar. Önemli olan, eski sürümün bir çeviri katmanıyla yaşaması ve duyurularak kapatılmasıdır: cevaba Sunset başlığı eklenir, log’dan onu kimin çağırdığı izlenir.

Spring'de sürüm· istersen atla

Spring Framework 7 (Spring Boot 4) sürümlemeyi yerleşik destekler: @GetMapping(version = "2") yazılır, sürümün nereden okunacağı configureApiVersioning ile seçilir.

Kafam karıştı, daha basit anlat

Sözleşmeye yeni bir madde eklemek kimseyi üzmez. Bir maddeyi silmek ya da değiştirmek imzalayanları zor durumda bırakır. Değiştirmen gerekiyorsa yeni bir sözleşme aç, eskisini bir süre yaşat.

Hızlı kontrolOrta

Bir REST cevabında aşağıdaki değişikliklerden hangisi mevcut istemciler için genellikle geriye uyumludur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sipariş cevabına yalnızca yeni bir totalMoney alanı eklendi, eski total alanı yerinde duruyor. Mobil uygulama sorunsuz çalışırken bir partner entegrasyonu hata vermeye başladı. En olası sebep ne?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Satır satır: bir yolu yeni servise çevirmek

Gateway taşımanın ortasında: katalog ve sipariş taşındı, gerisi monolitte.

Taşınanı yeni servise, gerisini monolite

StranglerRoutes.java
1@Bean
2RouteLocator strangler(RouteLocatorBuilder builder) {
3 return builder.routes()
4 // Moved: catalog lives in its own service now
şu an çalışan satır .route("catalog", r -> r.path("/api/catalog/**")
6 .uri("lb://catalog-service"))
7 // Moved: orders, the old unversioned path and v2 together
8 .route("orders", r -> r.path("/api/orders/**", "/api/v2/orders/**")
9 .uri("lb://order-service"))
10 // Not moved yet: everything else still reaches the monolith
11 .route("monolith", r -> r.order(Ordered.LOWEST_PRECEDENCE)
12 .path("/**")
13 .uri("http://legacy-shop:8080"))
14 .build();
15}

Debug

Adım 1/5

gateway GET /api/catalog/42 geldi. İlk eşleşen kural bu: katalog artık kendi servisinde.

Java 21UTF-8LF5:1

Sol/sağ ok tuşlarıyla da gezebilirsin.

Çeviri katmanı ve katı olmayan bir istemci kodda şöyle görünür:

Derinleş · Sipariş: yeni model, eski şekil, hoşgörülü okuyucu 3 dosya · ~46 satır · ilk okumada atlayabilirsin
Proje dosyaları

order-service/src/main/java/shop/order/api/ OrderV2Controller.java Yeni model: tutar para birimiyle birlikte. v2 bunu olduğu gibi döndürür.

order-service/src/main/java/shop/order/api/OrderV2Controller.java
public record Money(BigDecimal amount, String currency) {}
public record OrderV2(String id, Money total, String status) {}
@RestController
@RequestMapping("/api/v2/orders")
class OrderV2Controller {
private final OrderQueries orders;
OrderV2Controller(OrderQueries orders) {
this.orders = orders;
}
@GetMapping("/{id}")
OrderV2 get(@PathVariable String id) {
return orders.find(id);
}
}

order-service/src/main/java/shop/order/api/ OrderV1Controller.java Anti-corruption layer: yeni modeli eski istemcilerin beklediği düz şekle çevirir ve eski sürümün kapanacağını başlıkla duyurur.

order-service/src/main/java/shop/order/api/OrderV1Controller.java
// The shape old clients were built against: total is a plain number in TRY.
public record OrderV1(String id, BigDecimal total, String status) {}
// Anti-corruption layer: the new model never learns about the old shape.
@RestController
@RequestMapping("/api/orders") // the path old clients already call
class OrderV1Controller {
private final OrderQueries orders;
OrderV1Controller(OrderQueries orders) {
this.orders = orders;
}
@GetMapping("/{id}")
ResponseEntity<OrderV1> get(@PathVariable String id) {
OrderV2 order = orders.find(id);
OrderV1 old = new OrderV1(order.id(), order.total().amount(), order.status());
return ResponseEntity.ok()
.header("Deprecation", "true")
.header("Sunset", "Wed, 31 Mar 2027 23:59:59 GMT") // announced, not a surprise
.body(old);
}
}

partner-client/src/main/java/partner/ OrderView.java Partner tarafında tolerant reader: yalnızca ihtiyaç duyulan alanlar okunur, tanınmayan alanlar görmezden gelinir.

partner-client/src/main/java/partner/OrderView.java
// Read only what we need and ignore the rest: a field added tomorrow cannot break us.
@JsonIgnoreProperties(ignoreUnknown = true)
public record OrderView(String id, BigDecimal total) {}
Hızlı kontrolOrta

Gateway'de strangler fig kuralları yazıyorsun: /api/catalog/** yeni servise, geri kalan her şey /** ile monolite gidiyor. /** kuralı en yüksek öncelikle değerlendirilirse ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin gör

Dört yetenek, üç istemci. Her adımda kimin hâlâ çalıştığına bak.

Monoliti parça parça taşı — kim kırılıyor?

Tohum 325168

Facade yönlendirme tablosu

  • /catalogmonolit
  • /cartmonolit
  • /ordersmonolit
  • /paymentmonolit

Web · her yayında güncellenir

  • Katalogmonolit
  • Sepetmonolit
  • Siparişmonolit
  • Ödememonolit

Mobil v1 · eski sürüm, güncellenemiyor

  • Katalogmonolit
  • Sepetmonolit
  • Siparişmonolit
  • Ödememonolit

Partner · bilinmeyen alanda hata verir

  • Katalogmonolit
  • Sepetmonolit
  • Siparişmonolit
  • Ödememonolit
Hız
Adım 0

Şu an ne oldu?

Her şey monolitte, üç istemci de mutlu

Web her yayında güncelleniyor. Mobil v1 eski bir sürüm ve güncellenemiyor. Partner bilinmeyen bir alan görünce hata veriyor.

Görevler0/3

  • Dört yeteneği de taşı, hiçbir istemci bir an bile kırılmasınaçık

    İpucu

    Önde bir facade, eski istemciye eski şekli veren bir sürüm ve tek bir yazan.

  • Eski mobil uygulama ayakta kalsın, katı okuyan partner kırılsınaçık

    İpucu

    Eski alanı silmeden yeni bir alan eklersen kim onu görmezden gelebilir?

  • Bir siparişin iki kopyasını birbirinden ayıraçık

    İpucu

    Aynı isteği iki veritabanına yazarsan ve ikincisi düşerse?

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

  1. Varsayılanla oynat. Katalog ve sepet sessizce taşınıyor; sipariş modeli yerinde değişince iki eski istemci kırılıyor.
  2. “Yeni alan ekle” seç. Mobil v1 kurtuldu, Partner hâlâ kırık: aradaki fark okuma biçimi.
  3. URI ya da başlık sürümü seç. Dört adımın hiçbirinde kırmızı kutu yok.
  4. Önde facade olmasın. Daha ilk adımda, katalogda bile, iki istemci 404 alıyor.
  5. Çift yazmayı seç. Sipariş taşınınca bir istemcinin ödemesi sarıya dönüyor: iki veritabanı ayrıştı.
Hızlı kontrolOrta

Simülatörde önde facade yokken katalog taşındığında Web çalışmaya devam etti ama Mobil v1 ve Partner kırıldı. Neden?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Tuzaklar

İki yere birden yazmak. Taşırken aynı siparişi hem monolite hem yeni servise yazmak cazip gelir. Bu bir dual writeAynı işlemde iki ayrı sisteme (ör. veritabanı ve mesaj kuyruğu) yazmak. Ortak bir transaction olmadığı için biri başarılı olup diğeri başarısız olabilir.Sözlükte gör →: ikisini kapsayan bir transaction yok, ikinci yazma düşerse iki farklı gerçek kalır.

Çözüm, her verinin tek bir yazanı olması. Yazma sahipliği bir anda yeni servise geçer; monolit kopyasını olaylardan (outbox) ya da veritabanı değişikliklerini okuyan CDC’den alır.

“Başlık yoksa en yeni sürüm.” Başlıksız istek en yeni sürüme düşerse, başlığı hiç bilmeyen eski istemciler her yeni sürümde kırılır. Başlıksız istek desteklenen en eski sürümü almalı.

Kendini sına

Şimşek turu1/5

Strangler fig, monoliti tek seferde kapatıp yenisini açmaktır.

Soru 1/4İleri

Taşıma sırasında sipariş, aynı istekte hem monolitin hem yeni sipariş servisinin veritabanına yazılıyor. Bu tasarımın asıl riski ve yaygın düzeltmesi hangisi?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Aklında kalacak üç şey

  1. 1 Strangler fig monoliti yetenek yetenek taşır. Önde duran facade her adımda tek bir yolu yeni servise çevirir; bir adımı geri almak o yolu eski yerine çevirmek kadar kolaydır.
  2. 2 Alan eklemek geriye uyumludur; silmek, adını ya da şeklini değiştirmek kırıcıdır. Eski istemcilere eski şekli bir sürüm ve bir çeviri katmanı verir.
  3. 3 Aynı veriyi iki yere birden yazmak, iki yazmadan biri düşünce iki farklı gerçek bırakır. Taşıma sırasında her verinin tek bir yazanı olur, öteki taraf kopyasını olaylardan alır.
Sonraki kapı Bir hesabın bakiyesini hiçbir yere yazmadan, yalnızca olup biteni kaydederek tutabilir misin? CQRS ve Event Sourcing — Olanı Yaz, Okumayı Ayır · 10 dk

4 kart sonraki derste seni bekliyor

0/4 kart bu dersten toplandı