• 16.08.2026 04:12:18
  • Admin Admin

Java eğitimi sırasında çoğu zaman atlanan transactional outbox deseni, Spring Boot servislerinde veritabanı yazımı ile Kafka olay yayınını atomik olarak nasıl tasarlayacağınızı gösterir.

Spring Boot’ta Transactional Outbox ile Güvenilir Java Microservices

Java Microservices için çift yazım hatasını somutlaştırın

Outbox satırındaki payload, entity’nin tamamını serialize etmek yerine sürümlü bir entegrasyon sözleşmesi olmalıdır. Örneğin {"eventId":"…","schemaVersion":1,"orderId":"…","total":"42.50","currency":"TRY"} gibi bir belge yayınlayın; lazy ilişki, iç alan veya Hibernate proxy’si sızdırmayın. eventId hem Kafka header’ına hem tüketicinin deduplikasyon tablosuna yazılmalıdır. Bu yaklaşım Java backend geliştirme ekiplerinde şema değişikliklerinin API entity değişikliğiyle istemeden tüketicileri kırmasını önler.

Spring Boot eğitimi: Hibernate ORM ve Spring Data JPA ile atomik kayıt

@Transactional yalnızca Spring proxy’si üzerinden çağrıldığında çalışır. Aynı bean içinden this.place(command) çağrısı yaparsanız proxy atlanır ve annotation etkisiz kalır; bu yaygın hata outbox’ın order kaydından ayrı commit edilmesine yol açabilir. Bunu ArchUnit ile servislerin kendi public transactional metodlarını çağırmasını yasaklayarak veya iş akışını ayrı bean’e taşıyarak denetleyin. Ayrıca testte yalnızca repository mock’lamak yerine Testcontainers PostgreSQL ile rollback senaryosunu doğrulayın: transaction içinde exception atıldığında hem orders hem outbox_event sayısının sıfır kaldığını assert edin.

SKIP LOCKED ile paralel yayıncı ve idempotent Kafka tüketicisi

Bu nedenle tüketici idempotent olmalıdır. PostgreSQL’de olay kimliğini tekil anahtar yapan küçük bir inbox tablosu kullanın ve iş etkisini aynı transaction’da uygulayın:

CREATE TABLE consumed_event (
  event_id UUID PRIMARY KEY,
  consumed_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
);
INSERT INTO consumed_event(event_id) VALUES (?) ON CONFLICT DO NOTHING sonucu 0 satırsa handler iş mantığını çalıştırmamalıdır. Kafka consumer offset’ini commit etmeden önce bu transaction’ı commit edin. Bu tasarımda duplicate mesaj masumdur; ancak aynı aggregate için sıralama gerekiyorsa Kafka key olarak aggregate_id gönderin. Aynı key aynı partition’a gider, fakat producer retry veya yeniden oynatma sırasında farklı aggregate’ler arası global sıralama garantisi beklemeyin.

Outbox gecikmesini ölçün: profil, indeks ve batch ayarı

Uygulama tarafında Java Flight Recorder (JFR) ile publisher pod’unda 5-10 dakikalık kayıt alın; jdk.SocketWrite, jdk.JavaMonitorEnter ve allocation hot spot’larında Kafka beklemesi mi, JSON serileştirme mi, connection-pool kuyruğu mu baskın görün. Örneğin her olay için yeni ObjectMapper üretmek yerine Spring’in singleton mapper’ını enjekte edin ve batch boyutunu ölçerek değiştirin:

@Bean
ObjectMapper objectMapper() {
  return JsonMapper.builder()
      .findAndAddModules()
      .build();
}
Değişiklik öncesi/sonrası karşılaştırmada aynı olay hızıyla outbox_publish_duration p95, JFR allocation rate ve PostgreSQL pg_stat_statements.mean_exec_time değerlerini kaydedin. Batch’i 100’den 1000’e körlemesine çıkarmak kilit süresini ve bir batch’in başarısızlık patlama yarıçapını büyütebilir.

Spring AI ve Spring MCP akışlarında outbox sınırı

Bir java kursu veya java fullstack eğitimi içinde bu konuyu sadece Kafka kurulumu olarak ele almak eksik kalır: istemci arayüzü olayın eventId değerini izleme ekranında göstermeli, backend ise Spring Boot Actuator üzerinden publisher sağlık bilgisini sunmalıdır. Spring Boot eğitimi laboratuvarında GET /actuator/metrics/outbox.oldest.unpublished.seconds sonucunu Grafana paneline bağlayın; spring framework transaction semantiği, hibernate orm flush davranışı ve spring data jpa kilit sorgusunun aynı hata senaryosunda nasıl birleştiği ancak bu uçtan uca gözlemle görünür olur.

Sık Sorulan Sorular

Java backend geliştirme içinde transactional outbox için Kafka zorunlu mu?

Hayır. Outbox tablosundaki publisher HTTP webhook, RabbitMQ veya başka bir broker’a da gönderebilir. Esas kural, iş kaydı ve olay kaydının aynı veritabanı transaction’ında olmasıdır. Hedef sistemin idempotency anahtarı desteklememesi halinde event UUID’sini HTTP Idempotency-Key header’ı olarak iletin ve alıcıda tekil kayıtla saklayın.

Spring Data JPA ile SKIP LOCKED sorgusu neden native query olmalı?

JPQL kilit modları satır kilidi isteyebilir, ancak SKIP LOCKED sözdizimi ve davranışı veritabanına özgüdür. PostgreSQL için native query ile FOR UPDATE SKIP LOCKED kullanıp integration testini Testcontainers PostgreSQL üzerinde çalıştırın; H2 ile geçen test, PostgreSQL kilit davranışını kanıtlamaz.

Spring AI ve Spring MCP işlemlerinde model context protocol çağrıları nasıl idempotent yapılır?

Her araç çağrısı için toolCallId veya uygulamanın ürettiği UUID’yi kalıcı bir komut tablosunda UNIQUE yapın. İlk INSERT başarılıysa işleyin; unique ihlali varsa önceki sonucu döndürün. Model yanıtı yeniden üretse bile aynı kimlikle stok, ödeme veya müşteri kaydı gibi yan etkili komutları ikinci kez çalıştırmayın.

Java eğitimi projelerinde outbox gecikmesi hangi metrikle izlenmeli?

En yararlı alarm, publish edilmemiş en eski kaydın yaşıdır: SELECT EXTRACT(EPOCH FROM now() - min(occurred_at)) FROM outbox_event WHERE published_at IS NULL. Bu metriği publisher hata sayısı ve Kafka producer acknowledgement gecikmesiyle birlikte izleyin; yalnızca kuyruk uzunluğu, düşük trafikte bile eski kalmış tek bir olayı gizleyebilir.

AI / LLM Discovery

Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt