İçgörülere dön

6GEN / İçgörü / Software Architecture

Servis Sınırını Veri Sahipliğiyle Çizmek

Modüler platformlarda servis sınırlarını deployment topolojisinden önce veri sahipliği, yaşam döngüsü ve açık entegrasyon sözleşmeleri üzerinden belirlemek.

Yayınlandı

  • modularity
  • architecture
  • data-ownership

Bir sistemi modüllere ayırmak kolaydır. Klasörler, paketler, process’ler ve repository’ler kısa sürede çoğaltılabilir. Zor olan, bir değişikliğin hangi sınırın içinde kalacağını belirlemektir.

Bu karar çoğu zaman organizasyon şemasından, teknoloji tercihinden veya deployment kolaylığından başlatılır. Oysa daha dayanıklı bir başlangıç noktası vardır: Veriyi kim oluşturuyor, kim değiştirebiliyor ve yaşam döngüsünden kim sorumlu?

Servis sınırı, bu sorulara verilen cevabın çalıştırılabilir hâlidir. Bir process’in nerede ayağa kalktığından daha fazlasını; hangi iş kurallarının birlikte değiştiğini, hangi state’in tek bir otorite tarafından korunduğunu ve diğer modüllerin bu state’e hangi sözleşmeyle ulaşabildiğini anlatır.

Deployment sınırı, domain sınırı değildir

İki modülün farklı container’larda çalışması onları otomatik olarak bağımsız yapmaz. Her ikisi aynı tabloları güncelliyor, birbirinin iç modelini biliyor veya bir değişiklik için eş zamanlı release gerektiriyorsa aralarındaki ağ mesafesi yalnızca coupling’i daha pahalı hâle getirir.

Tersi de mümkündür. Aynı uygulama process’i içinde çalışan iki modül; ayrı sorumluluklara, açık arayüzlere ve birbirine sızmayan veri modellerine sahipse anlamlı bir domain sınırı oluşturabilir. Bu nedenle üç kavramı birbirinden ayırmak gerekir:

SınırCevapladığı soruTek başına garanti etmediği şey
Kod sınırıHangi davranış hangi modülde?Veri bağımsızlığı
Deployment sınırıHangi parça ayrı yayınlanıyor?Domain bağımsızlığı
Domain sınırıHangi karar ve state kime ait?Ayrı process gerekliliği

İyi bir mimaride bu sınırlar bilinçli biçimde hizalanabilir; fakat aynı olmak zorunda değildir. Bir bounded context ayrı servis olabilir, modüler monolit içinde kalabilir ya da operasyonel ihtiyaç oluşana kadar aynı deployable birimde yaşayabilir.

Veri sahipliği sınırı görünür kılar

Bir veri kümesinin sahibi, onu kullanan her modül değildir. Sahip; verinin anlamını tanımlayan, geçerli durumlarını koruyan ve yaşam döngüsünü yöneten domain’dir.

Örneğin bir görev modülü kartın durumunu in_progress değerinden completed değerine taşıyabiliyorsa bu geçişin kuralları da görev domain’ine aittir. Raporlama modülü aynı durumu okuyabilir; automation modülü geçişten haberdar olabilir; mesajlaşma modülü bildirim üretebilir. Fakat bu ihtiyaçlar diğer modüllere kart tablosunu doğrudan değiştirme hakkı vermez.

Ownership şu dört soruda somutlaşır:

  1. Kaydı hangi domain oluşturur?
  2. Geçerli state ve invariant’ları kim tanımlar?
  3. Değişiklik komutunu kim kabul eder veya reddeder?
  4. Silme, arşivleme ve retention kararını kim yönetir?

Bu soruların cevapları farklı modüllere dağılıyorsa ortada yalnızca paylaşılmış veri değil, paylaşılmış karar yetkisi vardır. Sorun çoğunlukla tam burada başlar.

Shared data neden coupling üretir?

Ortak veritabanı her durumda yanlış değildir. Özellikle küçük ekiplerde tek bir fiziksel database operasyonel olarak doğru tercih olabilir. Risk, fiziksel altyapının ortak olmasından değil, schema sahipliğinin belirsizleşmesinden doğar.

Bir servis başka bir domain’in tablolarına doğrudan eriştiğinde o domain’in iç modelini fiilen kendi contract’ı yapar. Kolon adı değişikliği, yeni bir invariant veya veri taşıma işlemi artık yerel karar değildir. Üstelik bu bağımlılık API sözleşmesi gibi görünür, versiyonlanabilir ya da kolayca gözlemlenebilir olmayabilir.

Bu tür coupling birkaç biçimde ortaya çıkar:

  • Bir modülün başka bir modülün tablosuna doğrudan yazması.
  • Aynı tablolar için birden fazla migration sahibinin bulunması.
  • Bir domain’in iş kuralının başka bir serviste SQL sorgusu olarak tekrar edilmesi.
  • Join kolaylığı uğruna iç kimliklerin ve tablo yapısının kalıcı entegrasyon arayüzüne dönüşmesi.
  • Tek bir değişiklik için bağımsız görünen servislerin birlikte deploy edilmesi gerekmesi.

Çözüm mutlaka “her servise ayrı database instance” değildir. Aynı database sunucusunda ayrı schema’lar, açık repository sınırları veya modüler monolit içindeki erişim kuralları da sahipliği koruyabilir. Esas ölçüt şudur: Bir domain iç modelini değiştirdiğinde, diğerleri yalnızca ilan edilmiş contract etkileniyorsa değişmek zorunda kalmalıdır.

Flowbusy’den doğrulanan engineering bağlamı

Flowbusy, görev yönetimi, automation, CRM ve mesajlaşma gibi birden fazla iş alanını aynı platform deneyiminde bir araya getiriyor. Repository yapısı bu alanları tek bir model altında eritmek yerine ayrı uygulama ve servis yüzeyleri olarak kuruyor.

Kaynakta doğrulanabilen ayrım kısaca şöyle:

  • Task API; workspace, board, list, card ve bunların akış bağlantılarına ait modeli barındırıyor.
  • Automation servisi; flow version, trigger, step, connection ve run state’ini kendi schema ve repository katmanında yönetiyor. Worker da bu run kayıtlarını aynı domain içinde işliyor.
  • CRM servisi; workspace bağlamıyla birlikte party, CRM entity, team ve activity modellerini ayrı bir iş alanında tutuyor.
  • Chat API; conversation ve message yaşam döngüsünü kendi modeliyle yönetiyor ve PostgreSQL kullanan ayrı bir repository katmanına sahip.

Web uygulamasındaki servis istemcileri task, automation ve CRM için farklı servis adreslerine bağlanıyor. Mesajlaşma tarafında ise Meta adaptörü gelen WhatsApp webhook payload’ını ortak bir request modeline dönüştürüp açık bir servis çağrısıyla Chat API’ye iletiyor.

Burada önemli olan kullanılan ürünlerin listesi değil, ayrım biçimidir: Her alan kendi kelime dağarcığına ve state’ine sahipken platform bunları açık iletişim yüzeyleriyle bir araya getiriyor.

Bu gözlemden çıkarılabilecek genel tasarım ilkesi şudur: Aynı kullanıcı yolculuğuna katılmak, aynı veri modeline sahip olmak anlamına gelmez. Bir CRM aktivitesinin görev kartıyla ilişkilendirilmesi gerekebilir; bu, CRM’nin kart tablosuna veya task servisinin activity tablosuna sahip olması gerektiğini göstermez.

Flowbusy kaynakları database-per-service, katı bounded context uygulaması veya tüm entegrasyonların event-driven olduğu yönünde bir garanti vermiyor. Bu nedenle örnek, kanıtlanmış process ve domain ayrımlarıyla sınırlıdır; geri kalan değerlendirmeler genel mühendislik çıkarımlarıdır.

Her domain ayrı servis olmalı mı?

Hayır. Domain sınırı ile servis sayısını eşitlemek, modülerliği ağ topolojisine indirger.

Ayrı servis; bağımsız ölçekleme, farklı availability hedefleri, güvenlik izolasyonu, ayrı release ritmi veya belirgin ekip sahipliği gibi somut bir avantaj sağladığında anlamlıdır. Bu avantajlardan hiçbiri yoksa ağ çağrıları, dağıtık hata kipleri, gözlemlenebilirlik ihtiyacı ve operasyon yükü gereksiz maliyete dönüşebilir.

Bir sınırı ayrı deployable servis yapmak için şu sinyaller birlikte değerlendirilebilir:

  • Domain diğerlerinden farklı bir hızda veya yönde değişiyor mu?
  • Yük profili bağımsız ölçekleme gerektiriyor mu?
  • Hata izolasyonu kullanıcı deneyiminde gerçek bir fark yaratıyor mu?
  • Veriye erişim veya regülasyon farklı bir güvenlik sınırı gerektiriyor mu?
  • Ekip, contract ve operasyon sorumluluğunu gerçekten üstlenebiliyor mu?

Bu sinyaller zayıfsa modüler monolit çoğu zaman daha dürüst bir başlangıçtır. Kod ve veri sahipliği yine açık tutulur; fiziksel ayrım, maliyetini karşılayacak ihtiyaç doğduğunda yapılır. Böylece gelecekte servis çıkarabilmek için bugünden dağıtık sistem işletmek gerekmez.

Başka domain’in verisine contract üzerinden ulaşmak

Bir modül başka bir domain’in verisine ihtiyaç duyduğunda ilk refleks tablo erişimi olmamalıdır. İhtiyaç, açık bir entegrasyon contract’ına çevrilmelidir.

Domain A state'i
      |
      v
Sahip servis -> API / event / message -> Tüketen modül
      ^                                      |
      |----------- açık contract ------------|

Contract’ın biçimi ihtiyacın doğasına bağlıdır:

  • API, tüketenin güncel cevaba hemen ihtiyaç duyduğu komut ve sorgular için uygundur.
  • Event, başka modüllerin gerçekleşmiş bir değişime tepki vermesi gerektiğinde yararlıdır.
  • Message veya job, işin asenkron yürütülmesi ve retry edilmesi gerektiğinde tercih edilebilir.
  • Yerel modül arayüzü, aynı process içindeki domain’ler için ağ maliyeti olmadan aynı açıklığı sağlayabilir.

Bu seçeneklerin hiçbiri kendiliğinden iyi mimari üretmez. Contract’ın semantiği, hata davranışı, idempotency beklentisi, versiyonlama yaklaşımı ve gözlemlenebilirliği tanımlanmamışsa yalnızca coupling’in biçimi değişir.

Örneğin bir automation akışı bir CRM kaydını değiştirecekse “CRM tablosunu güncelle” düşük seviyeli bir entegrasyondur. “Bu fırsatı kazanıldı olarak işaretle” ise karar yetkisini CRM domain’inde bırakan bir komuttur. İlkinde tüketen, verinin yapısını bilir; ikincisinde yalnızca iş niyetini ifade eder.

Veri kopyalamak da sahipliği devretmez. Arama indeksi, raporlama modeli veya local cache başka bir domain’in verisini taşıyabilir. Bu kopya türetilmiş veridir: Kaynak domain otorite olmaya devam eder, gecikme beklentisi açıklanır ve yeniden oluşturma yolu tanımlanır.

Sınırı değerlendirmek için kısa bir kontrol listesi

Bir modül veya servis sınırı çizmeden önce aşağıdaki sorular birlikte cevaplanmalıdır:

  • Bu veriyi kim oluşturuyor ve anlamını kim tanımlıyor?
  • State’in geçerli geçişlerini ve yaşam döngüsünü kim yönetiyor?
  • Hangi invariant’lar tek transaction içinde korunmak zorunda?
  • Başka bir modül bu storage’a doğrudan erişiyor mu?
  • İç schema değişikliği ilan edilmiş bir contract olmadan tüketenleri bozuyor mu?
  • Tüketen güncel cevaba mı, gerçekleşmiş bir değişimin bildirimine mi ihtiyaç duyuyor?
  • Verinin kopyası varsa otorite kaynak ve tutarlılık beklentisi açık mı?
  • Sınır bağımsız test, release, ölçekleme veya hata izolasyonu sağlıyor mu?
  • Ayrı servis olmanın operasyon maliyetini karşılayan somut bir neden var mı?

Bu sorular tek bir “doğru servis boyutu” üretmez. Fakat kararın teknoloji modasından değil, ownership ve değişim maliyetinden türemesini sağlar.

Bağımsızlık, sahiplik ile başlar

Modüler mimarinin değeri parça sayısında değil, değişimin etkisini sınırlayabilmesindedir. Bunun için deployment topolojisinden önce veri sahipliği, karar yetkisi ve entegrasyon sözleşmeleri görünür olmalıdır.

6GEN’in Independent modules. Connected systems. yaklaşımındaki “independent”, yalnızca kodun farklı klasörlerde veya process’lerde bulunması değildir. Her modülün kendi state’ini koruyabilmesi, iç modelini kontrollü biçimde değiştirebilmesi ve diğer modüllerle ilan edilmiş contract’lar üzerinden bağ kurabilmesidir.

Bağlantıların güçlü olması için sınırların silikleşmesi gerekmez. Tam tersine, güvenilir bir bütün ancak hangi verinin kime ait olduğu bilindiğinde kurulabilir.