İçeriğe geç
Muhammet Şafak
en
Güncelleme 3 dk okuma

Projelere Neden Modüler Monolit ile Başlıyorum?

Mikroservislere atlamadan önce tek deploy edilebilir bir uygulamada net modül sınırları kurmanın gerekçesi — ve bu kararı ne zaman bıraktığım.

Kısaca

Modül sınırı çizmek ile onu ayrı bir servise koymak iki ayrı karardır; mikroservis mimarisi bu ikisini tek karara sıkıştırıyor. Modüler monolit onları ayırıyor — sınır bugün bir method çağrısı, ölçülmüş bir sinyal geldiğinde bir network çağrısı. Bütün değer zorlamada: sınır ihlalinde CI build'i kırmıyorsa, elinizdeki modüler monolit değil, klasör isimleri düzgün bir monolittir.

Projelere Neden Modüler Monolit ile Başlıyorum? — kapak görseli

Yeni bir projenin ilk gününde, mimari tahtasının başında biri mutlaka şu soruyu soruyor: “Servisleri nasıl böleceğiz?” Cevabım çoğu zaman aynı: “Şimdilik bölmüyoruz.”

Bu bir erteleme ya da üşengeçlik değil. Neredeyse her yeni projeye bilinçli olarak modüler monolit ile başlıyorum: tek codebase, tek deploy, tek veritabanı — ama içeride net modül sınırları. Bu yazı neden böyle yaptığımın gerekçesi.

”Modüler monolit” ne demek?

Üç şeyi birbirinden ayırmak gerekiyor:

  • Klasik monolit — sınır yok. Her şey her şeye erişir, zamanla “big ball of mud”.
  • Mikroservisler — sınır var, ama sınır aynı zamanda bir network çağrısı.
  • Modüler monolit — sınır var, ama sınır process içinde; bir method çağrısı.

Asıl mesele şu: modül sınırı çizmek ile onu ayrı bir servise koymak iki ayrı karardır. Mikroservis mimarisi bu ikisini tek bir karara sıkıştırıyor — sınır demek, otomatik olarak ayrı deploy, ayrı veritabanı, network demek. Modüler monolit bu ikisini ayırıyor: sınırı bugün çizin, dağıtımı gerçekten gerekince yapın.

Sınırlar kodda nasıl görünüyor

Laravel tarafında kullandığım yerleşim kabaca şu:

app/
└── Modules/
    ├── Billing/
    │   ├── Domain/            # entity'ler, value object'ler — modüle özel
    │   ├── Application/       # use-case'ler
    │   ├── Infrastructure/    # repository, dış servis adaptörleri
    │   └── BillingApi.php     # modülün TEK public yüzeyi
    ├── Catalog/
    └── Notification/

Kural basit: bir modül, başka bir modülün yalnızca *Api.php sınıfını çağırır. Domain ve Infrastructure içindeki her şey o modüle özeldir — dışarıdan görünmez.

İkinci kural veritabanında: bir modül, başka bir modülün tablosuna doğrudan SELECT atmaz, foreign key bağlamaz. Catalog’un bir faturaya ihtiyacı varsa BillingApi’den geçer, billing_invoices tablosuna el uzatmaz. Bu disiplin sıkıcı görünüyor — değeri tam da burada.

Neden buradan başlıyorum?

Beş somut sebep:

  1. Sınırı yanlış çizme maliyeti düşük. Projenin ilk haftasında domain’i tam anlamıyorsunuz. Modül sınırı yanlış çıkarsa, modüler monolitte bunu düzeltmek bir refactor — IDE’nin “move” komutu ve birkaç saat. Aynı hatayı mikroserviste düzeltmek iki servisin API kontratını, iki deploy pipeline’ını ve canlı veri taşımayı içeriyor. Erken dönemde sınırlar kesinlikle yanlış olacak; ucuz düzeltilebilen yapıyı seçin.

  2. Tek transaction. İki modülü tek bir DB transaction’ı içinde tutarlı güncelleyebiliyorum. Mikroserviste aynı şey saga, outbox, eventual consistency demek. Bunlar gerçek araçlar — ama gerçekten gerekene kadar ertelenmesi gereken bir karmaşıklık.

  3. Operasyonel yük neredeyse sıfır. Tek deploy, tek log akışı, izlenecek tek hedef. Servisler arası retry, distributed tracing, kontrat versiyonlama — bunların hiçbiri yok. Üç kişilik bir ekip için bu, haftada kazanılan günlerdir.

  4. Refactor araçları çalışıyor. “Rename”, “find usages”, statik analiz — hepsi process sınırı içinde işliyor. Bir method çağrısını compiler doğruluyor; bir network kontratını kimse doğrulamıyor.

  5. Mikroservise geçişi kolaylaştırıyor. En sık atlanan nokta bu: sınırlar zaten net olduğu için, bir modülü ayrı servise çıkarmak ileride mekanik bir iş haline geliyor. Modüler monolit, mikroservislerin alternatifi değil; doğru yapıldığında onlara giden en ucuz yol.

Sınırı ayakta tutan disiplin

Modüler monolitin tek zayıf noktası şu: sınırlar zorla uygulanmıyorsa, yapı sessizce çürüyüp eski tip monolite döner. “Sadece bu sefer” diye atılan her doğrudan çağrı bir delik açar.

Sınırı ayakta tutan dört pratik:

  • Statik bağımlılık kuralları. deptrac ya da benzeri bir araçla “Catalog, Billing’in Domain katmanına erişemez” kuralını CI’da zorunlu kılın. İhlal, build’i kırsın.
  • Tek public yüzey. Her modülün dışarı açık API’si tek bir sınıf; geri kalan her şey internal. Görünürlüğü dilin izin verdiği ölçüde daraltın.
  • Veri sınırı. Modüller arası ilişki foreign key ile değil, ID + public çağrı ile kurulur.
  • Review’da tek soru. Kod incelemesinde sorulacak en önemli soru: “Bu değişiklik bir modül sınırını izinsiz geçiyor mu?”

Sınır CI tarafından zorlanmıyorsa, elinizde modüler monolit değil, sadece klasör isimleri düzgün bir monolit var. Bütün değer disiplinde.

Ne zaman ayrı servise bölüyorum?

Modülü ayrı bir servise çıkarmak için ölçülmüş bir sebep beklerim. Dört sinyalden biri yeterli:

  1. Ölçeklenme profili ayrışıyor. Notification modülü CPU-bound, geri kalan sistem I/O-bound. Birini diğerinden bağımsız ölçeklemek istiyorsam, ayırmak artık karşılığını veriyor.
  2. Sahiplik ayrışıyor. Modül ayrı bir ekibin sorumluluğuna geçti ve bağımsız deploy artık teknik değil, organizasyonel bir ihtiyaç.
  3. Farklı bir runtime gerekiyor. Ağır bir hesaplama Go’da çok daha ucuz, geri kalan PHP’de kalmalı.
  4. Hata izolasyonu zorunlu. Bir modülün çökmesi gerçekten geri kalan sistemi düşürmemeli ve bunu process içinde garanti edemiyorum.

Bunlardan biri ölçülerek ortaya çıkana kadar bölmek erken optimizasyondur. Bu, boring architecture savunmamın doğrudan uzantısı: karmaşıklığı, onu zorlayan kanıt gelene kadar erteleyin. Sınırlar baştan net olduğu için, o gün geldiğinde bölme işi korkutucu bir göç değil, planlı bir adım oluyor.


Mikroservisler bir mimari tarzı değil, bir dağıtım kararıdır. O kararı projenin ilk gününde — domain hakkında en az şey bildiğiniz anda — vermek için hiçbir sebep yok. Modüler monolit bana o kararı erteleme ve zamanı geldiğinde hazır olma lüksünü veriyor.

Sınırları erken çizin, dağıtımı geç yapın.

Sık sorulanlar

3 soru

  • Modüler monolit ne demek?

    Tek codebase, tek deploy, tek veritabanı — ama process içinde gerçek modül sınırları. Bir modül, başka bir modülün yalnızca tek public API sınıfını çağırır; Domain ve Infrastructure içindeki hiçbir şeyi görmez. İkinci kural veritabanında: bir modül başka bir modülün tablosuna doğrudan SELECT atmaz ve foreign key bağlamaz.

  • Modül sınırını çürümekten ne koruyor?

    Dört pratik. deptrac ya da benzeri bir araçla CI'da zorunlu kılınan statik bağımlılık kuralları — ihlal build'i kırar. Modül başına tek public yüzey sınıfı ve dilin izin verdiği ölçüde daraltılmış görünürlük. Foreign key yerine ID artı public çağrı ile kurulan veri sınırı. Ve her code review'da tek bir soru: bu değişiklik bir modül sınırını izinsiz geçiyor mu?

  • Bir modülü ne zaman ayrı servise bölüyorum?

    Dört sinyalden biri ölçüldüğünde: ölçeklenme profili ayrıştığında, sahiplik ayrı bir ekibe geçtiğinde, gerçekten farklı bir runtime gerektiğinde ya da hata izolasyonu zorunlu olup process içinde garanti edilemediğinde. Bunlardan biri ölçülerek ortaya çıkana kadar bölmek erken optimizasyondur.

Kaynaklar

Uygulamada

Bu ilkenin uygulandığı yazılar ve cevaplar:

İlgili

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi