Rails Monolith'te Modülerlik: Kod Sınırlarıyla Alan Ayrımı

## Monolith Mimarinin Temel Varsayımları Rails'in "monolith" yaklaşımı, tek bir uygulama süreci içinde birden fazla iş alanını barındırmak anlamına g...

Rails Monolith'te Modülerlik: Kod Sınırlarıyla Alan Ayrımı

Monolith Mimarinin Temel Varsayımları

Rails'in "monolith" yaklaşımı, tek bir uygulama süreci içinde birden fazla iş alanını barındırmak anlamına gelir. Bu, modülerliğin reddi değildir; modülerliğin süreç sınırlarıyla değil, kod sınırlarıyla kurulmasıdır. Rails'in varsayılan üretim yapılandırması bu modeli destekler: tek bir Gemfile, tek bir veritabanı bağlantı havuzu, tek bir dağıtım artefaktı.

Bu rehber, Rails 7.x ve 8.x sürüm hattını kapsar. Örneklerde kullanılan API'ler bu sürümlerde mevcuttur; daha eski sürümlerde bazıları farklılık gösterebilir. Önkoşul olarak Rails uygulama yapısına, Active Record ilişkilerine ve bin/rails komutlarına aşinalık beklenir.

Neden Tek Süreç, Çok Alan?

Mikroservis yaklaşımının maliyeti çoğunlukla dağıtım ve gözlemlenebilirlik tarafında ortaya çıkar: ağ gecikmesi, dağıtık işlem yönetimi, sürüm uyumsuzluğu. Monolith'te bu maliyet yoktur; bir metot çağrısı aynı bellek uzayında gerçekleşir. Bu, özellikle ekiplerin küçük olduğu ve alan sınırlarının henüz netleşmediği projelerde avantaj sağlar.

Ancak bu avantaj, disiplinle korunur. Aksi halde monolith, birbirine sıkı bağlı (tightly coupled) bir kod yığınına dönüşür ve modülerliğin getirdiği faydalar kaybolur.

Modülerliği Kod Sınırlarıyla Kurmak

Rails'in app/ dizini varsayılan olarak katman bazlıdır (models, controllers, views). Alan bazlı sınırlar için iki yaygın yaklaşım vardır.

Yaklaşım 1: packages Dizini ve Zeitwerk

Rails 6 ile gelen Zeitwerk otomatik yükleme, app/ altındaki herhangi bir dizini otomatik olarak yükler. Bu nedenle alan bazlı klasörler doğrudan app/ altına yerleştirilebilir:

# app/billing/invoice.rb
module Billing
  class Invoice < ApplicationRecord
    belongs_to :customer, class_name: "Billing::Customer"
  end
end

Bu yapıda Billing::Invoice sınıfı, app/billing/invoice.rb dosyasından otomatik yüklenir. Ek yapılandırma gerekmez.

Yaklaşım 2: app/models İçinde Modül Ad Alanları

Daha az müdahaleci bir yaklaşım, mevcut katmanları koruyup modül ad alanları kullanmaktır:

# app/models/billing/invoice.rb
module Billing
  class Invoice < ApplicationRecord
    self.table_name = "billing_invoices"
  end
end

Bu yöntem, ekip alışkanlıklarını bozmadan kademeli geçişe izin verir. Tablo adlarının modül ad alanıyla çakışmaması için table_name açıkça belirtilmelidir; aksi halde Rails, Billing::Invoice için billing_invoices tablosunu arar ve bu genellikle beklenen davranıştır.

Alan Sınırlarını Zorlamak

Modül ad alanları yalnızca isimlendirme sağlar; sınırları zorlamaz. Sınırları korumak için birkaç pratik kural uygulanabilir:

  • Alanlar arası çağrılar yalnızca açıkça tanımlanmış arayüzler üzerinden yapılmalıdır. Örneğin Billing::Invoice doğrudan Shipping::Shipment içinde kullanılmamalı; bunun yerine bir servis nesnesi veya olay aracılığıyla iletişim kurulmalıdır.
  • Ortak bağımlılıklar (örneğin ApplicationRecord, ApplicationController) tüm alanlar tarafından paylaşılır; bu beklenen bir durumdur.
  • Alanlar arası veritabanı ilişkileri (has_many :invoices, class_name: "Billing::Invoice") mümkündür ancak yabancı anahtar bağımlılıklarını artırır. Alternatif olarak kimlik tabanlı referanslar kullanılabilir.

Uygulama Sınırlarını Test Etmek

Monolith'te test stratejisi, alan sınırlarının korunup korunmadığını doğrulamalıdır. Örneğin, bir alanın diğerine doğrudan bağımlılığını tespit etmek için basit bir kontrol yazılabilir:

# test/architecture/boundaries_test.rb
require "test_helper"

class BoundariesTest < ActiveSupport::TestCase
  def test_billing_does_not_reference_shipping
    billing_files = Dir[Rails.root.join("app/billing/**/*.rb")]
    offenders = billing_files.select do |file|
      File.read(file).include?("Shipping::")
    end
    assert_empty offenders, "Billing, Shipping modülüne doğrudan bağımlı olmamalı"
  end
end

Bu test, statik bir kontrol yapar ve derleme zamanında sınır ihlallerini yakalar. Beklenen davranış: ihlal yoksa test geçer; varsa dosya yolu hata mesajında görünür.

Yaygın Hatalar

  • Modül ad alanı ile tablo adını karıştırmak: Billing::Invoice için tablo adı varsayılan olarak billing_invoices olur. Mevcut bir invoices tablosuna bağlanmak istiyorsanız self.table_name = "invoices" yazmalısınız; aksi halde ActiveRecord::StatementInvalid alırsınız.
  • Zeitwerk beklentisiyle dosya adını uyumsuzlaştırmak: app/billing/invoice.rb dosyası Billing::Invoice tanımlamalıdır. Billing::Invoices gibi çoğul bir sınıf adı kullanırsanız Zeitwerk yükleme hatası verir.
  • Alanlar arası doğrudan model çağrıları: Küçük projelerde sorun çıkarmasa da, alan sayısı arttıkça değişiklik maliyetini yükseltir. Sınır ihlallerini erken yakalamak için yukarıdaki gibi testler eklenebilir.

Güvenlik Etkileri

Monolith mimarisi, güvenlik açısından mikroservislere kıyasla daha az saldırı yüzeyi sunar: servisler arası kimlik doğrulama, ağ düzeyinde izolasyon ve dağıtık yetkilendirme katmanları gerekmez. Ancak bu, güvenlik risklerinin ortadan kalktığı anlamına gelmez.

  • Yetkilendirme tek noktada toplanmalıdır: Tüm alanlar aynı süreçte çalıştığı için, bir alandaki yetki kontrolü eksikliği diğer alanları da etkiler. Pundit veya CanCanCan gibi bir yetkilendirme katmanı tüm denetleyicilerde tutarlı uygulanmalıdır.
  • Toplu atama (mass assignment) riski: params.require(...).permit(...) kullanımı alan bazında tutarlı olmalıdır. Modül ad alanları bu korumayı otomatik sağlamaz.
  • CSRF ve oturum yönetimi: Rails'in varsayılan korumaları (protect_from_forgery, ActionController::Base üzerinden gelen oturum yönetimi) monolith yapıda doğrudan geçerlidir. API-only modda bu korumaların bilinçli olarak devre dışı bırakıldığını unutmayın.

Ne Zaman Monolith Yeterli Olmaz?

Alanlar arası iletişim süreç sınırları gerektiriyorsa (örneğin bağımsız ölçekleme, farklı dil/runtime zorunluluğu, ekipler arası bağımsız dağıtım), monolith bu ihtiyaçları karşılamaz. Ancak bu karar, mimari tercih değil operasyonel bir zorunluluk olmalıdır. Çoğu projede monolith, alan sınırları doğru kurulduğu sürece yeterlidir ve daha düşük toplam maliyetle çalışır.

İLGİLİ İÇERİKLER

Bunları da Okumak İsteyebilirsiniz