sazak@labs:~
oturum

etiketler

microservices.NETWCFStrangler Fig PatternLegacy ModernizationDatabase Migration

Software · Feb 14, 2026 · 11 dk okuma

Legacy .NET Sistemlerini Strangler Fig Pattern ile Güvenli Modernize Etmek

Büyük .NET Framework ve WCF sistemlerini yeniden yazmadan modernize etmek mümkündür. Strangler Fig Pattern, Anti-Corruption Layer, incremental migration ve rollback stratejileriyle riskin nasıl yönetileceğini öğrenin.

Legacy .NET Sistemlerini Strangler Fig Pattern ile Güvenli Modernize Etmek

Yıllardır üretimde çalışan büyük bir .NET Framework sistemini modernize etmenin en güvenli yolu, mevcut uygulamayı tek seferde yeniden yazmak değil, sorumluluklarını kontrollü biçimde yeni sisteme taşımaktır. Strangler Fig Pattern bu yaklaşımın mimari temelini oluşturur: Eski ve yeni uygulamalar bir süre birlikte çalışır, belirli işlemler kademeli olarak yeni bileşenlere yönlendirilir ve eski kod ancak yerine geçen davranış doğrulandıktan sonra devreden çıkarılır. Ancak bu pattern tek başına risksiz dönüşüm sağlamaz. Gerçek güvenlik, veri sahipliğinin, entegrasyon sözleşmelerinin ve geri dönüş mekanizmalarının nasıl tasarlandığına bağlıdır.

Bu ayrım özellikle .NET Framework, WCF, SOAP ve ortak SQL Server database üzerine kurulmuş kurumsal uygulamalarda önemlidir. Böyle sistemlerde yıllar içinde oluşan business rule'lar yalnızca C# sınıflarında bulunmaz; stored procedure'lerde, WCF behavior ayarlarında, batch job'larda ve dış sistemlerin beklediği SOAP mesajlarında da yaşar. Modernizasyon sırasında bu davranışlardan birini değiştirmek, uygulama başarıyla deploy edilmiş olsa bile fatura üretimini, tahsilatı veya başka bir kritik süreci bozabilir. Dolayısıyla hedef yalnızca modern .NET üzerinde çalışan kod üretmek değil, mevcut sistemin verdiği sözleri bozmadan mimari bağımlılıkları azaltmaktır.

Önce kodu değil, sistemin gerçek sınırlarını inceleyin

Legacy system assessment, eski uygulamanın hangi teknolojiyle yazıldığını listelemekten ibaret değildir. Asıl amaç, hangi bileşenin hangi iş davranışından sorumlu olduğunu, hangi veriyi değiştirdiğini ve hangi dış sistemlerin bu davranışa bağımlı olduğunu ortaya çıkarmaktır. Repository yapısı başlangıç noktasıdır; üretimdeki gerçek bağımlılıkları tek başına göstermez.

Tipik bir WCF uygulamasında aynı business operation farklı endpoint'ler üzerinden çağrılabilir. Bir SOAP endpoint'i dış müşterilere hizmet verirken başka bir endpoint aynı operasyonu şirket içindeki batch süreçlerine sunabilir. Uygulama ayrıca SQL Server stored procedure çağırabilir, transaction açabilir ve işlem tamamlandıktan sonra bir message queue'ya mesaj gönderebilir. Yalnızca service interface'e bakarak bu davranışın tamamı görülemez.

Assessment sırasında source code, configuration dosyaları, WSDL tanımları, database schema, SQL job'ları, deployment pipeline ve production log'ları birlikte incelenmelidir. Özellikle çağrı hacmi, hata oranı, işlem süresi, transaction sınırları, authentication yöntemi ve dış bağımlılıklar belirlenmelidir. Bunlar olmadan hangi modülün önce taşınacağına karar vermek tahmine dönüşür.

Technical debt de bu aşamada doğru sınıflandırılmalıdır. Eski .NET Framework kullanımı tek başına bütün teknik borcu açıklamaz. Asıl maliyet bazen birbirine bağımlı namespace'lerde, test edilemeyen static sınıflarda, uygulama genelinde paylaşılan mutable state'te veya business logic içeren stored procedure'lerde bulunur.

Burada yararlı bir ayrım vardır: Teknoloji borcu ile mimari borç aynı şey değildir. .NET Framework üzerinde çalışan fakat sınırları belirgin bir modül, modern .NET üzerinde çalışan ancak her service'in aynı database tablolarını değiştirdiği bir sistemden daha kolay yönetilebilir olabilir. Runtime yükseltmesi mimari bağımlılıkları kendiliğinden çözmez.

İlk migration adayını seçerken yalnızca kod büyüklüğüne bakmam. İş değeri, bağımlılık sayısı, veri yazma sorumluluğu, test edilebilirlik ve rollback maliyeti birlikte değerlendirilmelidir. Genellikle sınırları anlaşılmış, dış sözleşmesi belirgin ve transaction kapsamı dar bir işlem iyi bir başlangıçtır. En kritik ödeme akışını ilk deneme olarak seçmek çoğu sistemde gereksiz risk üretir.

Strangler Fig Pattern bir proxy yerleştirmekten fazlasıdır

Strangler Fig Pattern çoğu zaman basit bir mimari çizimle anlatılır: İstemci bir gateway'e gelir, gateway bazı request'leri legacy uygulamaya, bazılarını yeni service'e yönlendirir. Bu çizim routing mekanizmasını açıklar fakat dönüşümün en zor kısmını eksik bırakır. Asıl mesele, aynı iş davranışının iki uygulama arasında nasıl bölüneceği ve veri üzerindeki otoritenin ne zaman değişeceğidir.

Pattern'in temel mekanizması kontrollü sorumluluk devridir. Önce mevcut davranışın dışarıdan gözlemlenebilir sözleşmesi belirlenir. Ardından bu davranışı gerçekleştiren yeni bileşen geliştirilir. Trafiğin sınırlı bir bölümü yeni bileşene aktarılır, sonuçlar doğrulanır ve kapsam genişletilir. Son aşamada eski implementation kaldırılır.

Bu yaklaşımın uygulanabilmesi için bir interception point gerekir. HTTP tabanlı sistemlerde reverse proxy veya API gateway bu görevi üstlenebilir. Fakat WCF kullanan her sistem HTTP request routing ile yönetilemez. NetTcpBinding, MSMQ binding veya özel transport kullanan endpoint'lerde interception, transport seviyesinde değil çağıran uygulamanın adapter katmanında yapılmak zorunda kalabilir.

Bu nedenle WCF modernizasyonunu yalnızca endpoint adresini değiştirme işi olarak görmek hatalıdır. WCF, SOAP mesajlaşmasının yanında binding, security, session, transaction ve serialization davranışları da tanımlar. Yeni ASP.NET Core REST API aynı business operation'ı gerçekleştirse bile eski istemcinin beklediği davranışı otomatik olarak sunmaz.

Burada Anti-Corruption Layer devreye girer. Anti-Corruption Layer, legacy sistemin veri modellerini ve kavramlarını yeni domain model'e doğrudan taşımak yerine arada açık bir çeviri sınırı oluşturur. Örneğin eski SOAP sözleşmesindeki CustomerCode, DocumentType ve Amount alanları yeni sistemdeki nesnelere dönüştürülürken doğrulama ve anlam eşleştirmesi bu katmanda yapılır.

Bu katman basit bir DTO mapper olmak zorunda değildir. Legacy hata kodlarını yeni hata modeline çevirebilir, eksik alanların varsayılan değerlerini koruyabilir ve eski istemcilerin beklediği durum kodlarını üretebilir. Fakat business rule'ların iki tarafta farklılaşmasına izin veren ikinci bir domain implementation'a dönüşmemelidir.

Modern .NET'e geçişte REST ve gRPC seçenekleri de aynı bağlamda değerlendirilmelidir. REST, HTTP üzerinden erişen çok çeşitli istemciler ve dış entegrasyonlar için uygun olabilir. gRPC ise protobuf sözleşmeleri ve streaming gibi özellikleri nedeniyle kontrol edilen service-to-service iletişiminde avantaj sağlayabilir. Ancak mevcut SOAP tüketicileri gRPC konuşamaz; onların sözleşmesini koruyan bir adapter veya geçiş dönemi boyunca çalışan eski endpoint gerekir.

Backward compatibility burada yalnızca aynı operation adını korumak değildir. XML namespace, element sırası, optional field davranışı, SOAP fault yapısı ve authentication beklentileri de sözleşmenin parçası olabilir. WSDL değişmeden kalsa bile serialization davranışındaki küçük bir fark istemcinin mesajı reddetmesine neden olabilir.

Bir fatura servisini taşırken gerçek karar veri sahipliğidir

Somut bir örnek düşünelim. .NET Framework üzerinde çalışan bir WCF fatura servisi, CreateInvoice SOAP operation'ı üzerinden istek alıyor. İstek müşteri kodunu, fatura kalemlerini, para birimini ve işlem referansını içeriyor. Service, SQL Server üzerindeki Invoices ve InvoiceLines tablolarına kayıt yazıyor, toplam tutarı hesaplıyor ve cevap olarak fatura numarası döndürüyor.

Aynı operasyonun bir muhasebe uygulaması ve eski bir masaüstü istemci tarafından çağrıldığını varsayalım. Ayrıca gece çalışan bir SQL job, oluşturulan faturaları başka bir sisteme aktarıyor. Böyle bir durumda yalnızca yeni bir REST endpoint yazmak migration değildir; iki istemcinin sözleşmesini ve batch sürecinin veri beklentisini birlikte yönetmek gerekir.

İlk adım olarak mevcut WSDL, örnek SOAP request ve response mesajları, hata senaryoları ve database değişiklikleri kayıt altına alınır. CreateInvoice çağrısının hangi stored procedure'leri çalıştırdığı, invoice number üretiminin nerede yapıldığı ve aynı transaction içinde hangi tabloların güncellendiği belirlenir. Özellikle tekrar gönderilen request'lerin nasıl ele alındığı araştırılır.

Yeni tarafta ASP.NET Core üzerinde bir InvoiceService oluşturulur. Service'in kendi domain modeli ve application layer'ı bulunur. Dışarıya REST API sunulabilir; fakat eski SOAP istemcileri için mevcut WCF endpoint'i korunur. Bu endpoint, migration kapsamına alınan işlemleri bir adapter üzerinden yeni service'e yönlendirebilir.

Buradaki kritik tasarım kararı database migration stratejisidir. İlk aşamada yeni service'in mevcut SQL Server database'ini kullanması pratik görünebilir. Bu yaklaşım geçiş maliyetini azaltabilir, ancak eski ve yeni kodun aynı tablolara bağımsız biçimde yazması veri tutarlılığını tehlikeye atar.

Örneğin iki implementation farklı invoice number üretme kuralları kullanıyorsa unique constraint hataları oluşabilir. Daha kötüsü, iki taraf da teknik olarak geçerli fakat iş açısından farklı kayıtlar oluşturabilir. Bu nedenle belirli bir iş operasyonu için tek bir write owner tanımlanmalıdır.

Write owner, ilgili veriyi değiştirme yetkisine sahip tek mantıksal bileşendir. Migration sırasında CreateInvoice yazma yetkisi önce legacy service'te kalabilir. Yeni service bu davranışı çağırarak başlayabilir ve domain sınırı netleştikçe yazma sorumluluğu kontrollü biçimde devredilebilir. Alternatif olarak yeni service doğrudan yazma yetkisini alırken legacy endpoint yalnızca yönlendirme yapabilir.

Database migration için tek bir doğru yöntem yoktur. Aynı database üzerinde ayrı schema kullanmak geçici bir çözüm olabilir. Yeni service'in kendi database'ine geçmesi ise daha güçlü bir sahiplik sınırı sağlar, fakat data synchronization ve transaction yönetimi gerektirir.

Özellikle eski ve yeni database'lere aynı işlem sırasında yazmak tehlikelidir. Dual write sırasında ilk yazma başarılı, ikinci yazma başarısız olabilir. Uygulama düzeyinde iki bağımsız commit işlemi atomik değildir. Bu nedenle event publication gereken durumlarda transactional outbox gibi mekanizmalar değerlendirilebilir: Business data ve outbox kaydı aynı database transaction içinde yazılır, mesaj daha sonra güvenilir biçimde yayımlanır.

Fatura oluşturma gibi işlemlerde idempotency de gereklidir. Idempotency, aynı mantıksal isteğin tekrar gönderilmesinin ikinci bir iş sonucu üretmemesidir. İstemcinin sağladığı benzersiz işlem referansı database seviyesinde unique constraint ile korunabilir. Böylece network timeout sonrasında yapılan retry yanlışlıkla ikinci fatura oluşturmaz.

Bu örnekte başarılı migration ölçütü yeni endpoint'in HTTP 200 dönmesi değildir. Aynı input için hesaplanan tutarların, vergi kurallarının, fatura numarası davranışının ve downstream kayıtlarının beklenen sözleşmeyi karşılamasıdır. Teknik olarak başarılı bir response, business outcome'ın doğru olduğunu kanıtlamaz.

Incremental migration ancak ölçülebilir ve geri alınabilir olduğunda güvenlidir

Modularization, büyük bir uygulamayı rastgele küçük service'lere bölmek anlamına gelmez. Önce birlikte değişen business capability'ler belirlenmelidir. Fatura oluşturma, fatura sorgulama ve tahsilat işlemleri aynı tabloları kullanıyor olabilir; buna rağmen farklı transaction ve ownership sınırlarına sahip olabilirler.

Bazı durumlarda ilk hedef microservices değil, modular monolith olmalıdır. Modern .NET üzerinde modülleri açık interface'lerle ayıran tek deployment unit, dağıtık transaction ve network failure maliyetlerini erken aşamada artırmadan bağımlılıkları temizleyebilir. Service ayrımı ancak bağımsız deployment veya scaling ihtiyacı belirginse anlam kazanır.

Incremental migration için feature flags yararlıdır. Örneğin InvoiceCreationV2 flag'i belirli müşteri gruplarında yeni implementation'ı etkinleştirebilir. Ancak flag yalnızca request routing kontrolü değildir; hangi transaction'ın hangi sistem tarafından tamamlandığı da izlenmelidir. İşlem başladıktan sonra flag değişmesi aynı işlemin iki farklı implementation arasında bölünmesine yol açmamalıdır.

Feature flag kararını request başında sabitlemek bu riski azaltır. Daha uzun süren workflow'larda seçilen implementation bilgisi işlem kaydına yazılabilir. Böylece retry ve asynchronous continuation aynı yolu izler.

Parallel run, eski ve yeni implementation'ın aynı input üzerinde karşılaştırılmasını sağlar. Read-only sorgular için iki sistemden gelen response'lar karşılaştırılabilir. Ancak fatura oluşturma gibi side effect içeren işlemlerde her iki sistemi canlı database üzerinde çalıştırmak çift kayıt üretir.

Bu nedenle write operation'lar için shadow execution dikkatle tasarlanmalıdır. Yeni implementation izole test database üzerinde çalıştırılabilir veya yalnızca hesaplama aşamasının sonucu karşılaştırılabilir. Karşılaştırılan değerler de normalize edilmelidir; timestamp veya sistem tarafından üretilen identifier gibi doğal olarak farklı alanlar doğrudan eşitlik kriteri olmamalıdır.

Observability geçişin temel kontrol mekanizmasıdır. Legacy ve modern service çağrıları ortak bir correlation ID üzerinden ilişkilendirilmeli; request sayısı, latency, error rate ve business-level sonuçlar izlenmelidir. OpenTelemetry modern .NET tarafında tracing ve metrics üretmek için kullanılabilir. Legacy uygulamada aynı düzeyde instrumentation mümkün değilse adapter üzerinden en azından giriş, çıkış ve hata noktaları görünür hale getirilmelidir.

Ölçüm yalnızca teknik metriklerle sınırlı kalmamalıdır. Fatura servisinde oluşturulan belge sayısı, başarısız kayıtlar, tekrar işlenen request'ler ve tutar uyuşmazlıkları da takip edilmelidir. Bir endpoint'in latency değeri iyileşirken yanlış fatura üretme oranı artıyorsa migration başarılı değildir.

Rollback strategy deployment öncesinde hazırlanmalıdır. Stateless bir read endpoint'ini eski servise geri yönlendirmek genellikle kolaydır. Fakat yeni service farklı schema'ya veri yazmaya başladıysa routing değişikliği tek başına rollback sağlamaz. Eski uygulama yeni veriyi okuyamayabilir.

Bu nedenle database değişikliklerinde expand-and-contract yaklaşımı kullanılabilir. Önce eski ve yeni kodun birlikte çalışabileceği additive schema değişiklikleri yapılır. Yeni alanlar devreye alınır, veri taşınır ve eski alanlar ancak bütün bağımlılıklar kaldırıldıktan sonra silinir. Destructive migration işlemleri geri dönüş penceresi kapanmadan yapılmamalıdır.

Rollback için ayrıca bir data reconciliation planı gerekir. Yeni sistemin oluşturduğu kayıtların eski sisteme nasıl aktarılacağı veya hangi noktadan sonra rollback yerine forward fix uygulanacağı önceden belirlenmelidir. Deployment'ı geri almak ile business state'i geri almak aynı işlem değildir.

Güvenli dönüşümün ölçütü yeni runtime değil, kontrol edilen değişimdir

Legacy .NET modernizasyonunda önerim, ilk aşamada büyük bir rewrite projesi başlatmak yerine açık bir capability seçip onun etrafında Strangler Fig boundary oluşturmaktır. Bu boundary'nin bir tarafında mevcut WCF/SOAP sözleşmesi, diğer tarafında modern .NET implementation bulunmalıdır. Aradaki Anti-Corruption Layer, eski istemcilerin beklentilerini korurken yeni domain model'in legacy ayrıntılarla kirlenmesini engellemelidir.

İlk production migration mümkünse düşük riskli, açık input-output sözleşmesi olan bir işlemden başlamalıdır. Başarı kriterleri deploy öncesinde tanımlanmalıdır: Hangi response alanlarının eşleşeceği, hangi hata türlerinin kabul edileceği, hangi latency değişiminin tolere edileceği ve rollback'in hangi koşullarda tetikleneceği belli olmalıdır. Her migration diliminden sonra eski kodun gerçekten kaldırılması da hedeflenmelidir; aksi halde iki sistemin bakım maliyeti kalıcı hale gelir.

Database ownership netleşmeden service sayısını artırmayı doğru bulmuyorum. Aynı tabloları değiştiren bağımsız service'ler, görünüşte modern fakat operasyonel olarak daha kırılgan bir mimari oluşturur. Önce transaction sınırlarını ve veri sahipliğini belirlemek, ardından deployment sınırlarını ayırmak daha savunulabilir bir sıradır.

Bu yaklaşım her durumda en iyi seçenek değildir. Uygulamanın davranışı bilinmiyorsa, production gözlemlenebilirliği yoksa veya eski sistemin çalıştırılması güvenlik ve uyumluluk açısından kabul edilemez durumdaysa uzun süreli coexistence mümkün olmayabilir. Böyle bir durumda önce minimum gözlemlenebilirlik sağlamak, desteklenmeyen bileşeni izole etmek veya kontrollü bir replacement projesi yürütmek gerekebilir.

Benzer şekilde küçük, bağımsız ve iyi test edilmiş bir uygulamada doğrudan .NET upgrade yapmak, kapsamlı bir Strangler Fig altyapısı kurmaktan daha ekonomik olabilir. Pattern'in değeri, eski sistemin uzun süre yaşayacak olmasından değil, migration riskini küçük ve doğrulanabilir kararlara bölebilmesinden gelir.

Modernizasyon tamamlandığında hedef yalnızca WCF endpoint'lerinin kapanması ya da .NET Framework bağımlılığının kaldırılması olmamalıdır. Yeni sistemin hangi veriden sorumlu olduğu açıkça bilinmeli, dış sözleşmeler testlerle korunmalı, production davranışı ölçülebilmeli ve sonraki değişiklikler eski sisteme göre daha düşük maliyetle yapılabilmelidir. Eski uygulamayı kapatabilmek teknik bir kilometre taşıdır; asıl kazanım, onu kapatırken hangi iş davranışının değiştiğini kesin olarak bilmektir.

yorumlar

0

Henüz yorum yok. İlk notu sen bırak.

Yorum yazmak için giriş yap.

Gmail ile saniyeler içinde, ya da e-posta ile kaydol.

bağlı 26109:32:59