분산 시스템에서 DB 저장과 메시지 발행을 동시에 처리할 때 흔히 발생하는 문제가 있다.
@Transactional
public void createOrder(Order order) {
orderRepository.save(order); // ✅ 성공
kafkaTemplate.send("orders", order); // ❌ 실패하면?
}
DB는 저장됐는데 Kafka 발행이 실패하면? 주문은 생성됐지만 결제 서비스는 이 사실을 모른다. 데이터 정합성이 깨지는 순간이다.
핵심 아이디어는 간단하다. 메시지를 브로커에 직접 보내지 말고 DB에 먼저 저장하는 것이다.
CREATE TABLE outbox_events (
id BIGSERIAL PRIMARY KEY,
aggregate_type VARCHAR(100),
aggregate_id VARCHAR(100),
payload JSONB,
status VARCHAR(20) DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT NOW()
);
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
outboxRepository.save(new OutboxEvent("Order", order.getId(), toJson(order)));
// 같은 트랜잭션 = 둘 다 성공 or 둘 다 롤백
}
별도 스케줄러가 PENDING 상태를 폴링해서 브로커로 발행한다.
@Scheduled(fixedDelay = 100)
public void publish() {
outboxRepository.findByStatus("PENDING").forEach(e -> {
kafkaTemplate.send(e.getType(), e.getPayload());
e.setStatus("PUBLISHED");
});
}