-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature : Quarkus integration (#320)
* feat: cdi implementation * fix: PR comments * fix: cdi transaction manager instantiation to be transaction aware * Update CdiTransactionManager.java * ref: module name (quarkus) * ref: module name Co-authored-by: Romain Wilbert <romain.wilbert-ext@pole-emploi.fr> (cherry picked from commit 121f27d)
- Loading branch information
1 parent
d5a00d6
commit 4b4891a
Showing
15 changed files
with
616 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
**/.classpath | ||
**/.project | ||
**/.settings | ||
**/.factorypath | ||
/.idea | ||
/*.iml | ||
**/target/** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# transactionoutbox-quarkus | ||
|
||
|
||
Extension for [transaction-outbox-core](../README.md) which integrates CDI's DI and Quarkus transaction management. | ||
|
||
Tested with Quarkus implementation (Arc/Agroal) | ||
|
||
## Installation | ||
|
||
### Stable releases | ||
|
||
The latest stable release is available from Maven Central. | ||
|
||
#### Maven | ||
|
||
```xml | ||
<dependency> | ||
<groupId>com.gruelbox</groupId> | ||
<artifactId>transactionoutbox-quarkus</artifactId> | ||
<version>4.3.281</version> | ||
</dependency> | ||
``` | ||
|
||
#### Gradle | ||
|
||
```groovy | ||
implementation 'com.gruelbox:transactionoutbox-quarkus:4.3.281' | ||
``` | ||
|
||
### Development snapshots | ||
|
||
Maven Central is updated regularly. Alternatively, if you want to stay at the bleeding edge, you can use continuously-delivered releases from [Github Package Repository](https://github.com/gruelbox/transaction-outbox/packages). These can be used from production builds since they will never be deleted. | ||
|
||
#### Maven | ||
|
||
```xml | ||
<repositories> | ||
<repository> | ||
<id>github-transaction-outbox</id> | ||
<name>Gruelbox Github Repository</name> | ||
<url>https://maven.pkg.github.com/gruelbox/transaction-outbox</url> | ||
</repository> | ||
</repositories> | ||
``` | ||
|
||
You will need to authenticate with Github to use Github Package Repository. Create a personal access token in [your GitHub settings](https://github.com/settings/tokens). It only needs **read:package** permissions. Then add something like the following in your Maven `settings.xml`: | ||
|
||
```xml | ||
<servers> | ||
<server> | ||
<id>github-transaction-outbox</id> | ||
<username>${env.GITHUB_USERNAME}</username> | ||
<password>${env.GITHUB_TOKEN}</password> | ||
</server> | ||
</servers> | ||
``` | ||
|
||
The above example uses environment variables, allowing you to keep the credentials out of source control, but you can hard-code them if you know what you're doing. | ||
|
||
## Configuration | ||
|
||
Create your `TransactionOutbox` as a bean: | ||
|
||
```java | ||
|
||
@Produces | ||
public TransactionOutbox transactionOutbox(QuarkusTransactionManager transactionManager) | ||
{ | ||
return TransactionOutbox.builder().instantiator(CdiInstantiator.create()).transactionManager(transactionManager).persistor(Persistor.forDialect(Dialect.H2)).build(); | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
```java | ||
@Transactional | ||
public void doStuff() { | ||
customerRepository.save(new Customer(1L, "Martin", "Carthy")); | ||
customerRepository.save(new Customer(2L, "Dave", "Pegg")); | ||
outbox.get().schedule(getClass()).publishCustomerCreatedEvent(1L); | ||
outbox.get().schedule(getClass()).publishCustomerCreatedEvent(2L); | ||
} | ||
|
||
void publishCustomerCreatedEvent(long id) { | ||
// Remote call here | ||
} | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>transactionoutbox-parent</artifactId> | ||
<groupId>com.gruelbox</groupId> | ||
<version>${revision}</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<name>Transaction Outbox Quarkus</name> | ||
<packaging>jar</packaging> | ||
<artifactId>transactionoutbox-quarkus</artifactId> | ||
<description>A safe implementation of the transactional outbox pattern for Java (Quarkus extension library) | ||
</description> | ||
|
||
<properties> | ||
<quarkus.version>2.11.1.Final</quarkus.version> | ||
</properties> | ||
|
||
<dependencies> | ||
|
||
<!-- Runtime --> | ||
<dependency> | ||
<groupId>com.gruelbox</groupId> | ||
<artifactId>transactionoutbox-core</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>jakarta.enterprise</groupId> | ||
<artifactId>jakarta.enterprise.cdi-api</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>jakarta.transaction</groupId> | ||
<artifactId>jakarta.transaction-api</artifactId> | ||
</dependency> | ||
|
||
<!-- Test --> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-junit5</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-resteasy</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-arc</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-jdbc-h2</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-agroal</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-arc</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-undertow</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkus.platform</groupId> | ||
<artifactId>quarkus-bom</artifactId> | ||
<version>${quarkus.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
</project> |
19 changes: 19 additions & 0 deletions
19
transactionoutbox-quarkus/src/main/java/com/gruelbox/transactionoutbox/CdiInstantiator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.gruelbox.transactionoutbox; | ||
|
||
import javax.enterprise.context.ApplicationScoped; | ||
import javax.enterprise.inject.spi.CDI; | ||
|
||
@ApplicationScoped | ||
public class CdiInstantiator extends AbstractFullyQualifiedNameInstantiator { | ||
|
||
public static CdiInstantiator create() { | ||
return new CdiInstantiator(); | ||
} | ||
|
||
private CdiInstantiator() {} | ||
|
||
@Override | ||
protected Object createInstance(Class<?> clazz) { | ||
return CDI.current().select(clazz).get(); | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
transactionoutbox-quarkus/src/main/java/com/gruelbox/transactionoutbox/CdiTransaction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package com.gruelbox.transactionoutbox; | ||
|
||
import com.gruelbox.transactionoutbox.jdbc.JdbcTransaction; | ||
|
||
import javax.sql.DataSource; | ||
import javax.transaction.Synchronization; | ||
import javax.transaction.TransactionSynchronizationRegistry; | ||
import java.lang.reflect.InvocationHandler; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Proxy; | ||
import java.sql.Connection; | ||
import java.sql.PreparedStatement; | ||
import java.sql.SQLException; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.function.Supplier; | ||
|
||
public final class CdiTransaction implements JdbcTransaction { | ||
|
||
private final DataSource datasource; | ||
private final TransactionSynchronizationRegistry tsr; | ||
|
||
CdiTransaction(DataSource dataSource, TransactionSynchronizationRegistry tsr) { | ||
this.datasource = dataSource; | ||
this.tsr = tsr; | ||
} | ||
|
||
@Override | ||
public Connection connection() { | ||
try { | ||
return datasource.getConnection(); | ||
} catch (SQLException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public PreparedStatement prepareBatchStatement(String sql) { | ||
@SuppressWarnings("resource") BatchCountingStatement preparedStatement = | ||
Utils.uncheckedly( | ||
() -> BatchCountingStatementHandler.countBatches(connection().prepareStatement(sql))); | ||
tsr.registerInterposedSynchronization( | ||
new Synchronization() { | ||
@Override | ||
public void beforeCompletion() { | ||
if (preparedStatement.getBatchCount() != 0) { | ||
Utils.uncheck(preparedStatement::executeBatch); | ||
} | ||
} | ||
|
||
@Override | ||
public void afterCompletion(int status) { | ||
Utils.safelyClose(preparedStatement); | ||
} | ||
}); | ||
return preparedStatement; | ||
} | ||
|
||
@Override | ||
public void addPostCommitHook(Runnable runnable) { | ||
tsr.registerInterposedSynchronization(new Synchronization() { | ||
@Override | ||
public void beforeCompletion() { | ||
} | ||
|
||
@Override | ||
public void afterCompletion(int status) { | ||
runnable.run(); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public void addPostCommitHook(Supplier<CompletableFuture<Void>> hook) { | ||
tsr.registerInterposedSynchronization(new Synchronization() { | ||
@Override | ||
public void beforeCompletion() { | ||
} | ||
|
||
@Override | ||
public void afterCompletion(int status) { | ||
Utils.join(hook.get()); | ||
} | ||
}); | ||
} | ||
|
||
private interface BatchCountingStatement extends PreparedStatement | ||
{ | ||
int getBatchCount(); | ||
} | ||
|
||
private static final class BatchCountingStatementHandler implements InvocationHandler { | ||
|
||
private final PreparedStatement delegate; | ||
private int count = 0; | ||
|
||
private BatchCountingStatementHandler(PreparedStatement delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
static BatchCountingStatement countBatches(PreparedStatement delegate) { | ||
return (BatchCountingStatement) | ||
Proxy.newProxyInstance( | ||
BatchCountingStatementHandler.class.getClassLoader(), | ||
new Class[] {BatchCountingStatement.class}, | ||
new BatchCountingStatementHandler(delegate)); | ||
} | ||
|
||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | ||
if ("getBatchCount".equals(method.getName())) { | ||
return count; | ||
} | ||
try { | ||
return method.invoke(delegate, args); | ||
} finally { | ||
if ("addBatch".equals(method.getName())) { | ||
++count; | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.