2020-05-26 18:04:08 +02:00
[id="outbox-quarkus-extension"]
2020-01-14 21:53:46 +01:00
= Outbox Quarkus Extension
2020-05-26 16:47:32 +02:00
2020-01-14 21:53:46 +01:00
:toc:
:toc-placement: macro
:linkattrs:
:icons: font
:source-highlighter: highlight.js
toc::[]
== Overview
2021-11-22 10:54:24 +01:00
This extension is inspired by the xref:transformations/outbox-event-router.adoc[Outbox Event Router] single message transformation (SMT).
2020-05-26 18:39:29 +02:00
As discussed in the blog post link:/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/[Reliable Microservices Data Exchange with the Outbox Pattern], microservices often need to exchange information with one another and an excellent way to deal with that is using the Outbox pattern combined with {prodname}'s Outbox Event Router SMT.
2020-01-14 21:53:46 +01:00
2020-01-15 11:28:00 +01:00
The following image shows the overall architecture of this pattern:
image:outbox_pattern.png[Outbox Pattern]
2020-05-26 18:39:29 +02:00
The Outbox extension's goal is to provide a https://quarkus.io/[Quarkus] application with a reusable, highly configurable component that facilitates the use of the Outbox pattern paired with {prodname}'s CDC connector pipeline to reliably and asynchronously share data with any consumer of said data.
2020-01-14 21:53:46 +01:00
== Getting Started
2020-05-26 18:39:29 +02:00
In order to start using the {prodname} Outbox Quarkus extension, the extension needs to be added as a part of the Quarkus application as follows:
2020-01-14 21:53:46 +01:00
[source,xml,subs="verbatim,attributes"]
----
<dependency>
2020-07-18 00:30:43 +02:00
<groupId>io.debezium</groupId>
2021-06-14 17:20:25 +02:00
<artifactId>debezium-quarkus-outbox</artifactId>
2020-01-14 21:53:46 +01:00
<version>{debezium-version}</version>
</dependency>
----
The extension provides the application with the `io.debezium.outbox.quarkus.ExportedEvent` interface.
It's expected that an application class will implement this interface and that the event will be emitted using the `javax.enterprise.event.Event` class.
[NOTE]
====
The `ExportedEvent` interface is parameterized to allow the application to designate the Java types used by several attributes emitted by the event.
It's important that for a given Quarkus application, *all* implementations of the `ExportedEvent` interface must use the same parameter types or a build failure will be thrown since all events will use the same underlying database table.
====
== Example
2021-08-02 21:06:21 +02:00
The following example illustrates an implementation of the `ExportedEvent` interface representing an order that has been created:
2020-01-17 18:58:30 +01:00
.OrderCreatedEvent.java
2020-01-14 21:53:46 +01:00
[source,java,indent=0]
----
public class OrderCreatedEvent implements ExportedEvent<String, JsonNode> {
private static final String TYPE = "Order";
private static final String EVENT_TYPE = "OrderCreated";
private final long orderId;
private final JsonNode jsonNode;
private final Instant timestamp;
public OrderCreatedEvent(Instant createdAt, Order order) {
this.orderId = order.getId();
this.timestamp = createdAt;
this.jsonNode = convertToJson(order);
}
@Override
public String getAggregateId() {
2021-08-02 21:06:21 +02:00
return String.valueOf(orderId);
2020-01-14 21:53:46 +01:00
}
@Override
public String getAggregateType() {
return TYPE;
}
@Override
public JsonNode getPayload() {
return jsonNode;
}
@Override
public String getType() {
return EVENT_TYPE;
}
@Override
public Instant getTimestamp() {
return timestamp;
}
2021-12-10 21:09:10 +01:00
@Override
public Map<String, Object> getAdditionalFieldValues() {
// no additional fields
return Collections.emptyMap();
}
2020-01-14 21:53:46 +01:00
}
----
The following example illustrates an `OrderService` that emits the `OrderCreatedEvent`:
2020-01-17 18:58:30 +01:00
.OrderService.java
2020-01-14 21:53:46 +01:00
[source,java,indent=0]
----
@ApplicationScoped
public class OrderService {
2021-12-09 11:32:22 +01:00
@Inject
OrderRepository orderRepository;
2020-01-14 21:53:46 +01:00
@Inject
Event<ExportedEvent<?, ?>> event;
@Transactional
public Order addOrder(Order order) {
order = orderRepository.save(order);
event.fire(new OrderCreatedEvent(Instant.now(), order));
return order;
}
}
----
2020-01-15 11:28:00 +01:00
When the application code fires the event by calling `Event#fire()`, the Outbox extension will be notified that the event occurred and persists the contents of the event into an outbox event table within the scope of the current transaction.
2020-05-26 18:39:29 +02:00
The {prodname} CDC connector in conjunction with the Outbox Event Router will be monitoring this table and will be responsible for relaying that data using CDC events.
2020-01-14 21:53:46 +01:00
2021-10-27 14:16:10 +02:00
To see a full end-to-end demo, the https://github.com/debezium/debezium-examples/tree/main/outbox[Outbox] example illustrates two Quarkus microservice applications using the outbox pattern to share data between them when orders are placed or cancelled.
2020-01-14 21:53:46 +01:00
2023-04-12 17:04:07 +02:00
[id=reactive-outbox]
== Reactive Variant
2023-04-19 01:07:56 +02:00
If your application uses reactive datasources, or Hibernate Reactive, you must use a slightly different configuration to add the extension to your application.
2023-04-12 17:04:07 +02:00
2023-04-19 01:07:56 +02:00
For example, use the following configuration to import the reactive variant of the extension:
2023-04-12 17:04:07 +02:00
[source,xml,subs="verbatim,attributes"]
----
<dependency>
<groupId>io.debezium</groupId>
<artifactId>debezium-quarkus-outbox-reactive</artifactId>
<version>{debezium-version}</version>
</dependency>
----
2023-04-19 01:07:56 +02:00
The reactive extension provides the application with the same `io.debezium.outbox.quarkus.ExportedEvent` interface as the non-reactive variant, but it also provides the `DebeziumOutboxHandler` class.
2023-04-19 17:23:45 +02:00
Injecting the `DebeziumOutboxHandler` bean into an application provides a method for quickly persisting an `ExportedEvent` to the outbox table.
2023-04-19 01:07:56 +02:00
The following example shows an invocation that uses the same `ExportedEvent` as in the earlier example:
2023-04-12 17:04:07 +02:00
.OrderService.java
[source,java,indent=0]
----
@ApplicationScoped
public class OrderService {
@Inject
DebeziumOutboxHandler debeziumOutboxHandler;
@Inject
OrderRepository orderRepository;
@Inject
Event<ExportedEvent<?, ?>> event;
@ReactiveTransactional
public Uni<Order> addOrder(Order order) {
return orderRepository.persistAndFlush(order)
.call(debeziumOutboxHandler.persistToOutbox(new OrderCreatedEvent(Instant.now(), order)));
}
}
----
[NOTE]
====
2023-04-19 01:07:56 +02:00
The `persistToOutbox` method returns a Uni; therefore, it must be observed by a subscriber ito receive the result.
For more information about using the Mutiny library to build reactive applications, see the link:https://quarkus.io/guides/mutiny-primer[Quarkus Mutiny primer].
2023-04-12 17:04:07 +02:00
====
2021-12-10 21:09:10 +01:00
[id=additional-field-mappings]
== Additional Field mappings
2022-01-21 11:37:43 +01:00
The {prodname} Outbox SMT can be configured to read additional fields and emit those field values either as event headers, or as part of the event value.
2021-12-10 21:09:10 +01:00
2022-01-21 11:37:43 +01:00
In order to pass additional field mappings to be saved by the Quarkus Outbox extension,
2021-12-10 21:09:10 +01:00
the configuration property `quarkus.debezium-outbox.additional-fields` must be specified in the `application.properties`.
This configuration property is a comma-separated list of additional field definitions that will be added to the Outbox entity mapping and passed by the application's implementation of the `ExportedEvent` interface.
Each entry in this comma-separated list must follow this format:
[source]
----
<field-name>:<field-java-type>[:<field-column-definition>[:<field-jpa-attribute-converter>]]
----
The pattern indicates that the field's name and java-type are required while the column definition and JPA attribute converter are optional.
However, please note that if you wish to specify a JPA attribute converter then the column definition must be specified.
2022-01-21 11:37:43 +01:00
The following example shows how to define an additional field called `customer_name` that is represented in Java as a `String` and which should be stored in the outbox table as a `VARCHAR(100)` column.
2021-12-10 21:09:10 +01:00
This example also shows a JPA Attribute converter defined that forces the storage of the string to upper-case.
.application.properties
[source,properties,indent=0]
----
quarkus.debezium-outbox.additional-fields=customer_name:string:varchar(100):example.UpperCase
----
2022-01-21 11:37:43 +01:00
Once the field(s) are configured in the application's `.properties` file, the application's code needs to provide the corresponding values through its exported events.
2021-12-10 21:09:10 +01:00
In order to do this, the application class that extends the `ExportedEvent` needs to override the method called `getAdditionalFieldValues()` and return a `Map` of the additional field names and values.
In the following example, we show how to specify the `customer_name` field with a value of `Acme Goods`.
2022-01-21 11:37:43 +01:00
Using our `OrderCreatedEvent` from the example section above, we've extended the event:
2021-12-10 21:09:10 +01:00
.OrderCreatedEvent.java
[source,java,indent=0]
----
public class OrderCreatedEvent implements ExportedEvent<String, JsonNode> {
...
@Override
public Map<String, Object> getAdditionalFieldValues() {
return Collections.singletonMap("customer_name", "Acme Goods");
}
}
----
[NOTE]
====
Additional field mappings do allow specifying a JPA attribute converter per field.
In this example, we defined `example.UpperCase` that will convert any supplied string-value to upper-case prior to insertion.
A JPA attribute converter allows decoupling this type of behavior from the call site, allowing reuse of a common behavior.
====
With the configuration in the application's `.properties` file and updating of `OrderCreateedEvent` to provide these additional fields and values,
the {prodname} Outbox SMT now can access these additional field values and place them in the emitted event.
2020-01-14 21:53:46 +01:00
== Configuration
The Outbox extension can be configured by setting options in the Quarkus `application.properties` file.
The extension works out-of-the-box with a default configuration, but this configuration may not be ideal for every situation.
=== Build time configuration options
2020-05-26 19:17:56 +02:00
[cols="65%a,>12%a,>23%"]
|===
2020-01-14 21:53:46 +01:00
|Configuration property
|Type
|Default
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-table-name]]<<quarkus-debezium-outbox-table-name,`+quarkus.debezium-outbox.table-name+`>>::
2020-01-14 21:53:46 +01:00
The table name to be used when creating the outbox table.
|string
|OutboxEvent
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-id-name]]<<quarkus-debezium-outbox-id-name,`+quarkus.debezium-outbox.id.name+`>>::
2020-02-12 18:24:28 +01:00
The column name for the event id column. +
2021-08-02 19:14:39 +02:00
for example, `uuid`
2020-01-14 21:53:46 +01:00
|string
|`id`
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-id-column-definition]]<<quarkus-debezium-outbox-id-column-definition,`+quarkus.debezium-outbox.id.column-definition+`>>::
2020-02-12 18:24:28 +01:00
The database-specific column definition for the event id column. +
2021-08-02 19:14:39 +02:00
for example, `uuid not null`
2020-01-23 22:24:01 +01:00
|string
2020-03-11 14:57:46 +01:00
|`UUID NOT NULL`
2020-01-23 22:24:01 +01:00
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-aggregate-id-name]]<<quarkus-debezium-outbox-aggregate-id-name,`+quarkus.debezium-outbox.aggregate-id.name+`>>::
2020-01-14 21:53:46 +01:00
The column name for the event key column.
|string
|`aggregateid`
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-aggregate-id-column-definition]]<<quarkus-debezium-outbox-aggregate-id-column-definition,`+quarkus.debezium-outbox.aggregate-id.column-definition+`>>::
2020-02-12 18:24:28 +01:00
The database-specific column definition for the aggregate id. +
2021-08-02 19:14:39 +02:00
for example, `varchar(50) not null`
2020-01-23 22:24:01 +01:00
|string
2020-03-11 14:57:46 +01:00
|`VARCHAR(255) NOT NULL`
2020-01-23 22:24:01 +01:00
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-aggregate-id-converter]]<<quarkus-debezium-outbox-aggregate-id-converter,`+quarkus.debezium-outbox.aggregate-id.converter+`>>::
2020-02-12 18:24:28 +01:00
The JPA AttributeConverter for the event key column. +
2021-08-02 19:14:39 +02:00
for example, `com.company.TheAttributeConverter`
2020-01-23 22:24:01 +01:00
|string
|
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-aggregate-type-name]]<<quarkus-debezium-outbox-aggregate-type-name,`+quarkus.debezium-outbox.aggregate-type.name+`>>::
2020-01-23 22:24:01 +01:00
The column name for the event aggregate type column.
|string
|`aggregatetype`
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-aggregate-type-column-definition]]<<quarkus-debezium-outbox-aggregate-type-column-definition,`+quarkus.debezium-outbox.aggregate-type.column-definition+`>>::
2020-02-12 18:24:28 +01:00
The database-specific column definition for the aggregate type. +
2021-08-02 19:14:39 +02:00
for example, `varchar(15) not null`
2020-01-23 22:24:01 +01:00
|string
2020-03-11 14:57:46 +01:00
|`VARCHAR(255) NOT NULL`
2020-01-23 22:24:01 +01:00
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-aggregate-type-converter]]<<quarkus-debezium-outbox-aggregate-type-converter,`+quarkus.debezium-outbox.aggregate-type.converter+`>>::
2020-02-12 18:24:28 +01:00
The JPA AttributeConverter for the event aggregate type column. +
2021-08-02 19:14:39 +02:00
for example, `com.company.TheAttributeConverter`
2020-01-23 22:24:01 +01:00
|string
|
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-type-name]]<<quarkus-debezium-outbox-type-name,`+quarkus.debezium-outbox.type.name+`>>::
2020-01-14 21:53:46 +01:00
The column name for the event type column.
|string
|`type`
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-type-column-definition]]<<quarkus-debezium-outbox-type-column-definition,`+quarkus.debezium-outbox.type.column-definition+`>>::
2020-02-12 18:24:28 +01:00
The database-specific column definition for the event type. +
2021-08-02 19:14:39 +02:00
for example, `varchar(50) not null`
2020-01-23 22:24:01 +01:00
|string
2020-03-11 14:57:46 +01:00
|`VARCHAR(255) NOT NULL`
2020-01-23 22:24:01 +01:00
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-type-converter]]<<quarkus-debezium-outbox-type-converter,`+quarkus.debezium-outbox.type.converter+`>>::
2020-02-12 18:24:28 +01:00
The JPA AttributeConverter for the event type column. +
2021-08-02 19:14:39 +02:00
for example, `com.company.TheAttributeConverter`
2020-01-23 22:24:01 +01:00
|string
|
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-timestamp-name]]<<quarkus-debezium-outbox-timestamp-name,`+quarkus.debezium-outbox.timestamp.name+`>>::
2020-01-14 21:53:46 +01:00
The column name for the event timestamp column.
|string
|`timestamp`
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-timestamp-column-definition]]<<quarkus-debezium-outbox-timestamp-column-definition,`+quarkus.debezium-outbox.timestamp.column-definition+`>>::
2020-02-12 18:24:28 +01:00
The database-specific column definition for the event timestamp. +
2021-08-02 19:14:39 +02:00
for example, `timestamp not null`
2020-01-23 22:24:01 +01:00
|string
2020-03-11 14:57:46 +01:00
|`TIMESTAMP NOT NULL`
2020-01-23 22:24:01 +01:00
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-timestamp-converter]]<<quarkus-debezium-outbox-timestamp-converter,`+quarkus.debezium-outbox.timestamp.converter+`>>::
2020-02-12 18:24:28 +01:00
The JPA AttributeConverter for the event timestamp column. +
2021-08-02 19:14:39 +02:00
for example, `com.company.TheAttributeConverter`
2020-01-23 22:24:01 +01:00
|string
|
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-payload-name]]<<quarkus-debezium-outbox-payload-name,`+quarkus.debezium-outbox.payload.name+`>>::
2020-01-14 21:53:46 +01:00
The column name for the event payload column.
|string
|`payload`
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-payload-column-definition]]<<quarkus-debezium-outbox-payload-column-definition,`+quarkus.debezium-outbox.payload.column-definition+`>>::
2020-02-12 18:24:28 +01:00
The database-specific column definition for the event payload. +
2021-08-02 19:14:39 +02:00
for example, `text not null`
2020-01-14 21:53:46 +01:00
|string
2020-03-11 14:57:46 +01:00
|`VARCHAR(8000)`
2020-01-23 22:24:01 +01:00
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-payload-converter]]<<quarkus-debezium-outbox-payload-converter,`+quarkus.debezium-outbox.payload.converter+`>>::
2020-02-12 18:24:28 +01:00
The JPA AttributeConverter for the event payload column. +
2021-08-02 19:14:39 +02:00
for example, `com.company.TheAttributeConverter`
2020-01-23 22:24:01 +01:00
|string
|
2021-12-13 17:07:20 +01:00
|[[quarkus-debezium-outbox-payload-type]]<<quarkus-debezium-outbox-payload-type,`+quarkus.debezium-outbox.payload.type+`>>::
A fully-qualified class name of a Hibernate https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#basic-custom-type[user type] implementation. +
2021-12-11 00:05:26 +01:00
for example, `io.company.types.JsonNodeBinaryType`
|string
|
2022-01-10 17:22:33 +01:00
|[[quarkus-debezium-outbox-tracing-span-name]]<<quarkus-debezium-outbox-tracing-span-name,`+quarkus.debezium-outbox.tracing-span.name+`>>::
2020-12-11 08:47:46 +01:00
The column name for the tracing span context column.
|string
|`tracingspancontext`
2022-01-10 17:22:33 +01:00
|[[quarkus-debezium-outbox-tracing-span-column-definition]]<<quarkus-debezium-outbox-tracing-span-column-definition,`+quarkus.debezium-outbox.tracingspancontext.column-definition+`>>::
The database-specific column definition for the tracing span context column. +
2021-08-02 19:14:39 +02:00
for example, `text not null`
2020-12-11 08:47:46 +01:00
|string
|`VARCHAR(256)`
2021-12-10 21:09:10 +01:00
|[[quarkus-debezium-outbox-additional-fields]]<<quarkus-debezium-outbox-additional-fields,`+quarkus.debezium-outbox.additional-fields+`>>::
A comma-separated list of additional field mappings that will be persisted in the outbox table. +
+
See xref:#additional-field-mappings[additional field mappings] for details on format and usage.
|string
|
2020-05-26 19:17:56 +02:00
|===
2020-01-14 21:53:46 +01:00
[NOTE]
====
The build time configuration defaults will work with the Outbox Event Router SMT out of the box.
When not using the default values, be sure that the SMT configuration matches.
====
=== Runtime configuration options
2020-05-26 19:17:56 +02:00
[cols="65%a,>15%a,>20%"]
|===
2020-01-14 21:53:46 +01:00
|Configuration property
|Type
|Default
2021-08-02 19:14:39 +02:00
|[[quarkus-debezium-outbox-remove-after-insert]]<<quarkus-debezium-outbox-remove-after-insert,`+quarkus.debezium-outbox.remove-after-insert+`>>::
2020-01-14 21:53:46 +01:00
Whether the outbox entry is removed after having been inserted. +
+
2020-05-26 18:39:29 +02:00
_The removal of the entry does not impact the {prodname} connector from being able to emit CDC events.
2020-01-14 21:53:46 +01:00
This is used as a way to keep the table's underlying storage from growing over time._
|boolean
|true
2020-05-26 19:17:56 +02:00
|===
2020-12-11 08:47:46 +01:00
=== Distributed tracing
2023-08-11 17:12:17 +02:00
2020-12-11 08:47:46 +01:00
The extension has support for the distributed tracing.
See link:/documentation/reference/integrations/tracing[tracing documentation] for more details.