The Outbox Pattern: One of My Previous Articles
The Outbox Pattern: One of My Previous Articles
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
The type, aggregatetype, and aggregateid columns provide metadata
information about the event or message. These are useful to process
your event within Apache Kafka or to allow event consumers to filter
the event they want to handle.
OK, I already explained the left side of the graphic in a previous post.
Let’s focus on the message relay service. It gets the message from the
outbox table and sends it to the message broker.
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
But Debezium provides a much better way to set up your message
relay service. It doesn’t require you to write any code, and it doesn’t
need to poll the outbox table.
Setting up Debezium
In this example, I want to use a PostgreSQL database. The Debezium
connector takes a snapshot of the database when you start it for the
first time. After that is done, it automatically streams all changes to
an Apache Kafka instance. You can do that for all the tables in your
database, and the changes in each table get streamed to their own
topic. To be able to monitor the changes in your PostgreSQL
database, you need to install a decoder plugin on your PostgreSQL
server.
Or, if you just want to give it a try, you can use the docker example
images provided by the Debezium team. That’s what I’m using for this
article. The following docker-compose.yaml file provides the
required configuration to start docker containers for a Zookeeper, an
Apache Kafka, a PostgreSQL database, and Kafka Connect instance.
version: '2'
services:
zookeeper:
container_name: zookeeper
image: debezium/zookeeper:${DEBEZIUM_VERSION}
ports:
- 2181:2181
- 2888:2888
- 3888:3888
kafka:
container_name: kafka
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
image: debezium/kafka:${DEBEZIUM_VERSION}
ports:
- 9092:9092
links:
- zookeeper
environment:
- ZOOKEEPER_CONNECT=zookeeper:2181
postgres:
container_name: postgres
image: debezium/example-postgres:${DEBEZIUM_VERSION}
ports:
- 5432:5432
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
connect:
container_name: connect
image: debezium/connect:${DEBEZIUM_VERSION}
ports:
- 8083:8083
links:
- kafka
- postgres
environment:
- BOOTSTRAP_SERVERS=kafka:9092
- GROUP_ID=1
- CONFIG_STORAGE_TOPIC=my_connect_configs
- OFFSET_STORAGE_TOPIC=my_connect_offsets
- STATUS_STORAGE_TOPIC=my_connect_statuses
docker-compose up -d
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
The connector connects your database with Apache Kafka. It’s
responsible for getting the changes from your database and
publishing an event for each of them to Apache Kafka. You can add a
new connector by sending a POST request to connectors endpoint of
your Kafka Connect instance. If you want to implement your own
event transformation and routing, this is the only part you need.
So, let’s take a look at a simple connector and outbox event router
configuration that connects as the user postgres to the bookstore
database on localhost:5432.
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
outbox pattern. One of the main ideas of this pattern is that you only
expose 1 table as part of your API. In this example, that table is called
outboxevent, and it’s part of the store schema. By setting the
table.whitelistattribute to store.outboxevent, you can tell the
connector to only monitor that table.
{
"schema":
{
"type": "struct",
"fields":
[
{
"type": "struct",
"fields":
[
{
"type": "string",
"optional": false,
"name": "io.debezium.data.Uuid",
"version": 1,
"field": "id",
},
{
"type": "string",
"optional": false,
"field": "aggregatetype",
},
{ "type": "string", "optional": false, "field": "aggregateid" },
{ "type": "string", "optional": false, "field": "type" },
{ "type": "string", "optional": false, "field": "payload" },
],
"optional": true,
"name": "localhost.store.outboxevent.Value",
"field": "before",
},
{
"type": "struct",
"fields":
[
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
[
{
"type": "string",
"optional": false,
"name": "io.debezium.data.Uuid",
"version": 1,
"field": "id",
},
{
"type": "string",
"optional": false,
"field": "aggregatetype",
},
{ "type": "string", "optional": false, "field": "aggregateid" },
{ "type": "string", "optional": false, "field": "type" },
{ "type": "string", "optional": false, "field": "payload" },
],
"optional": true,
"name": "localhost.store.outboxevent.Value",
"field": "after",
},
{
"type": "struct",
"fields":
[
{ "type": "string", "optional": true, "field": "version" },
{ "type": "string", "optional": true, "field": "connector" },
{ "type": "string", "optional": false, "field": "name" },
{ "type": "string", "optional": false, "field": "db" },
{ "type": "int64", "optional": true, "field": "ts_usec" },
{ "type": "int64", "optional": true, "field": "txId" },
{ "type": "int64", "optional": true, "field": "lsn" },
{ "type": "string", "optional": true, "field": "schema" },
{ "type": "string", "optional": true, "field": "table" },
{
"type": "boolean",
"optional": true,
"default": false,
"field": "snapshot",
},
{
"type": "boolean",
"optional": true,
"field": "last_snapshot_record",
},
{ "type": "int64", "optional": true, "field": "xmin" },
],
"optional": false,
"name": "io.debezium.connector.postgresql.Source",
"field": "source",
},
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
into this:
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
{"schema":{"type":"string","optional":false},"payload":"1"}
{
"schema":
{
"type": "struct",
"fields":
[
{ "type": "string", "optional": false, "field": "payload" },
{ "type": "string", "optional": false, "field": "eventType" },
],
"optional": false,
},
"payload":
{
"payload": '{"id": 1, "title": "Hibernate Tips - More than 70 solutions to common
Hibernate problems", "chapters": [{"id": 2, "content": "How to map natural IDs"},
{"id": 3, "content": "How to map a bidirectional one-to-one association"}]}',
"eventType": "CREATE",
},
}
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
configuration options, please take a look at the Debezium
documentation.
When you use this connector, you will see that Debezium now publishes
the events to the bookstore.events topic and that the event contains the
additional id field.
www.thoughts-on-java.org
Outbox Pattern with CDC & Debezium
{
"schema":
{
"type": "struct",
"fields":
[
{ "type": "string", "optional": false, "field": "payload" },
{ "type": "string", "optional": false, "field": "eventType" },
{ "type": "string", "optional": false, "field": "id" },
],
"optional": false,
},
"payload":
{
"payload": '{"id": 16, "title": "Hibernate Tips - More than 70 solutions to
common Hibernate problems", "chapters": [{"id": 17, "content": "How to map
natural IDs"}, {"id": 18, "content": "How to map a bidirectional one-to-one
association"}]}',
"eventType": "CREATE",
"id": "16",
},
}
Conclusion
We have used the outbox pattern to update the database and publish
events in Apache Kafka. As you have seen, Debezium provides you a set
of connectors and transformers that you just need to configure to
publish an event whenever you write a new record to the outbox table.
That makes the implementation of the outbox pattern pretty simple.
www.thoughts-on-java.org