Yıllardır çalışan bir legacy sistemi tek seferde yeniden yazma kararı, ilk bakışta en temiz seçenek gibi görünür. Martin Fowler bu tür projelerin neden zorlandığını üç noktada toplar: mevcut sistemin davranışını ayrıntısıyla çıkarmak beklenenden zordur, kullanıcılar yeni sistem bitene kadar yeni özellik bekleyemez ve eski davranışın büyük bir kısmına ihtiyaç yoktur. Yeniden yazım uzadıkça iş ihtiyaçları da değişmeye devam eder ve yeni sistemin yetişmesi gereken hedef kayar.

Strangler Fig Pattern bu riski parçalara böler. Adını, Fowler'ın 2001'de Queensland yağmur ormanlarında gördüğü strangler fig bitkisinden alır: bir ağacın kovuğunda filizlenen bitki zamanla konak ağaçtan beslenir, sonunda kendi başına ayakta kalabilir; konak ağaç ise ölebilir. Yazılımdaki karşılığı, yeni işlevleri legacy kod tabanının üzerine ama ondan ayrı kurmak ve davranışı eski sistemden yeniye adım adım taşımaktır. Eski sistem, taşınacak işlevi kalmadığında kapatılır. Uygulamadaki asıl iş, her dilimde trafiği, veriyi ve geri dönüş yolunu birlikte yönetmektir.

Strangler Fig Pattern nasıl çalışır?

Pattern'in merkezinde, istemci ile arka uç sistemler arasına yerleştirilen bir facade (proxy) bulunur. Microsoft'un Azure Architecture Center'daki tarifine göre geçiş dört aşamada ilerler:

  1. Facade, istemci uygulama ile legacy sistem ve yeni sistem arasına yerleşir. Başlangıçta isteklerin çoğu legacy sisteme gider.
  2. Her iterasyonda yeni sisteme bir işlev daha eklenir ve facade ilgili istekleri yeni sisteme yönlendirir.
  3. Bütün işlevler taşınıp legacy sisteme bağımlılık kalmadığında legacy sistem kapatılır ve facade tüm istekleri yeni sisteme gönderir.
  4. Facade kaldırılır, istemci doğrudan yeni sistemle konuşur.

İstemciler bu süre boyunca aynı arayüzü kullanır ve geçişin farkına varmaz. Microsoft son adım için başka bir seçenek de verir: facade, eski istemcilerin kullanması için adapter olarak yerinde bırakılabilir; çekirdek sistem ise yeni istemciler için güncellenir.

Hangi durumda uygun, hangi durumda değil?

Microsoft ve AWS dokümanlarındaki koşulları aşağıdaki tablo bir araya getiriyor.

Uygun olduğu durumlar Uygun olmayabileceği durumlar
Büyük bir sistemi veya kritik bileşenleri tek seferde değiştirmek risk taşıyor Arka uç sisteme giden istekler yakalanamıyor
Legacy sistem geçiş boyunca uzun süre çalışabiliyor Kaynak koduna erişim yok ve veritabanı gibi değiştirilebilir başka bir entegrasyon noktası da yok
İş birimi, dönüşüm bitene kadar yeni özellik bekleyemiyor Sistem küçük ve bütünüyle değiştirmek basit
Kullanıcıların geçişten olabildiğince az etkilenmesi gerekiyor Eski sistemin hızla ve tümüyle kapatılması gerekiyor

AWS kaynak koduna erişimi şart sayar, çünkü taşınan servislere giden çağrıları yönlendirmek için monolitte küçük kod değişiklikleri yapmak ve monolitin içine bir anti-corruption layer eklemek gerekir; AWS'ye göre kod erişimi olmadan bu çağrılar yakalanamaz. Kodu değiştirilemeyen bileşenler için başka yollar da var. Microsoft, mantığı stored procedure'lerde duran veritabanı merkezli uygulamalarda veritabanının kendisinin yeni servislere mesaj gönderebileceğini, alternatif olarak transaction log'u okuyan Change Data Capture (CDC) ile değişikliklerin yakalanabileceğini belirtir.

Adım 1: hedefi ve seam'leri belirlemek

Thoughtworks'ten Ian Cartwright, Rob Horn ve James Lewis'in "Patterns of Legacy Displacement" çalışması, legacy bir sistemi devreden çıkarmayı dört faaliyete ayırır: ulaşılmak istenen sonucu anlamak, problemi nasıl daha küçük parçalara böleceğine karar vermek, bu parçaları başarıyla teslim etmek ve organizasyonu bu işi sürekli yapabilmesi için değiştirmek. İlk faaliyet atlanırsa, değişim maliyetini düşürmek, bir iş sürecini iyileştirmek veya eski bir sistemi emekliye ayırmak gibi hedeflerden hangisinin öncelikli olduğu belirsiz kalır. Thoughtworks ekibine göre çoğu modernizasyon bu hedeflerin birkaçını birlikte taşır ve hangisinin öncelikli olduğu yola çıkmadan belirlenmelidir. Öncelik belirlenmezse dilimlerin sırasını seçecek bir ölçüt de olmaz.

İkinci faaliyet seam'lere, yani sistemi yönetilebilir parçalara ayırmaya izin veren ekleme noktalarına dayanır. Fowler'a göre iyi tasarlanmış bir sistemde bu seam'ler zaten bulunur. Legacy sistemlerde ise çoğu zaman bulunmaz; sistemin nasıl parçalanacağını çıkarmak ve bu seam'leri sisteme eklemek ciddi emek ister. AWS'nin rehberi burada bir uyarı ve bir öneri sunar. Domain net değilken yapılan erken ayrıştırma pahalıya mal olabilir ve servis sınırları yanlış çizilebilir; sınırları belirlemek için Domain-Driven Design ve Event Storming önerilir. İyi aday olarak da kendi data access nesnesi olan, ilişkili iş mantığını bir arada tutan ve az bağımlılığı olan servisler öne çıkar.

İlk dilimi seçmeden önce dört soruya cevap aramak gerekir:

  • Bu işlevin istekleri tek bir URL yolu, mesaj tipi veya tablo üzerinden ayırt edilebiliyor mu?
  • İşlevin verisi başka domain'lerle hangi tablolarda paylaşılıyor?
  • İşlev taşındığında legacy sistemin hangi parçaları onu çağırmaya devam edecek?
  • Yeni sürüm hatalı çıkarsa trafiği geri çevirmek ne kadar sürer?

AWS ayrıca geçiş sırasında yeni özelliklerin monolite eklenmek yerine yeni servis olarak geliştirilmesini, monolitteki hataların ise düzeltilmeye devam edilmesini önerir. Böylece legacy sistemin geçiş sürerken büyümesi sınırlanır.

Adım 2: facade katmanını kurmak

Facade bir API gateway veya reverse proxy olarak kurulabilir. İlk sürüm bütün trafiği legacy sisteme geçirir; ilk dilim hazır olduğunda yalnızca o dilime ait yol yeni servise yönlendirilir. AWS'nin örneğinde bu ayrım API URL'ine göre yapılır. Aşağıdaki NGINX yapılandırması aynı fikri gösteriyor:

upstream legacy_app   { server legacy.internal:8080; }
upstream user_service { server users.internal:9000; }

server {
    listen 80;

    # Taşınan dilim: kullanıcı işlemleri yeni servise gider
    location /api/users/ {
        proxy_pass http://user_service;
    }
    location = /api/users {
        proxy_pass http://user_service;
    }

    # Henüz taşınmayan her şey legacy sistemde kalır
    location / {
        proxy_pass http://legacy_app;
    }
}

NGINX, prefix ile tanımlanmış location'lar arasından en uzun eşleşeni seçer; bu örnekte regex location olmadığı için seçimi bu kural belirler. /api/users/ ile başlayan istekler böylece yeni servise, geri kalanlar legacy sisteme gider. Prefix eğik çizgiyle bittiği ve istek proxy_pass ile işlendiği için NGINX, sonunda eğik çizgi olmayan /api/users isteğine 301 koduyla /api/users/ adresine yönlendirme döner. Bu yönlendirme olmasın diye örnekte, NGINX dokümanının önerdiği gibi ayrıca tam eşleşen bir location (location =) tanımlandı; istek böylece doğrudan yeni servise gider. Yeni bir dilim taşındığında yapılandırmaya o dilimin location blokları eklenir ve yapılandırma yeniden yüklenir; trafiği geri çevirmek için aynı bloklar kaldırılıp yapılandırma yeniden yüklenir. Veri tarafında geri dönüş ayrı bir konu; Adım 4'te ele alınıyor.

Facade'ın kendi riskleri var. Microsoft da AWS de facade'ın tek hata noktasına (single point of failure) ya da performans darboğazına dönüşmemesi gerektiği konusunda uyarır. Microsoft'un listesinde bir madde daha var: facade geçişe ayak uydurmalıdır. Pratikte bu, taşınan her dilim için yönlendirme kuralının da güncellenmesi demektir.

Araç seçimi de zamanla değişebilir. AWS'nin bu iş için sunduğu Migration Hub Refactor Spaces, 7 Kasım 2025'ten bu yana yeni müşterilere açık değil; AWS benzer işler için AWS Transform'a yönlendiriyor. Yönlendirme kurallarını ekibin zaten işlettiği bir gateway veya proxy'de tutmak, geçişin ortasında araç değiştirmek zorunda kalma riskini azaltır.

Facade'ın ulaşamadığı çağrılar

Her istek dışarıdan HTTP ile gelmez. Monolitin kendi içindeki çağrılar ile mesaj kuyruğu veya veritabanı üzerinden yapılan entegrasyonlar facade'dan geçmez. Bu durumlar için iki teknik öne çıkar:

  • Branch by Abstraction: Fowler'ın 2014 tarihli yazısında anlattığı teknikte önce mevcut modülün önüne bir abstraction katmanı konur ve çağıranlar bu katmana taşınır. Yeni implementasyon aynı abstraction'ın arkasında geliştirilir, çağıranlar parça parça yeni implementasyona geçirilir ve en sonunda eski kod silinir. Sistem bu süre boyunca düzenli olarak release edilebilir.
  • Event Interception: Thoughtworks ekibinin tarifinde sistem durumunu değiştiren güncellemeler yakalanır ve bir kısmı yeni bileşene yönlendirilir. Yakalama noktası mesajlaşma altyapısı (Wire Tap, Message Router, Content-Based Router), API gateway, web uygulamasının şablonuna eklenen bir script veya veritabanında pre-commit trigger'lar olabilir. Ekip legacy bileşenlere olabildiğince az dokunmak ister; post-commit trigger'ları ve CDC'yi de seçenek olarak ele alır.

Adım 3: Anti-Corruption Layer ile iki modeli ayırmak

Geçiş süresince iki sistem birbirini çağırmaya devam eder. Yeni sistem henüz taşınmamış bir işlev için legacy sisteme gider, legacy taraftaki bir bileşen de taşınmış bir işlevi yeni sistemde arar. Microsoft bu çağrılar için Anti-Corruption Layer (ACL) önerir. Eric Evans'ın Domain-Driven Design kitabında tarif ettiği bu katman iki alt sistem arasındaki istekleri çevirir; yeni sistemin modeli de böylece legacy şemaların ve eski API'lerin semantiğine uymak zorunda kalmaz.

AWS'nin örneğinde kullanıcı servisi mikroservise taşındıktan sonra monolitteki sepet servisi onu halen eski yerinden çağırır. ACL, monolitin içinde UserServiceFacade veya UserServiceAdapter gibi servise özgü sınıf olarak kurulur ve çağrıyı yeni arayüze çevirip mikroservise iletir. Sepet servisi geçişten habersiz kalır. Sepet servisi de taşındığında doğrudan yeni servisi çağırır ve o yoldaki ACL'ye ihtiyaç kalmaz.

Microsoft'un ACL sayfası tasarımda dikkat edilecek şu noktalara değinir:

  • ACL iki sistem arasındaki çağrılara gecikme ekler ve bakımı yapılacak ek bir bileşendir; nasıl ölçekleneceği baştan planlanmalıdır.
  • Katman çeviri mantığına odaklanmalıdır; iş kuralları ve orkestrasyon ACL'ye konmamalıdır.
  • Güven seviyeleri farklı sistemlerin arasında durduğu için girdi doğrulama ve sanitization bu sınırda yapılabilir.
  • Çeviri hatalarını teşhis etmek için correlation ID ve structured logging planlanmalıdır.
  • Transaction ve veri tutarlılığı izlenmelidir.

Thoughtworks ekibinin Legacy Mimic pattern'i aynı soruna geçiş süresi boyunca bakar. Service providing mimic, yeni implementasyonu legacy arayüzünün arkasına saklar ve eski bileşenler değişikliği fark etmez. Service consuming mimic ise henüz değiştirilmemiş legacy sistemlerle onların mevcut arayüzü üzerinden konuşur. Ekip, örnekteki iki mimic'in hedef mimaride kalmayacağını, geçici olduğunu belirtir.

Taşınan servisler arasındaki senkron çağrılar da yeni hata yolları açar. AWS, timeout'ların bağlantı veya thread pool tüketimine yol açabileceği durumlarda, uzun süre başarısız olması beklenen çağrılar için circuit breaker kullanılmasını önerir. Circuit breaker, retry ve fallback gibi dayanıklılık pattern'lerinin genel çerçevesi Enterprise Yazılım Mimarisi yazısında anlatıldı.

Adım 4: paylaşılan veritabanından domain veritabanına

Legacy sistemlerin çoğu, birden fazla domain'e hizmet eden merkezi bir veritabanına dayanır. Trafik yönlendirmesini geri almak çoğu durumda bir yönlendirme kuralını değiştirmek demektir; veri sahipliğini taşıdıktan sonra geri dönmek ise çok daha pahalıdır. Microsoft'un veritabanı örneği bu geçişi üç aşamaya ayırır:

  1. Yeni servis, eski veritabanıyla: Yeni servis kendi domain'inin isteklerini karşılamaya başlar ama monolitik veritabanındaki tabloları okuyup yazmaya devam eder. Diğer domain'ler legacy sistemde kalır.
  2. Ayrı domain veritabanı ve senkronizasyon: İlgili tablolar ve geçmiş veri ETL ile yeni domain veritabanına kopyalanır, ardından CDC monolitik veritabanındaki değişiklikleri yeni veritabanına aktarır. CDC'nin, ETL'in kopyaladığı veriden sonraki değişiklikleri kaçırmadan devam etmesi gerekir; Debezium gibi CDC araçları bunun için önce bir başlangıç snapshot'ı alır, ardından değişiklik log'unu snapshot'ın alındığı konumdan okumaya devam eder. Cutover'dan önce iki veritabanı arasındaki tutarlılık doğrulanır.
  3. Cutover: Doğrulamanın ardından yeni domain veritabanı o domain'in system of record'u olur. İlgili tablolar, stored procedure'ler ve bağımlılıklar monolitten kaldırılır.

Geri dönüş penceresi bu sırayla daralır. Aynı örneğe göre ikinci aşamada ve üçüncü aşamanın başında domain tabloları ve senkronizasyon hatları monolitte halen durduğu için monolitik veritabanına dönülebilir. Ancak cutover'dan sonra yazmalar yeni domain veritabanına gider; monolitteki tablolar bu değişiklikleri almıyorsa güncelliğini yitirir. Bu yüzden geri dönüşün veri kaybı olmadan yapılabilmesi, cutover sonrasındaki değişikliklerin monolitik veritabanına da aktarılmasına ya da geri dönüş sırasında uzlaştırılmasına bağlıdır. Legacy nesneler silindikten sonra dönmek ise bu nesneleri geri yüklemeyi ve veri değişikliklerini yeniden oynatmayı gerektirir; bu da emeği ve riski büyütür. Legacy nesnelerin silinmesi bu nedenle, her domain için yeni sistem doğrulandıktan sonra bilinçli olarak atılan son adım olarak planlanmalıdır.

Ters yönde de ihtiyaç doğabilir: raporlama veya henüz taşınmamış bileşenler, yeni servisin verisini monolitik veritabanında görmek isteyebilir. AWS bu durum için yeni servisin her güncellemede bir kuyruğa event gönderdiği, bir senkronizasyon agent'ının da bu kuyruğu dinleyip monolitik veritabanını güncellediği bir yapı tarif eder. Bu yapı iki veri deposu arasında veri tekrarı ve eventual consistency doğurabilir; AWS onu, uzun vadeli bir çözüm kurulana kadar kullanılacak taktik bir ara adım olarak görür.

Uygulama kodu iki ayrı hedefe yazıyorsa senkronizasyonun bilinen bir tuzağı vardır. Bir servis önce kendi veritabanına, sonra kuyruğa ayrı ayrı yazarsa (dual-write), ilk yazma başarılı olup ikincisi başarısız olduğunda ya da servis iki yazmanın arasında çöktüğünde veri ayrışır. Transactional Outbox, CDC ve idempotent consumer tasarımı Event-Driven mimarilerde veri tutarlılığı yazısında ayrıntılı olarak ele alındı. Geçiş boyunca hangi verinin hangi sistemde esas kabul edildiğini netleştirmek için Single Source of Truth yazısındaki çerçeve de kullanılabilir.

Thoughtworks ekibinin Event Interception örneği veri sahipliğini benzer bir sırayla devreder. Önce yeni servis CDC ile beslenerek dark launch edilir. Sonra okuma istekleri yeni servise yönlendirilir, yazmalar legacy sistemde kalır. Ardından yazma istekleri yakalanır ve yeni servis system of record olur; iş mantığı en son taşınır.

Adım 5: dark launching, karşılaştırma ve canary release ile doğrulama

Bir dilimi gerçek trafiğe açmadan önce, korunması istenen davranışlarda yeni implementasyonun legacy sistemle aynı sonucu ürettiğini görmek gerekir. Üç teknik bu işte birbirini tamamlar.

Dark launching, Fowler'ın 2020 tarihli tanımıyla yeni bir back end işlevini production'da çağırıp sonucunu kullanıcıya göstermeden performans etkisini ölçmektir. Fowler aynı tekniğin yeniden yazılmış bir işlevi paralel çalıştırmaya da olanak verdiğini yazar: eski ve yeni kod birlikte çağrılır, sonuçları karşılaştırılır, arayüze yalnızca biri döner. Bu durumda yan etkilere dikkat etmek gerekir. E-posta gönderen, ödeme alan veya stok miktarını azaltan bir işlev iki yolda birden çalışırsa aynı işlem iki kez yapılabilir; bu yan etkiler yeni tarafta kapatılmalı ya da test ortamına yönlendirilmelidir.

Sonuçları karşılaştırmak için GitHub'ın açık kaynak Scientist kütüphanesi iyi bir referans. Ruby için yazılan kütüphanenin README'si PHP, .NET, Python, Java, Node.js, Go gibi diller için alternatif implementasyonları da listeler. Kütüphanede eski kod use (control), yeni kod try (candidate) bloğunda çalışır. Kullanıcıya her zaman control'ün sonucu döner. Kütüphane iki bloğun çalışma sırasını rastgele belirler, sürelerini ölçer, sonuçları karşılaştırır; candidate'te oluşan hataları da kaydeder ama kullanıcıya yansıtmaz. README sınırı da açıkça yazar: candidate'in her çağrıda çalışacağı garanti edilemediği için Scientist yalnızca veri değiştirmeyen metotları sarmak için güvenlidir. Yazma yapan kod için README'nin önerisi, yazmanın yapıldığı her noktada eski ve yeni sistemi birlikte güncellemek ve sonuçları okuma anında karşılaştırmaktır. Bu ikili yazmanın tutarlılığı da yukarıdaki dual-write riskine tabidir.

Canary release, Danilo Sato'nun 2014 tarihli tanımıyla yeni sürümü önce kullanıcıların küçük bir alt kümesine açıp sonra bütün altyapıya yaymaktır. Alt küme rastgele seçilebilir, önce şirket içi kullanıcılardan oluşabilir veya coğrafya gibi bir ölçüte göre belirlenebilir. Sorun çıkarsa kullanıcılar eski sürüme geri yönlendirilir. Sato'nun bir uyarısı Strangler Fig geçişleri için de geçerli: veritabanı şeması değişiyorsa geçiş süresince şemanın iki uygulama sürümünü birden desteklemesi parallel change ile sağlanabilir.

Adım 6: transitional architecture'ı sökmek

Facade, monolit içindeki ACL'ler, mimic'ler, senkronizasyon agent'ları ve CDC hatları çoğunlukla yalnızca geçiş için kurulur. Bazıları kalıcı olabilir: Microsoft, facade'ın eski istemciler için adapter olarak kalabileceğini belirtir; Thoughtworks ekibi de dış bir sisteme karşı kurulan ACL'yi hedef mimarinin parçası olarak bırakır ve yalnızca geçici olanlara transitional architecture der. Bu kavram, legacy sistemin yerinden edilmesini kolaylaştırmak için kurulan ve iş bittiğinde kaldırılması planlanan yazılım parçalarını kapsar. Fowler bu kodun ilk bakışta israf gibi görünebileceğini kabul eder; ona göre kademeli yaklaşımın düşürdüğü risk ve erken gelen değer bu maliyeti karşılar.

Thoughtworks ekibi, geçici bileşenleri kurarken onları sonradan kolay kaldırılabilir yapmak için biraz daha emek harcamanın değebileceğini söyler; kullanılmayan bileşenler bile yerinde kaldıkça sonraki ekiplerin bakım işini zorlaştırabilir. Bunun için her geçici bileşene bir sahip atanabilir ve yazılı bir kaldırma koşulu belirlenebilir. Örneğin: "Kullanıcı domain'inin bütün okuma ve yazmaları yeni veritabanında, monolitten bu tablolara erişen kod kalmadı."

Geçişin organizasyonel tarafı

Fowler ve Thoughtworks ekibi aynı noktada buluşur: organizasyon kültürü ve liderlik değişmezse yeni sistem de zamanla benzer bir karmaşaya sürüklenebilir. Patterns of Legacy Displacement'ın dördüncü faaliyeti olan organizasyonun modernizasyonu sürekli yürütebilmesini sağlamak, bu riske karşılık gelir. AWS de servis sınırlarını çizerken compliance gereksinimlerini, release temposunu, ekiplerin coğrafi konumunu, ölçeklenme ihtiyaçlarını ve ekiplerin bilişsel yükünü hesaba katmayı önerir.

Strangler Fig Pattern'in bedeli, iki sistemin bir süre yan yana çalışması ve bu süre boyunca işletilen geçici mimaridir. Bu süreyi kısa tutmak için Adım 1'deki sorular ilk dilimden önce cevaplanmalı, Adım 6'daki kaldırma koşulları da ilk geçici bileşenle birlikte yazılmalıdır.

Kaynaklar: Strangler Fig, Martin Fowler (https://martinfowler.com/bliki/StranglerFigApplication.html); Strangler Fig pattern, Microsoft (https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig); Anti-corruption Layer, Microsoft (https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer); Strangler fig pattern, AWS (https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/strangler-fig.html); Anti-corruption layer, AWS (https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html); Patterns of Legacy Displacement (https://martinfowler.com/articles/patterns-legacy-displacement/); Event Interception (https://martinfowler.com/articles/patterns-legacy-displacement/event-interception.html); Legacy Mimic (https://martinfowler.com/articles/patterns-legacy-displacement/legacy-mimic.html); Transitional Architecture (https://martinfowler.com/articles/patterns-legacy-displacement/transitional-architecture.html); Branch by Abstraction (https://martinfowler.com/bliki/BranchByAbstraction.html); Dark Launching (https://martinfowler.com/bliki/DarkLaunching.html); Canary Release (https://martinfowler.com/bliki/CanaryRelease.html); GitHub Scientist (https://github.com/github/scientist); AWS Migration Hub Refactor Spaces (https://docs.aws.amazon.com/migrationhub-refactor-spaces/latest/userguide/what-is-mhub-refactor-spaces.html); Debezium PostgreSQL connector (https://debezium.io/documentation/reference/stable/connectors/postgresql.html); NGINX location (https://nginx.org/en/docs/http/ngx_http_core_module.html#location)