tet123/documentation/modules/ROOT/pages/integrations/outbox.adoc

405 lines
16 KiB
Plaintext
Raw Normal View History

[id="outbox-quarkus-extension"]
= Outbox Quarkus Extension
:toc:
:toc-placement: macro
:linkattrs:
:icons: font
:source-highlighter: highlight.js
toc::[]
== Overview
This extension is inspired by the xref:transformations/outbox-event-router.adoc[Outbox Event Router] single message transformation (SMT).
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.
The following image shows the overall architecture of this pattern:
image:outbox_pattern.png[Outbox Pattern]
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.
== Getting Started
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:
[source,xml,subs="verbatim,attributes"]
----
<dependency>
<groupId>io.debezium</groupId>
2021-06-14 17:20:25 +02:00
<artifactId>debezium-quarkus-outbox</artifactId>
<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:
.OrderCreatedEvent.java
[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);
}
@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;
}
@Override
public Map<String, Object> getAdditionalFieldValues() {
// no additional fields
return Collections.emptyMap();
}
}
----
The following example illustrates an `OrderService` that emits the `OrderCreatedEvent`:
.OrderService.java
[source,java,indent=0]
----
@ApplicationScoped
public class OrderService {
2021-12-09 11:32:22 +01:00
@Inject
OrderRepository orderRepository;
@Inject
Event<ExportedEvent<?, ?>> event;
@Transactional
public Order addOrder(Order order) {
order = orderRepository.save(order);
event.fire(new OrderCreatedEvent(Instant.now(), order));
return order;
}
}
----
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.
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.
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.
[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-19 01:07:56 +02:00
For example, use the following configuration to import the reactive variant of the extension:
[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:
.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].
====
[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.
2022-01-21 11:37:43 +01:00
In order to pass additional field mappings to be saved by the Quarkus Outbox extension,
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.
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.
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:
.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.
== 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
[cols="65%a,>12%a,>23%"]
|===
|Configuration property
|Type
|Default
|[[quarkus-debezium-outbox-table-name]]<<quarkus-debezium-outbox-table-name,`+quarkus.debezium-outbox.table-name+`>>::
The table name to be used when creating the outbox table.
|string
|OutboxEvent
|[[quarkus-debezium-outbox-id-name]]<<quarkus-debezium-outbox-id-name,`+quarkus.debezium-outbox.id.name+`>>::
The column name for the event id column. +
for example, `uuid`
|string
|`id`
|[[quarkus-debezium-outbox-id-column-definition]]<<quarkus-debezium-outbox-id-column-definition,`+quarkus.debezium-outbox.id.column-definition+`>>::
The database-specific column definition for the event id column. +
for example, `uuid not null`
|string
|`UUID NOT NULL`
|[[quarkus-debezium-outbox-aggregate-id-name]]<<quarkus-debezium-outbox-aggregate-id-name,`+quarkus.debezium-outbox.aggregate-id.name+`>>::
The column name for the event key column.
|string
|`aggregateid`
|[[quarkus-debezium-outbox-aggregate-id-column-definition]]<<quarkus-debezium-outbox-aggregate-id-column-definition,`+quarkus.debezium-outbox.aggregate-id.column-definition+`>>::
The database-specific column definition for the aggregate id. +
for example, `varchar(50) not null`
|string
|`VARCHAR(255) NOT NULL`
|[[quarkus-debezium-outbox-aggregate-id-converter]]<<quarkus-debezium-outbox-aggregate-id-converter,`+quarkus.debezium-outbox.aggregate-id.converter+`>>::
The JPA AttributeConverter for the event key column. +
for example, `com.company.TheAttributeConverter`
|string
|
|[[quarkus-debezium-outbox-aggregate-type-name]]<<quarkus-debezium-outbox-aggregate-type-name,`+quarkus.debezium-outbox.aggregate-type.name+`>>::
The column name for the event aggregate type column.
|string
|`aggregatetype`
|[[quarkus-debezium-outbox-aggregate-type-column-definition]]<<quarkus-debezium-outbox-aggregate-type-column-definition,`+quarkus.debezium-outbox.aggregate-type.column-definition+`>>::
The database-specific column definition for the aggregate type. +
for example, `varchar(15) not null`
|string
|`VARCHAR(255) NOT NULL`
|[[quarkus-debezium-outbox-aggregate-type-converter]]<<quarkus-debezium-outbox-aggregate-type-converter,`+quarkus.debezium-outbox.aggregate-type.converter+`>>::
The JPA AttributeConverter for the event aggregate type column. +
for example, `com.company.TheAttributeConverter`
|string
|
|[[quarkus-debezium-outbox-type-name]]<<quarkus-debezium-outbox-type-name,`+quarkus.debezium-outbox.type.name+`>>::
The column name for the event type column.
|string
|`type`
|[[quarkus-debezium-outbox-type-column-definition]]<<quarkus-debezium-outbox-type-column-definition,`+quarkus.debezium-outbox.type.column-definition+`>>::
The database-specific column definition for the event type. +
for example, `varchar(50) not null`
|string
|`VARCHAR(255) NOT NULL`
|[[quarkus-debezium-outbox-type-converter]]<<quarkus-debezium-outbox-type-converter,`+quarkus.debezium-outbox.type.converter+`>>::
The JPA AttributeConverter for the event type column. +
for example, `com.company.TheAttributeConverter`
|string
|
|[[quarkus-debezium-outbox-timestamp-name]]<<quarkus-debezium-outbox-timestamp-name,`+quarkus.debezium-outbox.timestamp.name+`>>::
The column name for the event timestamp column.
|string
|`timestamp`
|[[quarkus-debezium-outbox-timestamp-column-definition]]<<quarkus-debezium-outbox-timestamp-column-definition,`+quarkus.debezium-outbox.timestamp.column-definition+`>>::
The database-specific column definition for the event timestamp. +
for example, `timestamp not null`
|string
|`TIMESTAMP NOT NULL`
|[[quarkus-debezium-outbox-timestamp-converter]]<<quarkus-debezium-outbox-timestamp-converter,`+quarkus.debezium-outbox.timestamp.converter+`>>::
The JPA AttributeConverter for the event timestamp column. +
for example, `com.company.TheAttributeConverter`
|string
|
|[[quarkus-debezium-outbox-payload-name]]<<quarkus-debezium-outbox-payload-name,`+quarkus.debezium-outbox.payload.name+`>>::
The column name for the event payload column.
|string
|`payload`
|[[quarkus-debezium-outbox-payload-column-definition]]<<quarkus-debezium-outbox-payload-column-definition,`+quarkus.debezium-outbox.payload.column-definition+`>>::
The database-specific column definition for the event payload. +
for example, `text not null`
|string
|`VARCHAR(8000)`
|[[quarkus-debezium-outbox-payload-converter]]<<quarkus-debezium-outbox-payload-converter,`+quarkus.debezium-outbox.payload.converter+`>>::
The JPA AttributeConverter for the event payload column. +
for example, `com.company.TheAttributeConverter`
|string
|
|[[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. +
for example, `io.company.types.JsonNodeBinaryType`
|string
|
|[[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`
|[[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. +
for example, `text not null`
2020-12-11 08:47:46 +01:00
|string
|`VARCHAR(256)`
|[[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
|
|===
[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
[cols="65%a,>15%a,>20%"]
|===
|Configuration property
|Type
|Default
|[[quarkus-debezium-outbox-remove-after-insert]]<<quarkus-debezium-outbox-remove-after-insert,`+quarkus.debezium-outbox.remove-after-insert+`>>::
Whether the outbox entry is removed after having been inserted. +
+
_The removal of the entry does not impact the {prodname} connector from being able to emit CDC events.
This is used as a way to keep the table's underlying storage from growing over time._
|boolean
|true
|===
2020-12-11 08:47:46 +01:00
=== Distributed tracing
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.