2 puan yazan GN⁺ 2023-12-18 | 1 yorum | WhatsApp'ta paylaş
  • İlişkisel CRUD modeli depolama yapısını iyi gösterir, ancak iş süreçlerinin üzerinin kolayca örtülmesine yol açar; bu da sistemde gerçekte ne olduğunu izlemeyi zorlaştırır
  • Event Sourcing, her işlemin ardından oluşan değiştirilemez olayları bir Event Stream içinde bırakır ve daha sonra karar alma sürecinde mevcut durumu belirlemek için bu liste okunur
  • Modelleme, önce olayları bulup ardından kullanıcının davranış niyetini ifade eden komutları (command) ve iş kurallarını bağlayarak süreci anlama akışıyla ilerler
  • Mevcut ilişkisel veride olay adaylarını ararken durum sütunlarına, tarih sütunlarına, nullable olup olmadığına ve 1:N ilişkilere bakılır; ancak yalnızca durum değerlerinden tam geçmişin yeniden kurulabileceğini varsaymak risklidir
  • Yalnızca son durumun kaldığı veriyi taşırken geçmiş olayları zorla yeniden kurgulamak yerine, Order Imported gibi açık bir import olayıyla başlamak ve güvenli bir ortamda tekrar tekrar doğrulama yapmak daha gerçekçidir

CRUD verisinden olay merkezli modele bakmak

  • İlişkisel veri modeli hangi verilerin saklandığını gösterir, ancak sistem içinde neler yaşandığını ve süreçlerin nasıl etkileştiğini anlamayı zorlaştırır
  • Mevcut CRUD yaklaşımı verinin üzerine yazarak önemli iş bilgisinin kaybolmasına neden olabilir
  • Event Sourcing, depolama boyutundan çok bilgi kalitesini önceler ve her işlemden sonra ortaya çıkan olguları olay olarak saklar

Event Sourcing’in temel modeli

  • Olay, zaten gerçekleşmiş bir şeye dair olgudur ve bir kez kaydedildikten sonra değiştirilemeyen veridir
  • Event Stream, tek bir kayıt için meydana gelen her şeyi sırayla içeren listedir
  • Geçmiş olaylar değiştirilemez, ancak sona yeni bir olay eklenerek önceki bir hata düzeltilebilir
  • Karar alma sırasında olay listesi okunur ve doğrulanır; mevcut durum ile sonraki adım buna göre belirlenir

Süreç modelleme sırası

  • Modelleme önce olay keşfiyle başlar
  • Ardından komutlar (command) bulunur ve hangi eylemi gerçekleştirme niyetini taşıdığı tanımlanır
  • Son olarak iş kuralları düzenlenir
  • Olaylar, teknik ekip ile iş tarafının süreci birlikte anlaması için merkez eksen görevi görür
  • Alberto Brandolini’nin EventStorming yaklaşımında olduğu gibi olaylar, komutlar ve kurallar birlikte görülerek süreç anlaşılabilir

Mevcut ilişkisel veride olay adaylarını bulmak

  • 1. Durum sütunlarına bakmak

    • status sütununun değerleri, verinin yaşam döngüsündeki aşamaları yansıtabilir
    • Bir sipariş initiated, shipped, paid gibi durumlara sahipse bunlar sırasıyla Order Initiated, Order Shipped, Order Paid olay adayları olabilir
    • Ancak durum değerleri, iş sürecini düzleştiren bir yorum olabilir; bu yüzden eksiksiz oldukları varsayılmamalıdır
    • Olay adlarını Order Created, Order Updated, Order Deleted gibi CRUD işlemleri üzerinden vermekten kaçınılmalıdır
    • State Obsession kaçınılması gereken bir yaklaşım olarak gösterilir
  • 2. Tarih sütunlarını incelemek

    • Tarih sütunları, süreç yaşam döngüsünde önemli gerçekleşme anlarını gösterebilir
    • CreatedDate ve ModifiedDate çok bilgi vermezken, ShipmentDate, DeliveryDate, OrderPlacementDate daha iyi ipuçları sunar
    • Örnekler:
      • ShipmentDate, Order Shipped olayının eklenmesi için bir ipucu olabilir
      • OrderPlacementDate, Order Initiated yerine Order Placed adının daha iyi olabileceğini düşündürür
      • DeliveryDate, Order Delivered olayına ihtiyaç olabileceğini gösterir
    • Bu ipuçları, gerçek iş süreciyle örtüşmesi için alan uzmanlarıyla doğrulanmalıdır
  • 3. Sütunların nullable olup olmadığını analiz etmek

    • non-nullable sütunlar her zaman sağlanması gereken verilerdir
    • nullable sütunlar daha sonra başka bir işlemde sağlanan ya da isteğe bağlı değerler olabilir
    • Ordering Process içinde zorunlu bir sütunsa, ilgili veri ilk Order Initiated olayına da dahil edilmelidir
    • Tek bir olay türü her zaman akışın başlangıç noktası değildir; başlangıç olayı birden fazla olabilir
  • 4. 1:N ilişkisi fazla olan tabloları bulmak

    • Akış sınırlarını bulmak için önce 1:N ilişkisi çok olan tablolara bakılabilir
    • Çok sayıda “one” ilişkisine sahip tablolar, akış türü adayı olabilir
    • Verinin birbirinden bağımsız olarak var olup olamayacağı da mantıksal olarak değerlendirilmelidir
      • shipment, order’dan ayrı bir süreç olabilir
      • order line’ın order olmadan var olması zordur
    • Sınırları tartışma süreci, daha fazla olay keşfetmeye ve süreci daha iyi anlamaya yardımcı olabilir

Migrasyon sırasında sahte olaylar üretmemek

  • İlişkisel veri düzleşmiş son durum olduğu için, yalnızca bu duruma bakıp gerçekte yaşanmış ayrıntılı geçmiş olayları geriye dönük tahmin etmeye çalışmak başarısız ya da hatalı olabilir
  • Geçmişe ait küçük parçalı olayları zorla üretmek yerine, mevcut durumun tamamını ve yorumlama kodunu içeren Order Imported olayını açıkça sağlamak daha iyidir
  • Import olayı, verinin sisteme hangi yolla girdiğini net biçimde gösterir ve sorun çözme ile teşhis açısından önemli olabilir

Prototiple doğrulamak

  • Migrasyon, güvenli bir ortamda prototip olarak denenmeli ve modelin gerçekte nasıl çalıştığı doğrulanmalıdır
  • Sonuçlar beklenen değerlerle karşılaştırılırken yinelemeli biçimde düzeltmeler yapılmalıdır
  • Acele etmeden, mevcut bilgiyi kaybetmeden ve bu bilgiyi kullanarak sonraki modeli iyileştiren bir yaklaşım gereklidir
  • İlişkisel veriden belge tabanlı yapıya geçişe dair genel strateji, General strategy for migrating relational data to document-based yazısıyla da bağlantılıdır

1 yorum

 
GN⁺ 2023-12-18
Hacker News yorumları
  • 2c: Uygulamanın başka bölümlerinde de PostgreSQL gerekiyorsa, event verilerini de PostgreSQL + FOSS raporlama araçlarında (Apache Superset, Metabase vb.) saklayıp kabaca 2TB'a kadar dayanmak iyi olur
    Ondan sonra 2TB'ın tamamını çevrim içi tutmanız gerekip gerekmediğine, yoksa gün/saat bazında özetlerin yeterli olup olmadığına karar edebilirsiniz. İkincisiyse PostgreSQL ile devam etmek gayet yeterli olur[1]
    Bir müşteri 10TB+ ölçekte saniyede 1.500 event, kayıt başına 600 bayt (indekslemeden önce günde 80GB) işliyor; yalnızca 2 günlük ayrıntılı veriyi çevrim içi tutuyor, geri kalanını özetliyor ve ayrıntıları S3'e taşıyıp Athena SQL ile sorgulamaya devam ediyor[2]
    Müşteriye yönelik raporlama portalı dahil toplam maliyet 2 bin doların altında; AWS RDS çoklu AZ otomatik failover (db.m7g.2xlarge) üzerinde hem ekleme hem de raporlama sorguları çalışıyor ve yük %2'nin altında. İş ekibi grafikleri ve çizelgeleri kendisi oluşturduğu için 1 mühendis ayda 5 saatten azını bakıma harcıyor
    Tescilli araçlar kullanınca birkaç grafik “hazır” gelebiliyor, ama pgsql kullanınca veri tek yerde oluyor; öğrenilecek sistem tek, çevrim içi tutulacak/çoğaltılacak/yedeklenecek/kurtarılacak sistem tek, güvenliği sağlanacak/ölçeklenecek sistem tek, yönetilecek satıcı tek ve bu sistemi bilen milyonlarca mühendis var
    Preset veya Metabase gibi sistemlerde 12 grafik oluşturmak bir saat sürer ve bunu teknik olmayan kişiler de yapabilir
    Not: Önyargılıyım, ama 20 yılı aşkın süredir veritabanı ve raporlama sistemlerinin ortaya çıkıp kaybolduğunu gördüm; eski güzel PostgreSQL ise her yıl daha iyi hale geliyor
    https://instances.vantage.sh/aws/rds/db.m7g.2xlarge?region=u...
    [1] Gerçekten gerekirse ek ölçekleme için PostgreSQL uyumlu sistemler de var. Aurora 3~5 kat, TimescaleDB 10 kat, CitusDB 10 kat+ civarında ölçeklenebilir. Her birinin biraz standart dışına çıkma bedeli var, bu yüzden gerçekten ihtiyaç doğmadan önermem
    [2] Müşteri raporlama panosunun 1 saniyenin altında yanıt vermesi gerekiyor; bunu PostgreSQL indekslenmiş özet tabloları sorgulayarak sağlıyor. Athena ise paralel taramayla yaklaşık 1~2 saniyede yanıt veriyor

    • Bu yönde gidilecekse “zaman yolculuğu”, “üzerine yazılmış durumu geri alma”, “geçmiş event'leri yeniden yorumlama” gerektiğinde de yalnızca audit log yeterli olabiliyor
      Kaydetmeden önce verinin snapshot'ını tutup, belirli bir event sırasını tanımlayan/toplayan betikler bulundurup, ardından bir kişinin incelemesiyle yeni mantığın etkisini toplu şekilde geriye dönük uygulamak yeterli olur
      https://django-simple-history.readthedocs.io/en/latest/ gibi araçlar audit tabloları oluşturmak için yarı güvenilir ve basit bir çözüm; doğrudan veritabanı erişimini de denetlemek gerekiyorsa Postgres trigger'ları eklenebilir
      Teoride event sourcing'i seviyorum, ama pratikte yeni CRUD akışları eklemek veya erken-orta aşama startup'ların beklenmedik durumlarda sıkça yapmak zorunda kaldığı müdahaleleri/hotfix'leri hızlı ve güvenilir biçimde yayımlamak için gereken boilerplate çok fazla
      Ödeme işleme rayları uygulamak gibi bir iş değilse event sourcing doğru seçim olmayabilir
      https://news.ycombinator.com/item?id=17817375 (2018) içinde de event sourcing'in dezavantajları üzerine iyi bir sohbet var
    • Bu yorum yazının kendisinden daha değerli
      PostgreSQL'in tek sorunu, ekleme tarafında ilginç ölçeklenebilirlik sorunları olması. Genelde event kaynağı ile DB arasına bir kuyruk koymayı öneririm
    • Genel olarak {id:uuid,created_at:timestamptz,data:jsonb} tanımına sahip bir tablo kullanmak gibi mi, merak ediyorum
      Özellikle event yapıları çeşitliyse ve event tanımları zamanla değişiyorsa JSONB indeks özelliklerinden düzgün yararlanmak zorlaşıyor
      Sanırım bu belgeyi daha iyi öğrenmem gerekiyor: https://www.postgresql.org/docs/current/datatype-json.html#J...
    • Böyle bir sistemi kurmak için ayrıntılı bir yazıyı nerede bulabileceğimi merak ediyorum
  • Geçmişte ekipte event sourcing’i ciddi şekilde değerlendirdiğimiz olmuştu; bana problem arayan bir çözüm gibi görünmüştü.
    Bizde de çalışabilirdi ama faydaları hemen net değildi; yeni bir yaklaşım benimsemenin getireceği riskler ve deneme-yanılma, proje ya da şirket için en iyisi gibi görünmediğinden sonunda vazgeçtik.
    Öğrenme fırsatını kaçırmış araç odaklı bir karar da olabilir, ama arkamızdan bir tilki kovalamıyorken o tavşan deliğine girmediğime pişman değilim.

    • İyi çalışan, sıkıcı ve geleneksel sistemler; yapacak işi olmayan ve CV’sine yazacak şeyler parlatan şişkin mühendislik ekipleri için tehdittir.
      Bu çözümün çözdüğü “problem” tam olarak bu.
    • Örneğin zamansal veritabanları finansal veriler için epey mantıklı.
      Ama çoğu durumda normal bir veritabanı kullanıp geçmiş değişiklik kayıtlarını yardımcı tablolarda tutmak yeterli olur. Böylece ana veritabanı bir tür materialized view gibi davranır.
    • SQL’de saklanan verilerin neredeyse tamamı aslında doküman veritabanlarına daha uygun olurdu, ama kimse o tarafa alışık olmadığı için aynı şekilde devam ediliyor.
      Büyük bir şikâyetim yok; yanlış bir tercih olduğunu da düşünmüyorum, ama veri modeli değişikliklerini ele alma biçiminde sorun çıkıyor.
      Bence çoğu veri saklama yöntemi, günümüzde yazılımın geliştirilme biçimine yetişemedi; event’ler ve queue’lar gibi şeyler de gereken işlevleri mevcut sistemlerin üzerine ekleyerek oluşturmanın sonucu.
      Bugün pek çok veri ilişkisi, birden fazla servis arasında, yani veritabanının dışında gerçekleşiyor. Çünkü birçok kuruluşun modern IT ortamı böyle görünüyor.
      İşin farklı ekiplerini destekleyen dahili master data var ve işleri basitleştirmek için 300’den fazla IT sistemi ve uygulamasıyla etkileşime giriyor.
      Microservices kullanınca iş mantığını ve veri modelini temiz tutmak daha kolay, ama bunun karşılığında event’leri, queue’ları, veri durumunu ve bağımlı depoları da yönetmek gerekiyor; şu anda bu fazlasıyla karmaşık.
      SQL’i seviyorum, ama açıkçası bugün kurduğumuz sistemlerin çoğu SQLite’a koyulsa neredeyse yeterli olur gibi geliyor.
  • Bu tartışmalarda eksik kalan nokta, event-driven architecture’ın ne zaman uygun olduğudur.
    Kısaca söylemek gerekirse, müşteri bir şey yaptıysa ve yanıt bekliyorsa bu event-driven değil, basbayağı request/response’tur.
    Event-driven yaklaşım, bant dışı bir şey gerçekleştiğinde söz konusudur. Örneğin GitHub’a kod push’layıp bir build’in tetiklenmesi gibi.
    Bu örnekte güncellenmiş kodu görmek için sayfayı yenilemek request/response’tur, ama queue’ya alınan CI build’i event-driven’dır.
    Umarım yardımcı olur.

    • O kadar basit değil. Request-response olup olmaması, event sourcing ya da event-driven architecture seçmenin ölçütü değildir.
      Event sourcing veya event-driven yapıda da request-response, inline, blocking, döngüsel akışlar kurabilirsiniz.
      Tersine, event sourcing ya da event-driven olmadan da worker, queue, actor, multithreading gibi yöntemlerle asenkron yapıları gayet iyi kurabilirsiniz.
  • Domain event’leri modellemek, domain uzmanlarıyla çözmeye çalıştığınız problemi anlatmak için faydalıdır; çözümü planlarken dokümantasyonda bırakmak da doğru seviye olabilir.
    Uzun süre yaşayan durum makineleri için audit trail sağlayan bir sistemi gerçekten uygulamak istiyorsanız, Temporal.io veya durable functions gibi şeyleri kullanmanız muhtemelen daha iyi olur.
    Bu araçlar dahili kalıcılıkta event sourcing kullanır; işlevleri orkestre eden kod (workflows) ile gerçek dünyayla etkileşen koda (activities) farklı kısıtlar ekleyerek sizi yinelenen işlemleri ayıklama ve idempotency üzerine düşünmeye zorlayan bir programlama modeli sunar.

    • Durable functions’ta observability eksikliği sorunu var.
      Bu sorunu aşmanın yollarına dair öneriler duymak isterim.
  • Kavram ilginç, ama yazı nasıl çalıştığını iyi açıklayamıyor
    Mevcut durumun olay akışından verimli biçimde nasıl yeniden oluşturulduğunu ve olay akışının veritabanında nasıl modellendiğini merak ediyorum

    • Yazarın birkaç sunumu var
      https://www.youtube.com/watch?v=gG6DGmYKk4I
      https://www.youtube.com/watch?v=jnDchr5eabI
      https://www.youtube.com/watch?v=ArcypYS5XBQ
      https://www.youtube.com/watch?v=uODSwR2CIV4
      GitHub’da örnekleri de yönetiyor
      https://github.com/oskardudycz/EventSourcing.NetCore
      https://github.com/oskardudycz/EventSourcing.NodeJS
      https://github.com/oskardudycz/EventSourcing.JVM
    • İki yöntem var
      Birincisi, bu kullanım için tasarlanmış bir veritabanı kullanmak. Google BigQuery, Amazon Redshift, ClickHouse vb. var
      Mevcut tüm veriler özünde bir tür agregasyondur. Başka bir deyişle, olay veritabanı üzerinde yapılan bir group-by sorgusu gibidir
      Olaylar varsa, agregasyon sorgusuyla mevcut durumu ya da geçmiş bir durumu teknik olarak yeniden oluşturabilirsiniz; bu yüzden mantıklı
      İkincisi, ilişkisel deponun adını değiştirip olay sisteminin yanında duran bir önbellek katmanı demek
      İşlevsel olarak aynı şeydir, ama her şeyi olay güdümlü yapmak gerektiğine takıntılı kişilerde alarm zilleri çaldırmaz
      Yazıda tarif edilen mimari gerçekten var. Ancak son derece karmaşık olduğu için, onu kullanan servisler genellikle çok hedefli işler yapar. Google Analytics, Datadog, Splunk gibi şeyleri düşünün
    • Tek bir “mevcut durum” yok. Bu düşünce biçimi, her şeyi tek bir DB’de merkezileştirmekten kaynaklanıyor
      Farklı gereksinimlere göre farklı sistemlerde farklı durumlar oluşturabilirsiniz
      Bir alışveriş sistemi yaptığınızı varsayalım; satın almalar ve müşteriler olduğunda, bir servis olayları okuyup finansal amaçlar için ilişkisel tablolar oluşturabilir
      Başka bir servis olayları okuyup müşteri verileri için bir anahtar-değer deposu oluşturabilir, üçüncü bir servis de ürün araması için bir OpenSearch servisi çalıştırabilir
      Olay akışı bir listedir. Kafka gibi amaca uygun bir şey kullanırsanız, birden çok liste; yani topic’ler, partition’lar vb. olur
    • Değişimin çok olduğu ve yolun ortasında neler yaşandığını görmeye değecek kadar verinin ilginç olduğu belirli akışlarda kullanmak daha mantıklı
      Ama bu da ilişkisel model içinde çözülebilir
  • Bu, yukarıdan aşağıya vs aşağıdan yukarıya, ya da özel amaçlı vs genel amaçlı farkı
    Yukarıdan aşağıya yaklaşım, iş alanından başlayıp uygulamayı mevcut teknolojiler, araçlar ve tedarikçilerin üzerine eşlemektir
    Aşağıdan yukarıya yaklaşım ise mevcut teknolojiler, araçlar ve tedarikçilerden başlayıp bunları bir araya getirerek çalışan bir çözüm üretmektir
    Özel amaçlı tarafta DDD, CQRS/ES, Sagas, TBUI (görev tabanlı/güdümlü UI), GraphQL, cebirsel veri tipleri vb. var
    Genel amaçlı tarafta RDBMS, CRUD, REST, ACID işlemleri, CDC, genel amaçlı yönetici UI’ları, no-code/low-code, sınırlı/genel amaçlı tipler vb. var

  • Ben eski usul, iyi ilişkisel veriyi kullanmaya devam edeceğim

    • Güzel, işe yaramadığı noktaya kadar öyle yapabilirsiniz. Burada söylenen, vidada çekiç kullanmayın
  • Olay tabanlı mimariye katılıyorum, ama bu yazı ana fikri aktarmakta zorlanıyor gibi
    Veri ilişkileri ile iş eylemleri arasındaki farka odaklanırdım
    Eylemler ve iş faaliyetleri açısından düşünmeye başladığınızda, operasyonel ilişkisel veri depolarından uzaklaşma hareketi çok daha belirgin hale geliyor

    • Soyut düzeyde olaylar da ilişkiler olarak modellenebilir
  • Event sourcing’in birçok iyi özelliği var, bu yüzden ilginç
    Ama yine de ilişkilere ihtiyaç yok mu? Varsa bu ilişkiler nasıl uygulanıyor?
    Cevap “hepsi uygulama katmanı kodunda örtük olarak duruyor” ise bunu kabul etmek zor
    Yine de ilişkileri sorgulamak, ilişki görünümlerini güncel tutmak ya da buna benzer bir şey gerekiyor
    İlişkiler kalıcılık modelinin merkezinde olmasa da sorun değil; ama veri katmanının bir yerinde uygulanmaları gerekir, burada bundan söz edildiğini göremiyorum
    Firestore’da da aynı sorun var. Herkes ilişkileri bir şekilde ele alıyor, ama sonuçta ölçeklenemeyen spagetti uygulama koduna dönüşüyor

    • Event sourcing sistemlerinde olay akışı okuma modeline projekte edilir; okuma modeli ilişkisel, zaman serisi vb. birden fazla türde olabilir
      Fonksiyonel programlamaya aşinaysanız, bu özünde bir olay akışını tek bir duruma katlayan fold işlemiyle aynıdır
      Geçmişte event sourcing sistemleriyle çalışmış biri olarak, açıkça saklanan olay geçmişinin avantajı var; ama karmaşıklığı da epey artırıyor
      Okuma modelinin pratikte nasıl oluşturulacağı, model sürümlerinin nasıl yönetileceği, okuma modelinin snapshot’ının tutulup tutulmayacağı gibi sorunlar ortaya çıkıyor
      Deneyimime göre, bu kalıbın uygulandığı bağlamların çoğunda ek karmaşıklık buna değmedi
  • Gereken şey bir komut kuyruğu. Komut olayı, domain olayı değildir