Monolitik mimariden mikroservislere geçiş, ölçeklenebilirlik ve bağımsızlık için sihirli çözüm olarak sunulur. Ancak birçok mühendislik ekibi, mantığı dağıtmadan veritabanı karmaşıklığını dağıtma tuzağına düşer. Bu durum, teknik olarak dağıtılmış ancak kavramsal olarak monolitik sistemlere yol açar. Veritabanı mühendisliğinde, verinizi nasıl yapılandırdığınız, kodunuzu nasıl yapılandırdığınız kadar kritik öneme sahiptir. Bugün, iki yaygın anti-pattern olan Dağıtılmış Monolit ve Aşırı Normalizasyon'u keşfedecek ve bunlardan kaçınarak gerçekten dayanıklı mikroservisler oluşturmanın yollarını tartışacağız.
Dağıtılmış Monolit
Dağıtılmış monolit, mikroservislerin kod seviyesinde bağımsız göründüğü ancak veri seviyesinde sıkı bir şekilde birbirine bağlı olduğu durumdur. Bu genellikle servislerin tek bir veritabanı şemasını paylaştığı veya bir servisin başka bir servisin veritabanı tablolarına doğrudan sorgu attığı durumlarda ortaya çıkar. Bu durum, mikroservislerin temel ilkesini ihlal eder: Her servis verisini yalnızca kendisi yönetmelidir.
OrderService'nin müşteri verilerine ihtiyacı olduğunu varsayalım. Geliştiriciler, bu veriyi iyi tanımlanmış bir API üzerinden yaymak yerine, doğrudan CustomerService'in veritabanına join yapabilirler. Bu, katı bir bağımlılık yaratır. Müşteri veri şeması değiştiğinde, sipariş servisi kodu güncellenmiş olsa bile sipariş servisi bozulur. Bu bağımlılık, bağımsız dağıtımı ve ölçeklendirmeyi engeller.
Bunu düzeltmek için, Hizmet Başına Veritabanı (Database Per Service) desenini sıkı bir şekilde benimseyin. Servisler, asenkron olaylar (örneğin Kafka veya RabbitMQ kullanarak) veya senkron REST/gRPC API'leri üzerinden iletişim kurmalıdır. Bu, veri sahipliğinin servis sınırları içinde kapsüllenmesini sağlar.
Aşırı Normalizasyonun Tuzağı
Veriyi normalleştirmek, geleneksel ilişkisel veritabanlarında yinelenmeyi azaltır ve bütünlüğü sağlar; ancak dağıtılmış ortamlarda performans darboğazına dönüşür. Aşırı normalizasyon, anlamlı varlıkları yeniden oluşturmak için karmaşık join'lere ihtiyaç duyulan çok sayıda küçük tabloya verinin aşırı şekilde bölünmesi anlamına gelir.
Monolitik bir yapıda join'ler ucuzdur çünkü her şey aynı işlem alanındadır. Mikroservislerde ise veri farklı ağ sınırları boyunca bölünmüştür. Tek bir mantıksal varlığı yeniden oluşturmak genellikle birden fazla servise sorgu atmayı ve sonuçları uygulama tarafında birleştirmeyi gerektirir. Bu durum, N+1 sorgu sorununa, artan gecikmeye ve hata işlemede daha yüksek karmaşıklığa yol açar.
Bunun yerine normalleştirmek yerine, normalleştirme dışı (denormalization) yaklaşımını düşünün. Yinelenen veriyi ihtiyaç duyulan yerde saklayın. Örneğin, bir OrderService sık sık müşterinin adını görüntülüyorsa, bu adı doğrudan sipariş kaydında saklayın. Bu durum güncelleme anormalliklerine yol açsa da (müşteri adını değiştirdiğinde tüm mevcut siparişlerde adı güncellemek zorunda kalırsınız), okuma performansı ve basitleştirilmiş mimari için yapılan ödün genellikle buna değerdir. Güncellemeler için nihai tutarlılığı sağlamak amacıyla arka plan işlerini kullanın.
Pratik Uygulama Örneği
Anti-pattern ile önerilen yaklaşımı karşılaştıran pratik bir örneğe bakalım. Anti-pattern durumunda veritabanı şeması şu şekilde görünebilir:
-- ANTI-PATTERN: Paylaşılan Şema / Dağıtılmış Monolit
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID, -- Kullanıcı tablosuna doğrudan yabancı anahtar
status VARCHAR(20)
);
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE
);
-- Bu join, servisleri sıkı bir şekilde birbirine bağlar
SELECT o.id, u.email
FROM orders o
JOIN users u ON o.user_id = u.id;
Şimdi, verinin normalleştirme dışı yapıldığı ve sahipliğin net olduğu geliştirilmiş yaklaşımı ele alalım:
-- EN İYİ UYGULAMA: Normalleştirme Dışı ve Sahip Olunan Veri
CREATE TABLE orders (
id UUID PRIMARY KEY,
user_id UUID, -- Referans için ID saklanır, doğrudan arama için değil
user_email VARCHAR(255), -- Hızlı okumalar için normalleştirme dışı
status VARCHAR(20)
);
-- Kullanıcı e-postasını güncellediğinde bir olay yayınlanır
CREATE TABLE user_audit_log (
user_id UUID,
old_email VARCHAR(255),
new_email VARCHAR(255),
timestamp TIMESTAMP
);
Sonuç
Dağıtılmış monolitlerden ve aşırı normalizasyondan kaçınmak sadece teknik doğrulukla ilgili değildir; mimari disiplinle de ilgilidir. Sıkı veri sınırlarını zorlayarak ve normalleştirme yoluyla ağ gecikmesinin gerçeğini kabul ederek, mikroservislerinizin bağımsız olarak ölçeklenmesini ve zincirleme hatalardan korkmadan dağıtılmasını sağlarsınız. Unutmayın, amaç tek bir makine için en verimli veritabanına sahip olmak değil, dağıtılmış bir dünya için en dayanıklı sistemi oluşturmaktır. Mevcut veri erişim desenlerinizi denetleyerek başlayın, sıkı bağımlılıkları belirleyin ve nihai tutarlılığı ve gevşek bağımlılığı önceliklendiren bir modele yavaş yavaş geçiş yapın.