Modüler Monolith .NET ile Nasıl Kurulur? Modül Sınırları ve Bağımlılık Yönetimi

Modüler Monolith .NET uygulamasında tek uygulama içindeki modül ve veri sahipliği sınırları

Modüler Monolith .NET uygulamasında proje sayısını artırmakla kurulmaz. Sipariş, stok ve faturalama aynı process içinde çalışabilir; esas karar, birbirlerinin hangi tiplerini ve verilerini kullanabilecekleridir. Bir modülün DbContext nesnesini diğerine açtığınızda assembly ayrımı bu bağımlılığı düzeltmez.

Domain boundary yazısında iş sınırlarını ele almıştık. Burada o sınırı kodda nasıl görünür hale getireceğimize bakalım. Örnek bir sipariş uygulamasında stok rezervasyonu, stok modülünün sunduğu bir işlem olsun. Sipariş modülü stok tablosunu güncellemesin ve stok entity’sini kendi modeline taşımasın.

Modüler Monolith .NET için iş sınırını bulmak

Bir satış uygulamasında ürün, sipariş, stok ve fatura tablolarını görmek, dört doğru modül bulduğumuz anlamına gelmez. Tablo isimleri verinin nasıl saklandığını anlatır. Modül sınırı ise bir kararın hangi kurallarla verildiğini, kim tarafından değiştirilebildiğini ve başka kararlarla ne kadar sık birlikte değiştiğini anlatmalıdır. Bu nedenle tasarıma solution ağacından başlamadan önce siparişin oluşturulmasından teslimata kadar geçen akışı yazmak daha kullanışlıdır. Sipariş hangi anda kesinleşiyor, hangi anda stok ayrılıyor, ödeme başarısız olduğunda hangi kayıt geri alınıyor? Bu soruların yanıtı sınıfların yerinden daha değerlidir.

Somut bir senaryo düşünelim. Müşteri üç ürün için sipariş gönderiyor. Sipariş modülü teslimat adresini ve satış koşullarını doğruluyor. Stok modülü ilgili depoda ayrılabilecek miktarı kontrol ediyor. Ödeme modülü tahsilatın durumunu yönetiyor. Faturalama ise hukuki belgenin oluşturulması ve numaralandırılmasıyla ilgileniyor. Her adımın ayrı verisi olabilir; ancak ayrı veri olması tek başına bağımsız modül gerekçesi değildir. Birbirinden farklı iş kurallarının, değişim sebeplerinin ve yetki alanlarının bulunması daha güçlü bir gerekçedir. Örneğin fatura numarasını değiştirme kararı stok rezervasyonunu etkilememelidir.

Sınırı bulmak için her iş kararının yanına sahibi olan modülü yazın. Ardından kararın ihtiyaç duyduğu bilgileri ve ürettiği sonuçları belirtin. Sipariş fiyatının belirlenmesi için güncel katalog bilgisi gerekebilir; buna rağmen geçmiş siparişin fiyatı katalogdaki sonraki fiyat değişikliğinden etkilenmemelidir. Sipariş, satın alma anındaki fiyatı kendi anlamıyla saklar. Katalog ise güncel satış teklifinin sahibidir. İki tarafta fiyat alanı bulunması gereksiz duplication sayılmayabilir. Aynı kelimenin farklı yaşam döngülerini tek ortak entity içinde toplamak daha tehlikeli bir bağımlılık oluşturabilir.

Bu çalışma sırasında modüller arasında sürekli karşılıklı çağrı çıkıyorsa sınırları yeniden değerlendirin. Sipariş stoktan bilgi alıyor, stok tekrar siparişten durum istiyor, sipariş aynı işlem içinde stok tablosunu da değiştiriyorsa sorumluluklar belirsizdir. Çözüm her zaman yeni bir orchestration framework eklemek değildir. Bazen yanlış yerde duran iş kuralını sahibine taşımak yeterlidir. Bazen iki parça gerçekten tek modül olmalıdır. Modüler Monolith .NET tasarımında başarının ölçüsü modül sayısı değil, bir iş değişikliğinin hangi alanlara yayıldığının anlaşılabilir olmasıdır.

Modüler Monolith .NET contract tasarımı

Genel bir IRepository<StockItem> interface’i sipariş modülüne stok nesnesini yükleme, alanlarını değiştirme ve kaydetme imkânı verirse teknik bir abstraction kurmuş oluruz. Fakat stok kararını hâlâ sipariş modülü verir. Böyle bir contract, veri erişim yöntemini gizlerken iş kuralını dışarı sızdırabilir. Bunun yerine rezervasyon isteğini ifade eden bir işlem, stok modülünün doğrulama ve concurrency kararlarını kendi içinde tutmasını sağlar. Tüketici yalnızca ihtiyacı olan sonucu görür. Bu yaklaşım API yüzeyini küçültür, ancak contract’ın anlamını açık yazmayı gerektirir.

Rezervasyonun yalnızca başarılı veya başarısız olması her senaryoda yeterli değildir. Ürün bulunamadı, istenen miktar geçersiz, stok yetersiz ve aynı rezervasyon daha önce işlendi durumları farklı kullanıcı davranışları gerektirebilir. Örnekteki bool, sınırı anlaşılır tutmak için seçildi. Gerçek bir contract tasarlanırken hangi sonuçların beklenen iş sonucu olduğu belirlenmelidir. Depoya erişilememesi gibi teknik arızalarla stok yetersizliği aynı sonuçta birleştirilirse çağıran taraf yanlış retry uygulayabilir. Stok yetersizliğine sürekli retry yapmak yeni stok yaratmaz; geçici bağlantı hatası ise başka bir politika gerektirir.

Contract parametrelerinde başka modülün entity’sini taşımak da sınırı zayıflatır. Sipariş modülüne StockItem vermek, tüketicinin stok modelinin yeni alanlarını, navigation property’lerini ve persistence davranışını öğrenmesini kolaylaştırır. Bunun yerine gerekli kimlik, miktar ve işlem bağlamı aktarılabilir. Dönen sonuç da tüketicinin karar vermesi için yeterli bilgi taşımalıdır. Bu, bütün tiplerin DTO olması gerektiği anlamına gelmez. Değişim etkisini sınırlandırmak için hangi tiplerin gerçekten ortak anlam taşıdığını değerlendirmek anlamına gelir. Ortak contract paketi zamanla bütün domain modellerinin deposuna dönüşmemelidir.

Bir rezervasyon kimliğinin anlamı ayrıca önemlidir. Sipariş kaydetme işlemi tekrar geldiğinde yeni rezervasyon oluşturmak mı, önceki sonucu dönmek mi gerekir? Aynı kimlikle farklı miktar gönderilirse ne olur? Rezervasyonun süresi dolabilir mi? Bunlar implementasyon ayrıntısı olarak bırakılırsa tüketiciler farklı varsayımlar geliştirir. Contract yorumları ve davranış testleri bu koşulları görünür hale getirmelidir. Her çağrıya idempotency eklemek zorunlu değildir; fakat tekrarlanan çağrının yan etkisi varsa tekrarın hangi kimlikle tanınacağını baştan düşünmek gerekir. Özellikle ileride HTTP veya mesajlaşmaya geçişte bu kararın maliyeti büyür.

Modüler Monolith .NET: contract sınırı

Tercihim, tüketicinin gerçekten ihtiyaç duyduğu işlemi küçük bir contract üzerinden sunmak. IStockModule, genel CRUD interface’i değildir. Rezervasyonun anlamını, başarısız olabilecek koşulunu ve iptal desteğini belirtir. Interface’i ayrı bir Stock.Contracts assembly’sinde tutabiliriz; implementasyon ise Stock assembly’sinde internal kalır.

Aşağıdaki .NET 10 console örneği, contract çağrısını ve rezervasyon sonucunu gösterir. NuGet bağımlılığı yoktur. dotnet new console --framework net10.0 ile oluşturulan projenin Program.cs dosyasına yerleştirebilirsiniz. Tek dosya, örneği çalıştırmak içindir; gerçek assembly sınırı için tipleri belirtilen projelere ayırın.

IStockModule stock = new StockModule();
var first = await stock.TryReserveAsync(
    "SKU-1",
    3,
    CancellationToken.None
);
var second = await stock.TryReserveAsync(
    "SKU-1",
    3,
    CancellationToken.None
);
if (!first || second)
    throw new InvalidOperationException("Reservation rule failed.");
Console.WriteLine("Reservation boundary verified.");

public interface IStockModule
{
    Task<bool> TryReserveAsync(
        string sku,
        int quantity,
        CancellationToken ct
    );
}

internal sealed class StockModule : IStockModule
{
    private readonly Dictionary<string, int> quantities = new()
    {
        ["SKU-1"] = 5,
    };

    public Task<bool> TryReserveAsync(
        string sku,
        int quantity,
        CancellationToken ct
    )
    {
        ct.ThrowIfCancellationRequested();
        if (string.IsNullOrWhiteSpace(sku))
            throw new ArgumentException("SKU required.", nameof(sku));
        if (quantity <= 0)
            throw new ArgumentOutOfRangeException(nameof(quantity));
        if (
            !quantities.TryGetValue(sku, out var available)
            || available < quantity
        )
            return Task.FromResult(false);
        quantities[sku] = available - quantity;
        return Task.FromResult(true);
    }
}

Örnekteki dictionary yalnızca local davranışı göstermektedir. Thread-safe değildir, restart sonrasında veri korunmaz ve sipariş kaydıyla atomik transaction kurmaz. Bu nedenle bunu production stok deposu olarak kullanmayın. Gerçek rezervasyon işlemi, veritabanında concurrency kontrolü ve rezervasyon kimliğiyle uygulanmalıdır.

Dependency yönünü derleyiciye de anlatmak

Orders projesi yalnızca Stock.Contracts projesine reference ekler. Uygulamayı birleştiren host ise modüllerin registration metotlarını çağırır. Stock implementasyonuna doğrudan project reference eklememek, yanlış bağımlılığı oluşturmayı zorlaştırır. Public tipler, InternalsVisibleTo, reflection ve ortak utility projeleriyle açılan geçitler ayrıca incelenmelidir.

Contract’ı domain entity’sinden ayırmak her veri için büyük DTO hiyerarşisi kurmayı gerektirmez. Örnekteki bool küçük bir demo için yeterlidir; kullanıcıya ayrıntılı hata sunulacaksa insufficient stock ve invalid quantity gibi sonuçlar açık bir modelle temsil edilmelidir. Exception mesajından iş sonucu çıkarmak sözleşmeyi kırılgan hale getirir.

Aynı process, farklı veri sahipliği

Tek database kullanabiliriz. Ancak migration’ı, tabloyu ve yazma kurallarını yöneten modül belli olmalı. Bir rapor başka modülün verisine ihtiyaç duyuyorsa public read contract veya sahibi tarafından yönetilen projection değerlendirin. İç tabloları bütün uygulamanın ortak modeli haline getirmek ilerideki değişim maliyetini büyütür.

Sipariş kaydı ile rezervasyonun aynı anda başarılı olması gerekiyorsa transaction sınırını ayrıca tasarlayın. Ortak relational connection ve transaction seçenekleri modülleri koordine eder; ayrı veritabanlarına geçiş bu garantiyi değiştirebilir. EF Core transaction dokümantasyonu context’ler arası paylaşım koşullarını açıklıyor.

Modüler Monolith .NET tasarımında modülü ayrı servise çıkarma ihtimali için şimdiden bütün çağrıları HTTP’ye çevirmeyi tercih etmem. Önce ownership, contract ve ölçülebilir bağımsızlık ihtiyacını belirleyin. Network eklemek, mevcut bağımlılığı kendiliğinden azaltmaz; timeout, retry ve partial failure sorumlulukları ekler.

Çalışan örneği adım adım okumak

Örnekte başlangıç miktarı beş olan bir ürün bulunuyor. İlk çağrı üç adet ayırmak istiyor. Miktar kontrolü başarılı olduğunda kalan stok ikiye düşüyor ve çağrı true dönüyor. İkinci çağrı yine üç adet istediğinde eldeki iki adet yeterli gelmiyor; miktar değişmeden false dönüyor. Son kontrol, ilk çağrının başarılı ve ikincisinin başarısız olmasını birlikte doğruluyor. Böylece örnek yalnızca ekran çıktısı üretmiyor; gösterilen rezervasyon davranışı bozulursa exception ile bunu görünür hale getiriyor. Bununla birlikte iki ardışık çağrı, eşzamanlı davranışın testi değildir.

CancellationToken kontrolü işlem başlamadan önce iptal talebini gözlüyor. Burada herhangi bir I/O bulunmadığından işlem çok kısa sürüyor. Gerçek implementasyonda token, destekleyen veritabanı çağrılarına da aktarılmalıdır. Token’ı interface’e koymak tek başına iptal desteği sağlamaz. Ayrıca iptal edilmiş bir request, tamamlanmış bir transaction’ın otomatik olarak geri alınacağı anlamına gelmez. İşlem commit edilmiş, yanıt istemciye ulaşmamış olabilir. Tüketicinin iptal sonrasında tekrar çağrı yapması mümkünse rezervasyon kimliği ve durum sorgulama davranışı birlikte düşünülmelidir. İptal ile başarısızlık aynı kavram değildir.

Dictionary üzerindeki kontrol ve güncelleme birlikte atomik değildir. İki thread aynı anda miktarı okuyup her biri kendi hesabına göre başarılı sonuç üretebilir. Dictionary yerine thread-safe bir collection seçmek bile birden fazla adımın iş kuralını kendiliğinden atomik yapmaz. Burada ihtiyaç, tekil okuma güvenliğiyle sınırlı değildir; yeterli stok kontrolü ile azaltmanın birlikte korunmasıdır. Production implementasyonunda uygun transaction, concurrency token veya koşullu update tasarlanmalıdır. Hangisinin doğru olduğu provider’a ve iş kuralına bağlıdır. Bu makaledeki çalışan örnek bu garantiyi uygulamadığı için ona concurrency doğrulaması atfetmiyoruz.

Örneğin tek dosyada bulunması da bilinçli bir öğretim sınırıdır. internal implementasyon aynı assembly içinde erişilebilir kalır; onu sipariş kodundan ayıracak gerçek project yapısı bu dosyada kurulmuş değildir. Ayrı assembly’lere taşıdığınızda contract ve implementasyon görünürlüğünü derleyici üzerinden sınamaya başlayabilirsiniz. C# erişim kurallarının ayrıntıları için Microsoft’un internal referansına bakabilirsiniz. Derleyicinin sağladığı sınır, reflection veya veri tabanına doğrudan erişim gibi başka kanalları kapatmaz. Assembly görünürlüğü mimari sınırın bir parçasıdır, bütün garantisi değildir.

Solution yapısını ne kadar parçalamalıyız?

Başlangıç için host, modül implementasyonu ve dışarı açılan contract’ları ayrı düşünmek yeterli olabilir. Her modül için hemen Domain, Application, Infrastructure, Persistence, Abstractions ve Shared projeleri oluşturmak zorunlu değildir. Bir modülün iç karmaşıklığı henüz düşükse bu katmanlar aynı assembly içinde namespace düzeniyle yönetilebilir. Ayrı proje, bağımlılık yönünü derleyiciyle korumak veya farklı derleme ihtiyaçlarını karşılamak için somut fayda sağladığında eklenmelidir. Dosyaları çok sayıda projeye dağıtmak, bir özellik üzerinde çalışan geliştiricinin anlaması gereken kavram sayısını azaltmayabilir.

Tercihim: Önce tüketicinin görmemesi gereken implementasyonu kapatan sınırı kurmak, sonra modül içindeki ayrımı ihtiyaçla büyütmek. Bu seçim küçük ve orta ölçekli bir ekipte geliştirme akışını sade tutar. Kararın maliyeti, aynı assembly içindeki bazı bağımlılıkların yalnızca ekip kuralı veya mimari testle korunmasıdır. Domain’in infrastructure paketlerinden bağımsız kalması kritikse ayrı assembly daha erken anlamlı olur. Farklı ekipler modül üzerinde çalışıyor veya bağımlılık ihlalleri sık tekrar ediyorsa derleyici kısıtlarını güçlendirmek için ek proje ayrımı tercih edilebilir.

Host’un rolü iş kuralı dağıtmak değil, uygulamayı birleştirmektir. Konfigürasyon, dependency registration ve çalıştırma düzeni burada bulunabilir. Siparişin hangi stok kuralıyla onaylanacağı host içinde yazılmaya başlarsa modüller yalnızca teknik bileşene dönüşür. Benzer şekilde registration metodu bütün persistence tiplerini dışarı açmamalıdır. Tüketicinin kullanacağı interface’ler ile yalnızca modülün ihtiyaç duyduğu servisleri ayırmak gerekir. Lifetime kararları da bu noktada incelenmelidir; scoped veri erişimi kullanan bir implementasyonu singleton tüketiciye taşımak, sınır doğru görünse bile çalışma zamanı hatası doğurabilir.

Ortak database kullanırken ownership nasıl korunur?

Tek database operasyonel olarak kullanışlıdır: bağlantı yönetimi, yedekleme ve transaction koordinasyonu daha az bileşen gerektirebilir. Bunun karşılığında fiziksel yakınlık, başka modülün tablosunu kolayca kullanma isteğini artırır. Ayrı schema veya tablo öneki sorumluluğu görünür kılar; ancak erişim yetkileri aynıysa teknik olarak yazmayı engellemez. Dolayısıyla isimlendirme, migration sahipliği, kod reference’ları ve testler birlikte düşünülmelidir. Bir modülün migration’ı başka modülün kolonunu değiştiriyorsa nedenini açıklayabilmeliyiz. Database düzeni ile code ownership aynı kararı desteklemelidir.

Okuma ihtiyacı için her zaman senkron contract çağrısı gerekmeyebilir. Yönetim ekranı farklı modüllerin verilerini birlikte listeleyecekse sahibi belli olan bir projection oluşturulabilir. Bu projection’ın ne kadar güncel olması gerektiği açık yazılmalıdır. Sipariş onayında kullanılan stok kontrolü ile günlük rapordaki stok özeti farklı tutarlılık ihtiyaçları taşır. Raporu hızlandırmak için oluşturulan görünüm, kritik iş kararının kaynağına dönüşürse eski veriyle işlem yapılabilir. Read model’in hangi iş için kullanılabileceği, yalnızca kolon listesinden anlaşılamaz; tüketim amacı da contract’ın parçasıdır.

Modüller arası foreign key de ayrı değerlendirme gerektirir. Veritabanı bütünlüğü sağlayabilir, fakat migration sırasını ve bağımsız değişim alanını bağlayabilir. Her foreign key’i kaldırmak otomatik olarak daha modüler bir tasarım oluşturmaz. Kaldırıldığında referans bütünlüğünü kim, hangi anda ve hangi hata yoluyla sağlayacak? Bağımsız taşınma olasılığı ile bugünkü veri doğruluğunun bedelini karşılaştırmak gerekir. Ortak database kullanan ve birlikte deploy edilen bir sistemde bazı database kısıtları bilinçli olarak tutulabilir. Bu durumda ileride servis ayrıştırmasının yalnızca kod taşıma işi olmayacağı belgelenmelidir.

Transaction seçiminin iş sonucu üzerindeki etkisi

Sipariş kaydıyla stok rezervasyonunun tek atomik işlem olması bir seçenek olabilir. Ortak transaction başarısız olduğunda iki değişiklik birlikte geri alınır. Bunun için kullanılan relational bağlantı ve transaction düzeni bilinçli tasarlanmalıdır; yalnızca iki modülün aynı database adresini kullanması yeterli değildir. Bu koordinasyon modüllerin çalışma biçimini bağlar. Alternatif olarak sipariş bekleyen durumda kaydedilebilir ve rezervasyon ayrı adımda tamamlanabilir. İkinci yaklaşım daha fazla ara durum ve telafi davranışı gerektirir. Seçim, müşteriye hangi durumların gösterilebildiğine ve işin ne kadar süre belirsiz kalabildiğine dayanmalıdır.

Ödeme gibi harici bir yan etkiyi bu yerel transaction’ın kapsamına aldığımızı varsayamayız. Veritabanı rollback olduğunda ödeme servisi tahsilatı kendiliğinden geri almaz. Bu nedenle transaction sınırını genişletmek yerine tamamlanmış ödeme, başarısız rezervasyon ve başarısız sipariş kaydı kombinasyonları için açık iş akışı gerekir. Yerel commit sonrasında güvenilir mesaj üretimi ihtiyacı varsa outbox değerlendirilebilir. Ancak outbox da tüketici tarafındaki bütün yan etkileri tek seferlik yapmaz. Modüler sınır tasarımına bir pattern eklemek, o pattern’in sağlamadığı garantileri de sağlamış gibi kabul edilmemelidir.

Sınırı doğrulayan testler ve operasyon sinyalleri

Bir modül contract’ının testleri başarılı çağrının yanında geçersiz miktar, olmayan ürün, yetersiz stok, iptal ve tekrar davranışını da ele almalıdır. Gerçek persistence implementasyonu concurrency garantisi vaat ediyorsa aynı ürüne paralel rezervasyon göndererek toplam ayrılan miktarın stoktan fazla olmadığını kontrol edin. Provider’ın davranışını görmek için kullanılan test database’i production kararına uygun olmalıdır. Basit bir in-memory fixture, relational constraint veya isolation davranışını doğrulamaz. Testin kapsamını sonucu kadar açık yazmak, ileride yanlış güven oluşmasını önler.

Mimari testte ise sipariş assembly’sinin stok implementasyonuna reference taşımadığını, contract’ın persistence entity’si döndürmediğini ve izin verilmeyen package bağımlılıklarını kontrol edebilirsiniz. Her namespace kuralına test eklemek gerekli değildir. Sık bozulan ve etkisi yüksek sınırları otomatik korumak daha değerlidir. Modül seviyesinde integration testi kurarken yalnızca tek servisi instantiate etmek yerine registration ve gerçek bağımlılıkları da çalıştırmak bazı wiring hatalarını yakalar. Fakat bunun başarılı olması bütün uygulamanın deploy veya migration sürecinin doğrulandığı anlamına gelmez.

Operasyonda toplam request süresinin yanında modül çağrılarının süresi ve hata nedeni izlenmelidir. Aynı process içinde çalışan bir modül de pahalı sorgular veya geniş transaction’larla başka iş akışlarını etkileyebilir. Stok yetersizliği iş metriğidir; bağlantı hatası operasyon sinyalidir. İkisini aynı hata sayacında birleştirmek hem alarm gürültüsü hem yanlış ürün yorumu üretir. Log ve trace kayıtları modül ile işlem kimliğini ilişkilendirebilir. Müşteri verisi veya hassas payload’ları sırf teşhis kolaylığı için kaydetmek yerine gerekli bağlamı sınırlı ve anlamlı alanlarla taşımak gerekir.

Bir modülü ayrı servise çıkarma kararı

Modüler Monolith .NET yaklaşımında ayrı servise geçiş için adaylık, modülün kendi contract’ını ve verisini yönetebilmesiyle başlar. Yine de bağımsız deployment, farklı ölçekleme ihtiyacı veya ekip organizasyonu gibi somut gerekçe gerekir. Tek process içindeki çağrıyı HTTP’ye çevirmek timeout, erişilebilirlik ve contract versioning kararları ekler. Ortak transaction kullanan akışlar yeniden tasarlanır. Bu nedenle önce hangi çağrıların request yanıtını beklediğini, hangi verilerin birlikte değiştiğini ve hangi raporların doğrudan join yaptığını çıkarın. Servis ayrıştırmasının bedeli bu bağlantılarda görünür olur.

Geçişin ilk adımı, bütün modülleri aynı anda dağıtık hale getirmek olmamalıdır. Bağımlılıkları en anlaşılır olan tek aday üzerinde veri taşıma, contract uyumluluğu ve geri dönüş planı kurulabilir. Eski ve yeni implementasyonun aynı işi iki kez yapmaması özellikle önemlidir. Trafiği bölmek mümkünse routing kararı ile veri sahipliği aynı anda tutarlı olmalıdır. Ayrı servisin mevcut mimari sorunu çözüp çözmediğini ölçmek gerekir; yeni network katmanının getirdiği gecikme veya operasyon maliyeti, bağımsızlık faydasını aşabilir. Ölçüm yapılmadan hız veya maliyet kazancı iddia etmeyin.

Mevcut uygulamada ilk hafta yapılabilecek değişiklikler

Çalışan bir monolith üzerinde başlarken bütün domain’i yeniden modellemek yerine tek bir kullanım senaryosunu seçin. Örneğin sipariş oluşturma akışında stok tablosunu doğrudan güncelleyen kodu bulun. Bu kodun kullandığı alanları, verdiği kararları ve çağıranlarını listeleyin. Ardından aynı davranışı stok modülüne ait bir işlem altında toplayın. İlk değişiklikte iş sonucunu değiştirmemek, sınır düzenlemesinin etkisini anlamayı kolaylaştırır. Mevcut transaction davranışı varsa yeni contract üzerinden de nasıl korunacağını açıklayın. Refactoring sırasında aynı anda rezervasyon politikası, persistence provider ve mesajlaşma biçimini değiştirmek hata kaynağını ayırmayı zorlaştırır.

Sonraki adım, bu yeni geçidi kullanmayan erişimleri görünür hale getirmektir. Kod aramasıyla stok entity’sinin başka modüllerde nerede kullanıldığını çıkarabilirsiniz. Her erişimi bir anda silmek yerine iş kararında kullanılan yazmaları önce taşıyın. Raporlama okumaları için ayrı bir geçiş planı hazırlayın. Böylece kritik invariant’ları koruyan sınır erkenden güçlenir; yüksek sayıda raporun dönüşümü daha kontrollü ilerler. Bir istisna geçici olarak bırakılıyorsa sahibi ve kaldırılma koşulu belli olmalıdır. Açıklamasız istisna zamanla uygulamanın normal bağımlılık yolu haline gelir.

İlk hafta sonunda ulaşılabilecek anlamlı sonuç, bütün solution’ın yeni klasörlere taşınması değildir. Seçilen iş akışında stok kararının tek sahibi bulunmalı, tüketici contract dışında persistence modeline ihtiyaç duymamalı ve davranış testleri aynı sonucu doğrulamalıdır. Takım yeni bir özellik eklediğinde bu sınırı hangi yoldan kullanacağını anlayabilmelidir. Sonraki modülü seçerken en kolay taşınan kodu değil, bağımlılık yüzünden en çok değişiklik maliyeti üreten alanı değerlendirin. Bu yaklaşım modülerliği bir büyük dönüşüm projesinden çıkarıp günlük geliştirmede korunan ve ölçülebilen bir tasarım kararına dönüştürür.

By tanju.bozok

Senior .NET Engineer | Backend architecture, distributed systems & real-time communication