Course contentsShow
← Databases & SQL
Lesson 1938 of 1,95318. Application Integration PatternsFree lesson

Database-Centric Messaging

Use database tables as message queues for reliable, transactional task processing within the application logic.

In this lesson, you will learn how to leverage your existing relational database as a reliable, transactional message broker to ensure task execution is consistent with your application state.

Why use the Database as a Queue?

In distributed systems, separating your application logic from your messaging infrastructure (like RabbitMQ or Kafka) introduces "dual-write" problems—where the database updates successfully, but the message fails to send, or vice versa. By using a database table as a queue, you wrap your task submission and your domain changes in a single ACID transaction. This ensures that a task is only enqueued if the data change that prompted it is also committed.

The Transactional Inbox/Outbox Pattern

To implement this, create an outbox table that acts as a staging area for tasks. When a worker thread needs to perform a side effect (like sending an email or triggering an external API), it inserts a row into this table alongside the business data change.

BEGIN;
-- Perform domain logic
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- Add the message to the queue
INSERT INTO outbox (task_type, payload, status, created_at)
VALUES ('SEND_EMAIL', '{"to": "user@example.com", "msg": "Transfer complete"}', 'PENDING', NOW());
COMMIT;

Processing the Messages

Once committed, a separate background worker polls the outbox table. To handle concurrency and ensure a task isn't processed twice, use the FOR UPDATE SKIP LOCKED clause. This allows multiple workers to grab unique tasks without blocking each other:

-- Retrieve the next available task and lock it for the current worker
SELECT id, payload 
FROM outbox 
WHERE status = 'PENDING' 
ORDER BY created_at ASC 
LIMIT 1 
FOR UPDATE SKIP LOCKED;

Once the worker processes the task, it updates the status to PROCESSED or deletes the row. Because the locking happens at the row level within the database engine, you eliminate the risk of race conditions or duplicate processing, achieving "at-least-once" delivery without adding external infrastructure.

Key Takeaway: By using your database as a message queue, you guarantee transactional integrity between your application state and your background tasks, effectively treating side effects as part of your database commit.