ASP.NET'te kutudan çıkan modüler monolit: HostingStartup ile modül yükleme
ASP.NET Core 2.0'dan beri var olan hosting startup assemblies mekanizması, modüler monolit kurmak için NuGet paketi ya da kendi plugin sisteminizi yazmadan yeterli olabiliyor. Modüller `[assembly: HostingStartup]` taşıyan sıradan sınıf kütüphaneleri; hangilerinin yükleneceğine `HOSTINGSTARTUPASSEMBLIES` ortam değişkeni karar veriyor. Böylece tek Docker imajı, farklı compose değerleriyle monolit ya da dağıtık topoloji olarak çalışabiliyor.
Kilit noktalar
- Modül, `[assembly: HostingStartup]` attribute'u taşıyan normal bir sınıf kütüphanesidir; `IHostModule` benzeri bir arayüz gerekmez.
- Hangi modüllerin yükleneceğini `HOSTINGSTARTUPASSEMBLIES` ortam değişkeni belirler; aynı image farklı compose değerleriyle monolit veya dağıtık çalışır.
- Endpoint ve middleware eklemek için modül kendini `IStartupFilter` olarak kaydedip `UseEndpoints` kullanmalı; host'ta açık bir `app.UseRouting()` bulunmalı.
- `IHostingStartup.Configure` içinde `IWebHostBuilder.Configure` çağırmak host pipeline'ını tamamen değiştirdiği için yasak; sadece `ConfigureServices` ve `ConfigureAppConfiguration` kullanılmalı.
- MVC ve Razor Pages, yüklenmemiş modüllerin controller ve page'lerini bile bulur; `GenerateMvcApplicationPartsAssemblyAttributes=false` ile bu otomatik keşif kapatılıp parçalar elle eklenmeli.
- Hatalı assembly adı log'a crit yazıp başlatmayı sürdürür; sessiz atlanan modülleri fark etmek için log takibi şart.
Yazı, ASP.NET Core'un az bilinen hosting startup assemblies özelliğini modüler monolit için kullanmayı anlatıyor. `WebApplication.CreateBuilder` çalışırken `HostingStartupAssemblies` konfigürasyon anahtarını (ortam değişkeni `HOSTINGSTARTUPASSEMBLIES`) okuyor, listedeki assembly'leri `Assembly.Load` ile yüklüyor ve içindeki `[assembly: HostingStartup(typeof(T))]` attribute'u üzerinden `IHostingStartup.Configure(IWebHostBuilder)` metodunu çağırıyor. Uygulamanın kendi assembly'si her zaman ilk sırada çalışıyor; geri kalanlar değişkendeki sırayla geliyor.
Modül, `OutputType=Library` verilmiş bir Web SDK projesi. İçinde `ConfigureServices` ve `ConfigureAppConfiguration` ile servis ve konfigürasyon kaydı yapılıyor; endpoint ve middleware eklemek için modül kendini `IStartupFilter` olarak DI'ya kaydediyor ve `UseEndpoints` kullanıyor. Host'un `Program.cs` dosyasında modüllerden hiç söz edilmiyor, sadece proje referansları derleme çıktısına kopyalanmaları için duruyor. Aynı `ICalculator` kontratının in-process, HTTP, gRPC ve RabbitMQ (EasyNetQ) implementasyonları ayrı modüller olarak takılıp çıkarılabiliyor, iş kodu değişmiyor.
Gerçek dünyada kullanımı
- Aynı kod tabanından hem tek süreçli monolit hem de servis başına ayrı container çıkarmak isteyen bir SaaS ekibi
- Geliştirme ve testte in-process `ICalculator` implementasyonu, production'da HTTP veya gRPC implementasyonu kullanan bir backend
- Modüler monoliti yavaş yavaş dağıtık hale getirmek isteyen, ancak her adımda iş koduna dokunmak istemeyen bir ekip
- Entegrasyon testlerinde tüm modülleri tek host'ta F5 ile çalıştırıp debug etmek isteyen geliştiriciler
Neden önemli
Modülerliği host'tan tamamen ayırdığı için dağıtım topolojisi bir konfigürasyon kararı haline geliyor; modül sınırlarını yanlış çizdiğinizde ya da mimariyi değiştirmek istediğinizde kod yeniden yazılmıyor.