“Sipariş oluşturulduğunda ne oluyor?” sorusuna bir ekipte kimsenin net cevap verememesini izledim. Kod tabanında OrderCreated event’ini sekiz ayrı listener dinliyordu; hangisinin hangi sırada, hangi koşulda çalıştığı tek bir yerden okunamıyordu. Akış kodda değil, kafalarda — hem de eksik — yaşıyordu.
Event-driven mimari bağı gevşetir. Bu yazı, gevşeyen başka bir şey daha olduğunu hatırlatmak için: nedensellik.
Event ne çözer
Bir event yayınlayan kod, onu kimin dinlediğini bilmez. OrderCreated yayınlanır; faturalama, bildirim ve analitik onu ayrı ayrı dinler. Sipariş kodu bu üçünden habersizdir.
Kazanç gerçek: yeni bir tüketici eklemek, üreticiye dokunmadan olur. Bağımsız geliştirme, bağımsız dağıtım, fan-out. Birbirini tanımayan parçalar.
Event neyi gizler
Aynı madalyonun öbür yüzü: bağ gevşedikçe akış görünmez olur.
Doğrudan bir method çağrısında akış kodun kendisidir — tanımına gider, devamını okursunuz. Event’te öyle değil. OrderCreated yayınlandığında ne olduğunu görmek için tüm listener’ları elle bulmanız gerekir. Compiler size yardım etmez: bir method çağrısının kontratını derleyici doğrular, bir event’in kontratını kimse doğrulamaz.
Modüler monolit yazısındaki cümle burada da geçerli — bir network kontratı gibi, bir event kontratı da derleyicinin kör noktasındadır.
Ödenmeden seçilmemeli: iki ön koşul
Event-driven mimariye geçmeden önce iki bedel peşin ödenir.
Schema disiplini. Event bir kontrattır. OrderCreated’in payload’ı değiştiğinde, onu dinleyen sekiz yerin haberi olmaz — ta ki production’da kırılana kadar. Event’ler versiyonlanmalı, alanlar geriye uyumlu eklenmeli, payload bir şema ile doğrulanmalı. Bu disiplin yoksa, gevşek bağ “sessiz kırılma” demektir.
İzlenebilirlik. Bir isteğin sistemden geçişini görebilmek için her event bir correlation ID taşımalı:
event(new OrderCreated(
orderId: $order->id,
correlationId: request()->header('X-Correlation-Id') ?? (string) Str::uuid(),
));
Bu ID log’lara ve sonraki event’lere taşınmazsa, çok-listener’lı bir akışta “bu iş neden çalıştı” sorusunun cevabı yoktur. Tracing, event-driven’ın isteğe bağlı bir eklentisi değil, ön koşuludur.
Laravel’de: in-process event ≠ event-driven mimari
Laravel’in event()/listener mekanizması, senkron çalıştığında aslında düzenlenmiş bir method çağrısıdır — aynı process, aynı transaction, aynı stack trace. Bu güvenli ve izlenebilir; bunu rahatça kullanın.
Event-driven mimarinin getirdiği zorluklar, event bir sınırı geçtiğinde başlar: bir queue’ya, bir message broker’a, başka bir process’e. Listener’a ShouldQueue ekleyip event’i RabbitMQ’ya gönderdiğiniz an, schema ve tracing borcu devreye girer. İkisini birbirine karıştırmayın: in-process listener kullanmak sizi “event-driven mimariye” geçirmez.
Ne zaman event-driven’a geçilir
Event’i gerçekten bir sınırın ötesine taşımak şu durumlarda gerekçeli:
- Gerçek bir fan-out var. Bir olayı, birbirini tanımaması gereken çok sayıda bağımsız tüketici dinliyor.
- Tüketici asenkron olmalı. İş, üreticinin yanıtını bekletmemeli; “kabul edildi” yeterli.
- Tüketiciler bağımsız ölçeklenmeli ya da dağıtılmalı. Bu da bizi mikroservis kararına götürür — aynı ölçülmüş-sinyal eşiği.
Bunlar yoksa, doğrudan bir service çağrısı hem daha okunur hem daha güvenlidir. Tek bir listener’ı olan bir event, sadece kılık değiştirmiş bir method çağrısıdır — üstelik izi sürülmesi daha zor olanı.
Event-driven mimari bağı gevşetir; bunu yaparken nedenselliği de gevşetir. Birincisini istiyorsanız, ikincisinin faturasını — schema ve tracing — peşin ödeyin.
Gevşek bağ bedava değildir; görünmez akışla ödenir.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.