Message-Driven Architecture
Modern yazılım sistemlerinde servisler arasındaki iletişim, uygulamanın ölçeklenebilirliğini, dayanıklılığını ve bakım kolaylığını doğrudan belirleyen kritik bir mimari karardır. Bu derste synchronous (senkron) ve asynchronous (asenkron) iletişim modellerini, mesaj aracılarının (message broker) rolünü, temel mesajlaşma kalıplarını ve mesaj teslim garantilerini derinlemesine inceleyeceğiz.
Synchronous vs Asynchronous Communication
Servisler arası iletişimde iki temel paradigma vardır:
Synchronous (Senkron) İletişim: İstemci bir istek gönderir ve yanıt gelene kadar bloklanır. HTTP/REST, gRPC gibi protokoller bu kategoridedir. Senkron iletişimde istemci, karşı servisin ayakta olduğunu ve makul sürede yanıt vereceğini varsayar.
Client ──HTTP POST──▶ Order Service ──HTTP GET──▶ Inventory Service
(bekle...) (bekle...)
Client ◀──200 OK────── Order Service ◀──200 OK──── Inventory ServiceAvantajları: Basit, anlaşılması kolay, hata yönetimi doğrudan yapılır, işlem sonucu anında bilinir.
Dezavantajları: Temporal coupling (zamansal bağımlılık) — her iki servis de aynı anda ayakta olmalıdır. Bir servis yavaşlarsa veya çökerse, çağıran servis de etkilenir. Zincirleme çağrılarda gecikme birikir (latency amplification).
Asynchronous (Asenkron) İletişim: Gönderen mesajını bir aracıya (broker) bırakır ve yanıt beklemeden işine devam eder. Alıcı, mesajı kendi hızında işler.
Order Service ──msg──▶ [Message Broker] ──msg──▶ Inventory Service
(devam eder) (kuyrukta bekler) (hazır olunca işler)Avantajları: Servisler birbirinden bağımsız çalışır (loose coupling), yük dengeleme doğal olarak gerçekleşir, bir servisin çökmesi diğerini etkilemez (mesajlar kuyrukta bekler), yatay ölçekleme kolaydır.
Dezavantajları: Daha karmaşık altyapı, hata izleme zorlaşır, eventual consistency (nihai tutarlılık) ile yaşamayı öğrenmek gerekir, mesaj sıralaması ve tekrarlı teslim gibi ek sorunlar ortaya çıkar.
Message Broker Nedir?
Message broker, üretici (producer) ve tüketici (consumer) arasında aracı rolü üstlenen bir yazılımdır. Temel sorumlulukları:
Mesaj alımı ve depolama: Üreticiden gelen mesajları kabul eder ve tüketici hazır olana kadar saklar.
Yönlendirme (routing): Mesajları doğru tüketiciye veya kuyruğa iletir.
Teslim garantisi: Mesajların kaybolmamasını, tekrar denenebilmesini sağlar.
Protokol dönüşümü: Farklı protokol konuşan sistemler arasında köprü kurar.
Popüler message broker'lar:
| Broker | Protokol | Kullanım Alanı |
|---|---|---|
| RabbitMQ | AMQP | Geleneksel mesajlaşma, iş kuyrukları, RPC |
| Apache Kafka | Kafka Protocol | Event streaming, log aggregation, yüksek throughput |
| ActiveMQ | JMS, AMQP, STOMP | Enterprise Java entegrasyonu |
| Amazon SQS | HTTP | AWS cloud-native uygulamalar |
| Redis Streams | Redis Protocol | Hafif mesajlaşma, caching ile birlikte |
Pub/Sub vs Point-to-Point
Mesajlaşma sistemlerinde iki temel dağıtım modeli vardır:
Point-to-Point (Noktadan Noktaya): Bir mesaj yalnızca bir tüketici tarafından işlenir. Mesaj bir kuyruğa (queue) gönderilir; birden fazla tüketici dinliyorsa, her mesaj yalnızca birine teslim edilir. Bu model iş dağıtımı (work distribution) için idealdir.
Producer ──▶ [ Queue ] ──▶ Consumer A (msg-1, msg-3)
──▶ Consumer B (msg-2, msg-4)Örnek senaryo: E-ticaret siparişleri bir kuyruğa yazılır, birden fazla worker sipariş işler. Her sipariş yalnızca bir worker tarafından alınır.
Publish/Subscribe (Yayınla/Abone Ol): Bir mesaj, o konuya (topic) abone olan tüm tüketicilere iletilir. Yayıncı (publisher) mesajı bir topic'e gönderir; her abone kendi kopyasını alır.
Publisher ──▶ [ Topic ] ──▶ Subscriber A (tüm mesajlar)
──▶ Subscriber B (tüm mesajlar)
──▶ Subscriber C (tüm mesajlar)Örnek senaryo: Sipariş oluşturulduğunda "OrderCreated" eventi yayınlanır; stok servisi, bildirim servisi ve fatura servisi hepsi bu eventi alıp kendi işlerini yapar.
Hibrit modeller de mevcuttur. Kafka'nın consumer group mekanizması, pub/sub ile point-to-point'i birleştirir: aynı grup içindeki consumer'lar mesajları paylaşırken, farklı gruplar tüm mesajları alır.
Mesaj Teslim Garantileri
Dağıtık sistemlerde mesaj teslimi karmaşık bir problemdir. Üç temel garanti seviyesi vardır:
At-Most-Once (En Fazla Bir Kez): Mesaj en fazla bir kez teslim edilir. Mesaj kaybedilebilir ama asla tekrar teslim edilmez. "Fire and forget" yaklaşımıdır. Üretici mesajı gönderir, onay beklemez.
// At-Most-Once: Mesaj gönder, sonucu önemseme
rabbitTemplate.convertAndSend("queue", message);
// Mesaj kaybolursa kaybolur — tekrar denemesi yokKullanım alanı: Log toplama, metrik gönderme gibi bir miktar veri kaybının kabul edilebilir olduğu senaryolar. Çok hızlıdır çünkü onay mekanizması yoktur.
At-Least-Once (En Az Bir Kez): Mesaj en az bir kez teslim edilir. Mesaj asla kaybolmaz ama birden fazla kez teslim edilebilir. Üretici, broker'dan onay (acknowledgement) alır; alamazsa mesajı tekrar gönderir.
// At-Least-Once: Consumer mesajı işledikten sonra ACK gönderir
@RabbitListener(queues = "orders")
public void handleOrder(Order order, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
processOrder(order); // İşle
channel.basicAck(tag, false); // Onay gönder
// ACK gitmeden consumer çökerse → mesaj tekrar teslim edilir
}Kullanım alanı: Ödeme işlemleri, sipariş oluşturma gibi veri kaybının kabul edilemez olduğu senaryolar. Tüketici tarafının idempotent (tekrarlı çağrıda aynı sonucu veren) olması gerekir.
Exactly-Once (Tam Olarak Bir Kez): Mesaj tam olarak bir kez teslim ve işlenir. En zor garanti seviyesidir ve pratikte tamamen sağlamak çoğu zaman mümkün değildir. Kafka, transaction desteği ile "effectively exactly-once" semantiği sunar — bu, producer ve consumer'ın Kafka transaction'ları içinde çalışmasıyla sağlanır.
// Kafka exactly-once semantics (transaction ile)
@Transactional
public void processAndForward(ConsumerRecord<String, String> record) {
// consume + produce aynı transaction içinde
kafkaTemplate.send("output-topic", transform(record.value()));
// Transaction commit olursa her iki işlem de geçerli
// Rollback olursa ikisi de geri alınır
}İdempotency: Tekrarlı Teslimin Panzehiri
At-least-once garanti modelinde mesajlar birden fazla kez gelebileceğinden, tüketici tarafının idempotent olması kritiktir. İdempotent bir operasyon, kaç kez çalışırsa çalışsın aynı sonucu üretir.
Yaygın idempotency stratejileri:
Unique ID kontrolü: Her mesaja benzersiz bir ID atanır; tüketici, daha önce işlediği ID'leri bir veritabanı tablosunda veya cache'te tutar.
Upsert (Insert or Update): Veritabanı işlemlerinde INSERT yerine UPSERT kullanarak, aynı kaydın tekrar eklenmesini engellersiniz.
Conditional update: Versiyon numarası veya durum kontrolü ile güncelleme yapmak.
// Idempotent consumer örneği
public void handlePayment(PaymentEvent event) {
if (processedEventRepository.existsById(event.getEventId())) {
log.info("Event already processed: {}", event.getEventId());
return; // Zaten işlenmiş, atla
}
paymentService.processPayment(event);
processedEventRepository.save(new ProcessedEvent(event.getEventId()));
}Ne Zaman Asenkron Mesajlaşma Kullanmalı?
Asenkron mesajlaşma her zaman en iyi çözüm değildir. Kullanmanız gereken durumlar:
Uzun süren işlemler: Video dönüştürme, rapor oluşturma, e-posta gönderme
Servisler arası gevşek bağlantı: Bir servisin çökmesinin diğerlerini etkilememesini istediğinizde
Yük tesviyesi (load leveling): Ani trafik artışlarında mesajları kuyrukta biriktirip sabit hızda işleme
Event-driven mimariler: Bir olay gerçekleştiğinde birden fazla servisin tepki vermesi gerektiğinde
Senkron iletişimin daha uygun olduğu durumlar:
Kullanıcının anında yanıt beklediği işlemler (login, bakiye sorgulama)
Basit CRUD operasyonları
Güçlü tutarlılık (strong consistency) gerektiren senaryolar
AI Asistan
Sorularını yanıtlamaya hazır