Giriş: Olay Yönelimli Mimari Neden Kritik?
Olay yönelimli mimari (Event-Driven Architecture - EDA), modern dağıtık sistemlerin bel kemiğini oluşturan bir paradigmadır. Neden mi? Çünkü monolitik uygulamaların aksine, EDA;
- Yüksek ölçeklenebilirlik sağlar: Producer ve consumer'lar bağımsız olarak ölçeklenebilir.
- Zayıf bağlılık (loose coupling) sunar: Servisler birbirlerinin varlığından habersizdir, sadece olayları dinler.
- Gerçek zamanlı veri işleme imkanı verir: Anlık bildirimler, fraud detection, IoT sinyalleri gibi use-case'ler için idealdir.
Ancak, EDA'nın yanlış uygulanması, prodüksiyonda felaketlere yol açabilir. Bu makalede, Spring Boot 3.x ile prodüksiyon seviyesinde bir EDA uygulaması nasıl inşa edilir, adım adım inceleyeceğiz.
1. Temel Kavramlar ve Mimarinin Omurgası
1.1. Event-Driven Mimari Bileşenleri
Bir EDA sisteminde üç ana bileşen bulunur:
- Producer (Olay Üretici): Olayı yayınlayan servis (örn:
OrderService→OrderCreatedEvent). - Event Bus (Olay Aracısı): Olayları taşıyan altyapı (örn: Apache Kafka, RabbitMQ, AWS SNS/SQS).
- Consumer (Olay Tüketicisi): Olayı dinleyen ve işleyen servis (örn:
NotificationService,InventoryService).
// Örnek Event Sınıfı (Immutable, serializable)
public record OrderCreatedEvent(
UUID orderId,
UUID customerId,
List items,
Instant createdAt
) implements Serializable {
public record OrderItem(UUID productId, int quantity) {}
}
Serializable yapmazsanız, Kafka veya RabbitMQ gibi broker'lar olayları deserialize edemez. Ayrıca, record kullanmak immutable ve thread-safe olaylar sağlar, ancak JSON serileştirme için @JsonCreator veya jackson-databind uyumluluğunu kontrol edin.1.2. Event Akış Diyagramı (SVG)
Aşağıda, tipik bir EDA akışını gösteren SVG diyagramı yer alıyor. Bu diyagram, makalenin sonunda detaylı olarak açıklanacak:
<svg viewBox="0 0 800 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#8b5cf6"></polygon>
</marker>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0f172a"></stop>
<stop offset="100%" stop-color="#1e1b4b"></stop>
</linearGradient>
</defs>
<rect width="800" height="400" fill="url(#gradient)"></rect>
<g stroke="#8b5cf6" stroke-width="2" fill="none" marker-end="url(#arrowhead)">
<rect x="50" y="150" width="150" height="100" rx="5" fill="rgba(139, 92, 246, 0.2)"></rect>
<text x="125" y="200" text-anchor="middle" fill="#e5e7eb" font-family="Arial" font-size="14">Order Service
(Producer)</text>
<rect x="300" y="50" width="200" height="300" rx="5" fill="rgba(139, 92, 246, 0.1)"></rect>
<text x="400" y="30" text-anchor="middle" fill="#e5e7eb" font-family="Arial" font-size="16">Event Bus
(Kafka/RabbitMQ)</text>
<rect x="600" y="100" width="150" height="200" rx="5" fill="rgba(139, 92, 246, 0.2)"></rect>
<text x="675" y="120" text-anchor="middle" fill="#e5e7eb" font-family="Arial" font-size="12">Notification
Service</text>
<text x="675" y="150" text-anchor="middle" fill="#e5e7eb" font-family="Arial" font-size="12">(Consumer)</text>
<text x="675" y="180" text-anchor="middle" fill="#e5e7eb" font-family="Arial" font-size="12">Inventory
Service</text>
<text x="675" y="210" text-anchor="middle" fill="#e5e7eb" font-family="Arial" font-size="12">(Consumer)</text>
<path d="M200 200 L300 200"></path>
<path d="M500 120 L600 120"></path>
<path d="M500 180 L600 180"></path>
<path d="M500 240 L600 240"></path>
</g>
</svg>
2. Spring Boot ile Producer Uygulaması
2.1. Gerekli Bağımlılıklar
Spring Boot 3.x ile Kafka kullanmak için pom.xml dosyasına aşağıdaki bağımlılıkları ekleyin:
org.springframework.kafka
spring-kafka
3.1.0
com.fasterxml.jackson.core
jackson-databind
spring-kafka yerine spring-cloud-stream kullanmayı düşünebilirsiniz. Ancak, low-level kontrol istiyorsanız (örn: custom partitioner, retry policies), doğrudan spring-kafka tercih edin. spring-cloud-stream, abstraction sağlar ama esnekliği azaltır.2.2. Kafka Producer Konfigürasyonu
application.yml dosyasında Kafka ayarlarını yapılandırın:
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
retries: 3
acks: all
acks: all kullanarak, producer'ın mesajın tüm replica'lara yazıldığından emin olmasını sağlayın. Bu, veri kaybını önler ama latency artabilir. Trade-off'ı iyi değerlendirin.2.3. Event Yayınlama (Producer Kodları)
OrderService sınıfı, bir sipariş oluşturulduğunda OrderCreatedEvent yayınlayacak:
@Service
@RequiredArgsConstructor
public class OrderService {
private final KafkaTemplate kafkaTemplate;
private final String TOPIC_NAME = "orders";
public UUID createOrder(OrderRequest request) {
UUID orderId = UUID.randomUUID();
OrderCreatedEvent event = new OrderCreatedEvent(
orderId,
request.customerId(),
request.items(),
Instant.now()
);
// Async olarak event yayınla
kafkaTemplate.send(TOPIC_NAME, orderId.toString(), event)
.addCallback(
result -> log.info("Event published: {}", event),
ex -> log.error("Failed to publish event", ex)
);
return orderId;
}
}
KafkaTemplate.send() çağrısı non-blocking olsa da, callback'ler @Async ile çalışmaz. Eğer callback içinde uzun süren işlemler yapacaksanız, @Async veya CompletableFuture kullanın. Aksi halde, thread pool tıkanabilir.3. Spring Boot ile Consumer Uygulaması
3.1. Kafka Consumer Konfigürasyonu
application.yml dosyasında consumer ayarlarını yapılandırın:
spring:
kafka:
consumer:
group-id: notification-service-group
auto-offset-reset: earliest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: "*"
auto-offset-reset: earliest kullanmak, consumer'ın baştan başlamasını sağlar. Prodüksiyonda latest tercih edin, aksi halde eski olaylar tekrar işlenir. Ayrıca, group-id'yi servis bazında benzersiz yapın (örn: notification-service-v1).3.2. Event Dinleme ve İşleme (Consumer Kodları)
NotificationService sınıfı, OrderCreatedEvent'i dinleyecek ve müşteriye bildirim gönderecek:
@Service
@RequiredArgsConstructor
public class NotificationService {
private final EmailSender emailSender;
@KafkaListener(topics = "orders", groupId = "notification-service-group")
public void handleOrderCreated(OrderCreatedEvent event) {
String emailContent = String.format(
"Siparişiniz alındı! Sipariş No: %s",
event.orderId()
);
emailSender.send(
event.customerId(),
"Sipariş Onayı",
emailContent
);
}
}
3.3. Hata Yönetimi ve Retry Mekanizmaları
Consumer'larda hata yönetimi kritiktir. Aşağıdaki gibi bir RetryTemplate kullanarak, transient hatalar için otomatik retry sağlayın:
@Configuration
public class KafkaConsumerConfig {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(1000); // 1 saniye
retryTemplate.setBackOffPolicy(backOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
}
@Service
@RequiredArgsConstructor
public class NotificationService {
private final RetryTemplate retryTemplate;
@KafkaListener(topics = "orders", groupId = "notification-service-group")
public void handleOrderCreated(OrderCreatedEvent event) {
retryTemplate.execute(
context -> {
emailSender.send(
event.customerId(),
"Sipariş Onayı",
"Siparişiniz alındı!"
);
return null;
},
context -> {
log.error("Failed to send email after {} retries", context.getRetryCount());
return null;
}
);
}
}
@RetryableTopic (Spring Kafka 2.7+) ile declarative retry de yapabilirsiniz.4. Event Akışının İzlenmesi ve Observability
4.1. Metrikler ve Logging
EDA sistemlerinde observability hayati önem taşır. Aşağıdaki metrikleri mutlaka izleyin:
- Producer Metrikleri:
record-send-rate,record-error-rate,request-latency-avg. - Consumer Metrikleri:
records-consumed-rate,records-lag-max,poll-interval-avg.
Spring Boot Actuator ile Kafka metriklerini açığa çıkarın:
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
4.2. Distributed Tracing
Olay akışını end-to-end trace etmek için OpenTelemetry veya Spring Cloud Sleuth kullanın:
@KafkaListener(topics = "orders", groupId = "notification-service-group")
public void handleOrderCreated(OrderCreatedEvent event, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
Span span = tracer.nextSpan().name("process-order-event").start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
// İşlemleri trace et
emailSender.send(event.customerId(), "Sipariş Onayı", "...")
} finally {
span.finish();
}
}
traceparent header'ını Kafka mesajlarına eklemeyi unutmayın.5. İleri Düzey Konular ve Prodüksiyon İpuçları
5.1. Event Schema Yönetimi
Olay şemalarını (schema) merkezi bir yerde yönetin. Apache Avro veya JSON Schema kullanarak, backward/forward compatibility sağlayın:
{
"type": "record",
"name": "OrderCreatedEvent",
"fields": [
{"name": "orderId", "type": "string"},
{"name": "customerId", "type": "string"},
{"name": "items", "type": {"type": "array", "items": {
"type": "record",
"name": "OrderItem",
"fields": [
{"name": "productId", "type": "string"},
{"name": "quantity", "type": "int"}
]
}}}
]
}
5.2. Event Sourcing ve CQRS
EDA, Event Sourcing ve CQRS ile mükemmel bir uyum sağlar. Örneğin:
- Event Sourcing: Tüm state değişikliklerini olaylar olarak saklayın.
- CQRS: Okuma ve yazma modellerini ayırın (örn:
OrderWriteServiceveOrderReadService).
// Event Sourcing için basit bir repository
public interface EventStore {
void save(Event event);
List load(UUID aggregateId);
}
// CQRS için ayrı servisler
@Service
public class OrderWriteService {
private final EventStore eventStore;
public void createOrder(OrderRequest request) {
OrderCreatedEvent event = new OrderCreatedEvent(...);
eventStore.save(event);
}
}
@Service
public class OrderReadService {
private final EventStore eventStore;
public Order getOrder(UUID orderId) {
List events = eventStore.load(orderId);
return Order.replay(events);
}
}
5.3. Dead Letter Queue (DLQ) ve Poison Pill Yönetimi
Consumer'larda işlenemeyen olayları DLQ'ya gönderin:
@KafkaListener(topics = "orders.DLQ", groupId = "notification-service-group")
public void handleDeadLetter(OrderCreatedEvent event) {
log.error("Dead letter event: {}", event);
// Manuel müdahale veya retry mekanizması ekleyin
}
// Consumer'da DLQ'ya gönderme
@KafkaListener(topics = "orders", groupId = "notification-service-group")
public void handleOrderCreated(OrderCreatedEvent event) {
try {
emailSender.send(...);
} catch (Exception e) {
kafkaTemplate.send("orders.DLQ", event);
}
}
@DltHandler (Spring Kafka 2.7+) kullanarak DLQ işlemlerini kolaylaştırın.6. Sonuç ve Öneriler
Olay yönelimli mimariler, dağıtık sistemlerin geleceğidir. Ancak, doğru uygulanmadığında karmaşık hatalara ve prodüksiyon felaketlerine yol açabilir. Bu makalede ele aldığımız konuları özetleyelim:
- Producer/Consumer Kodları: Spring Boot ile nasıl sağlam bir EDA sistemi inşa edeceğinizi gördük.
- Hata Yönetimi: Retry mekanizmaları, DLQ ve observability ile sisteminizi dayanıklı hale getirin.
- İleri Düzey Konular: Event sourcing, CQRS ve schema yönetimi ile mimarinizi bir üst seviyeye taşıyın.
Öneriler:
- Kafka yerine RabbitMQ kullanmayı düşünüyorsanız, message durability ve partitioning konularında dikkatli olun.
- Event duplication sorununu çözmek için idempotent consumer'lar tasarlayın.
- Consumer lag'i izlemek için Prometheus + Grafana kullanın.
- Schema evolution için Avro veya Protobuf tercih edin.
Bu sorulara cevap veren bir mimari, prodüksiyonda başarıya ulaşır.
Ek: Event Akış SVG'si (Detaylı Açıklama)
Makalenin başında yer alan SVG diyagramı, tipik bir EDA akışını göstermektedir:
- Producer (Order Service): Sipariş oluşturulduğunda
OrderCreatedEventyayınlar. - Event Bus (Kafka/RabbitMQ): Olayı taşır ve consumer'lara iletir.
- Consumer'lar (Notification/Inventory Service): Olayı dinler ve işler.
Diyagramdaki renk kodları ve geometrik şekiller şu anlamlara gelir:
- Mor tonları: Event akışını temsil eder.
- Oklar: Olayın yönünü gösterir.
- Dikdörtgenler: Servisleri temsil eder.
Bu diyagram, sistem mimarinizi dokümante etmek için de kullanılabilir. SVG formatı, scalable ve editlenebilir olduğu için tercih edilir.
Yorumlar
Bir Yorum Bırakın
Henüz yorum yapılmamış. İlk yorumu siz yapın!