The Transactional Outbox is a way to ensure that 2 systems are in sync without having to use a distributed transaction between those systems. A simple example is storing the order of a customer in the database, and sending an email to confirm the order.
If we implement this naively, we could do this:
@Component
@Transactional
public class CompleteOrder {
private final OrderRepository orderRepository;
private final MailSender mailSender;
public CompleteOrder(OrderRepository orderRepository, MailSender mailSender) {
this.orderRepository = orderRepository;
this.mailSender = mailSender;
}
public void execute(CompleteOrderParameters parameters) {
Order order = createOrder(parameters);
this.orderRepository.save(order);
this.mailSender.notifyOrderRegistered(order);
}
}
The CompleteOrder
class is a use case that stores the order and sends the email.
However, what if things go wrong? If the mail provider is down, the mail is never sent to the customer.
What is worse is that the transaction will be rolled back and the user gets an error.
It is not the customer’s fault that the mail server is not there.
We should retry sending the email a few minutes later when the mail server is back up and running.
With the Transactional Outbox pattern, we can avoid this problem by storing the fact that we should do some external action (send an email, put a message on a queue, etc.) first.
Then, an asynchronous process can look at the database to know what still needs to happen, and can do that whenever there is time. If the external system is not available, the task can be retried later until it succeeds.