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::InvoicedoğrudanShipping::Shipmentiç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::Invoiceiçin tablo adı varsayılan olarakbilling_invoicesolur. Mevcut birinvoicestablosuna bağlanmak istiyorsanızself.table_name = "invoices"yazmalısınız; aksi haldeActiveRecord::StatementInvalidalırsınız. - Zeitwerk beklentisiyle dosya adını uyumsuzlaştırmak:
app/billing/invoice.rbdosyasıBilling::Invoicetanımlamalıdır.Billing::Invoicesgibi ç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.