Now, we will talk about what the Outbox Pattern is and how I used it in my Hospital Information System project.

The project I mentioned is this: Hospital Information System

First of all, we need to understand in which cases this pattern is useful. Let's imagine you have a scenario where incoming data must be reliable and consistent; furthermore, you don't have any problem with latency. So, the Outbox Pattern is your new friend.

It guarantees that database updates and message publishing happen atomically by storing messages in a local "Outbox" table within the same database transaction, which are then published asynchronously.

How I Used It

In my case, when a patient is discharged, I have to make sure that the Billing Service runs and creates a bill for this patient using their data, with no toleration for missing a record due to network failures.

Another case is when a doctor places a Lab Order. In this scenario, we must guarantee that the Lab Service is notified the moment an order is created.

By using the Outbox Pattern in DoctorLabOrderService.placeOrder(), we save the lab order and the LabOrderPlacedEvent in a single atomic transaction. This prevents the "Dual Write" problem where data is saved in the database but the message fails to send to the broker. This ensures that even if the message broker is temporarily down, the order will eventually be synchronized with the Lab Service, maintaining a reliable workflow for patient diagnostics.

The Trade-offs

However, every pattern comes with some trade-offs:

Increased Complexity

You now have to manage an additional database table (the Outbox) and write extra logic for a background "Publisher" service that handles the message delivery. It is more code to maintain compared to a simple message send.

Polling Overhead & Latency

Since we use a @Scheduled task to check for new messages every 5 seconds, there is a natural delay before the message reaches the next service. Also, the database is constantly being queried for "PENDING" messages, which adds a small but continuous load on your database.

Idempotency Requirement

There is a small risk that a message is successfully sent to Kafka, but the system crashes before it can mark the message as "PUBLISHED" in the database. In this case, the message will be sent again. This means all your consumer services (like Billing or Lab) must be idempotent; they must be able to handle receiving the same message twice without creating duplicate records.