-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- adds frist draft for cdc
- Loading branch information
Showing
46 changed files
with
1,370 additions
and
21 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
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,46 @@ | ||
<?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"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>io.es4j</groupId> | ||
<artifactId>es4j-extensions</artifactId> | ||
<version>0</version> | ||
</parent> | ||
|
||
<artifactId>es4j-saga</artifactId> | ||
|
||
<properties> | ||
<maven.compiler.source>17</maven.compiler.source> | ||
<maven.compiler.target>17</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.es4j</groupId> | ||
<artifactId>es4j-dependencies</artifactId> | ||
<version>${project.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.es4j</groupId> | ||
<artifactId>es4j-core</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.es4j</groupId> | ||
<artifactId>es4j-sql</artifactId> | ||
<version>${project.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.soabase.record-builder</groupId> | ||
<artifactId>record-builder-processor</artifactId> | ||
</dependency> | ||
</dependencies> | ||
</project> |
14 changes: 14 additions & 0 deletions
14
es4j-extensions/es4j-saga/src/main/java/io/es4j/saga/Saga.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,14 @@ | ||
package io.es4j.saga; | ||
|
||
import java.util.List; | ||
|
||
public interface Saga<T, R> { | ||
|
||
List<Class<? extends SagaTransaction<T>>> transactionOrder(); | ||
|
||
T supplyPayload(R request); | ||
default boolean async() { | ||
return false; | ||
} | ||
|
||
} |
169 changes: 169 additions & 0 deletions
169
es4j-extensions/es4j-saga/src/main/java/io/es4j/saga/SagaManager.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,169 @@ | ||
package io.es4j.saga; | ||
|
||
import io.es4j.sql.exceptions.NotFound; | ||
import io.smallrye.mutiny.Multi; | ||
import io.smallrye.mutiny.Uni; | ||
import io.smallrye.mutiny.vertx.UniHelper; | ||
import io.vertx.core.json.JsonObject; | ||
|
||
import java.util.*; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.stream.Collectors; | ||
|
||
public record SagaManager<T extends SagaTrigger, R>( | ||
Class<R> requestClass, | ||
Class<T> payloadClass, | ||
Saga<T, R> saga, | ||
Set<? extends SagaTransaction<T>> transactions, | ||
SagaStore sagaStore | ||
) { | ||
|
||
public boolean isMatch(String rClass) { | ||
return requestClass.getName().equals(rClass); | ||
} | ||
|
||
public T supplyPayload(JsonObject request) { | ||
return saga.supplyPayload(request.mapTo(requestClass)); | ||
} | ||
|
||
public Uni<Void> process(JsonObject request, SagaStore sagaStore) { | ||
final var payload = new AtomicReference<>(saga.supplyPayload(request.mapTo(requestClass))); | ||
if (payload.get().fireAndForget()) { | ||
handleSaga(sagaStore, payload) | ||
.subscribe() | ||
.with(UniHelper.NOOP); | ||
return Uni.createFrom().voidItem(); | ||
} | ||
return handleSaga(sagaStore, payload); | ||
} | ||
|
||
private Uni<Void> handleSaga(SagaStore sagaStore, AtomicReference<T> payload) { | ||
return sagaStore.fetchSaga(payload.get().id()) | ||
.onFailure(NotFound.class).recoverWithNull().replaceWith(sagaRecord(payload)) | ||
.flatMap(sagaRecord -> switch (sagaRecord.state()) { | ||
case STAGED -> Uni.createFrom().voidItem(); | ||
case INITIALIZED -> performSaga(sagaStore, payload, sagaRecord); | ||
case COMMITTED -> rollback(sagaStore, payload, sagaRecord); | ||
default -> Uni.createFrom().failure(new IllegalStateException()); | ||
}) | ||
.replaceWithVoid(); | ||
} | ||
|
||
|
||
private SagaRecord sagaRecord(AtomicReference<T> payload) { | ||
return new SagaRecord( | ||
payload.get().id(), | ||
SagaState.INITIALIZED, | ||
transactions.stream().map(t -> new SagaTransactionRecord(t.name(), SagaTransactionState.INITIALIZED, null)).collect(Collectors.toSet()), | ||
null, | ||
JsonObject.mapFrom(payload.get()).getMap() | ||
); | ||
} | ||
|
||
private Uni<?> rollback(SagaStore sagaStore, AtomicReference<T> payload, SagaRecord sagaRecord) { | ||
return Uni.createFrom().voidItem(); | ||
} | ||
|
||
private Uni<Void> performSaga(SagaStore sagaStore, AtomicReference<T> payload, SagaRecord sagaRecord) { | ||
if (Objects.nonNull(sagaRecord.payload())) { | ||
payload.set(JsonObject.mapFrom(sagaRecord.payload()).mapTo(payloadClass)); | ||
} | ||
final var executionStack = new Stack<SagaTransaction<T>>(); | ||
final var rollbackStack = new Stack<SagaTransaction<T>>(); | ||
transactions.stream() | ||
.filter(requiredTransaction -> sagaRecord.transactions().stream() | ||
.noneMatch(executedTransaction -> executedTransaction.transactionName().equals(requiredTransaction.name())) | ||
) | ||
.forEach(executionStack::push); | ||
return Multi.createFrom().iterable(executionStack) | ||
.onItem().transformToUniAndConcatenate(transaction -> { | ||
final var record = sagaRecord.transactions().stream().filter(t -> transaction.name().equals(t.transactionName())).findFirst().orElseThrow(); | ||
return processTransaction(payload, record, transaction, sagaRecord) | ||
.onItemOrFailure().transformToUni( | ||
(item, failure) -> { | ||
rollbackStack.push(transaction); | ||
if (Objects.nonNull(failure)) { | ||
// todo interrupt flow trigger rollback | ||
} else { | ||
|
||
} | ||
return null; | ||
} | ||
); | ||
} | ||
) | ||
.collect().asList() | ||
.onFailure().invoke( | ||
throwable -> { | ||
|
||
} | ||
) | ||
.replaceWithVoid(); | ||
} | ||
|
||
private Uni<? extends T> processTransaction(AtomicReference<T> payload, SagaTransactionRecord sagaTransactionRecord, SagaTransaction<T> transaction, SagaRecord record) { | ||
return switch (sagaTransactionRecord.state()) { | ||
case INITIALIZED -> stage(record, sagaTransactionRecord, payload, transaction); | ||
case STAGED -> commit(record, sagaTransactionRecord, payload, transaction); | ||
case COMMIT_FAILURE, REVERT_FAILURE, STAGE_FAILURE -> rollback(record, sagaTransactionRecord, payload, transaction); | ||
case COMMITTED, REVERTED -> Uni.createFrom().item(payload.get()); | ||
}; | ||
} | ||
|
||
private Uni<T> rollback(SagaRecord sagaRecord, SagaTransactionRecord transactionRecord, AtomicReference<T> payload, SagaTransaction<T> transaction) { | ||
return transaction.stage(payload.get()).map(payload::getAndSet) | ||
.onFailure().retry().withBackOff(transaction.configuration().retryBackOff()).atMost(transaction.configuration().numberOfRetries()) | ||
.onItemOrFailure().call((item, failure) -> handleRollbackResult(sagaRecord, transactionRecord, item, failure)); | ||
} | ||
|
||
private Uni<Void> handleRollbackResult(SagaRecord sagaRecord, SagaTransactionRecord transactionRecord, T item, Throwable failure) { | ||
if (Objects.nonNull(failure)) { | ||
final var tResult = SagaTransactionRecordBuilder.builder(transactionRecord).state(SagaTransactionState.REVERT_FAILURE).build(); | ||
sagaRecord.transactions().add(tResult); | ||
} else { | ||
final var tResult = SagaTransactionRecordBuilder.builder(transactionRecord).state(SagaTransactionState.REVERTED).build(); | ||
sagaRecord.transactions().add(tResult); | ||
} | ||
return sagaStore.update(SagaRecordBuilder.builder(sagaRecord.computeState()) | ||
.payload(JsonObject.mapFrom(item).getMap()) | ||
.build()); | ||
} | ||
|
||
private Uni<T> stage(SagaRecord sagaRecord, SagaTransactionRecord transactionRecord, AtomicReference<T> payload, SagaTransaction<T> transaction) { | ||
return transaction.stage(payload.get()).map(payload::getAndSet) | ||
.onItemOrFailure().call((item, failure) -> handleStageResult(sagaRecord, transactionRecord, item, failure)); | ||
} | ||
|
||
private Uni<Void> handleStageResult(SagaRecord sagaRecord, SagaTransactionRecord transactionRecord, T item, Throwable failure) { | ||
if (Objects.nonNull(failure)) { | ||
final var tResult = SagaTransactionRecordBuilder.builder(transactionRecord).state(SagaTransactionState.STAGE_FAILURE).build(); | ||
sagaRecord.transactions().add(tResult); | ||
} else { | ||
final var tResult = SagaTransactionRecordBuilder.builder(transactionRecord).state(SagaTransactionState.STAGED).build(); | ||
sagaRecord.transactions().add(tResult); | ||
} | ||
return sagaStore.update(SagaRecordBuilder.builder(sagaRecord.computeState()) | ||
.payload(JsonObject.mapFrom(item).getMap()) | ||
.build()); | ||
} | ||
|
||
private Uni<T> commit(SagaRecord sagaRecord, SagaTransactionRecord transactionRecord, AtomicReference<T> payload, SagaTransaction<T> transaction) { | ||
return transaction.commit(payload.get()).map(payload::getAndSet) | ||
.onItemOrFailure().call((item, failure) -> handleCommitResult(sagaRecord, transactionRecord, item, failure)); | ||
} | ||
|
||
private Uni<Void> handleCommitResult(SagaRecord sagaRecord, SagaTransactionRecord transactionRecord, T item, Throwable failure) { | ||
if (Objects.nonNull(failure)) { | ||
final var tResult = SagaTransactionRecordBuilder.builder(transactionRecord).state(SagaTransactionState.COMMIT_FAILURE).build(); | ||
sagaRecord.transactions().add(tResult); | ||
} else { | ||
final var tResult = SagaTransactionRecordBuilder.builder(transactionRecord).state(SagaTransactionState.COMMITTED).build(); | ||
sagaRecord.transactions().add(tResult); | ||
} | ||
|
||
return sagaStore.update(SagaRecordBuilder.builder(sagaRecord.computeState()) | ||
.payload(JsonObject.mapFrom(item).getMap()) | ||
.build()); | ||
} | ||
|
||
} |
22 changes: 22 additions & 0 deletions
22
es4j-extensions/es4j-saga/src/main/java/io/es4j/saga/SagaOrchestrator.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,22 @@ | ||
package io.es4j.saga; | ||
|
||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.core.json.JsonObject; | ||
|
||
import java.util.List; | ||
|
||
public class SagaOrchestrator { | ||
|
||
List<SagaManager> sagas; | ||
SagaStore sagaStore; | ||
|
||
|
||
|
||
public Uni<Void> route(String className, JsonObject request) { | ||
final var sagaWrapper = sagas.stream().filter(saga -> saga.isMatch(className)).findFirst().orElseThrow(() -> new IllegalArgumentException("Saga not found")); | ||
final var payload = sagaWrapper.supplyPayload(request); | ||
|
||
return null; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
es4j-extensions/es4j-saga/src/main/java/io/es4j/saga/SagaRecord.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,31 @@ | ||
package io.es4j.saga; | ||
|
||
import io.soabase.recordbuilder.core.RecordBuilder; | ||
|
||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
|
||
@RecordBuilder | ||
public record SagaRecord( | ||
String id, | ||
SagaState state, | ||
Set<SagaTransactionRecord> transactions, | ||
Map<String, Object> trigger, | ||
Map<String, Object> payload | ||
) { | ||
|
||
public SagaRecord computeState() { | ||
if (transactions().stream().allMatch(t -> t.state() == SagaTransactionState.STAGED)) { | ||
return SagaRecordBuilder.builder(this).state(SagaState.STAGED).build(); | ||
} else if (transactions().stream().allMatch(t -> t.state() == SagaTransactionState.COMMITTED)) { | ||
return SagaRecordBuilder.builder(this).state(SagaState.COMMITTED).build(); | ||
} else if (transactions().stream().allMatch(t -> t.state() == SagaTransactionState.REVERTED)) { | ||
return SagaRecordBuilder.builder(this).state(SagaState.REVERTED).build(); | ||
} else if (transactions().stream().anyMatch(t -> t.state().name().contains("FAILURE"))) { | ||
return SagaRecordBuilder.builder(this).state(SagaState.FAILED).build(); | ||
} else { | ||
return SagaRecordBuilder.builder(this).state(SagaState.INITIALIZED).build(); | ||
} | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
es4j-extensions/es4j-saga/src/main/java/io/es4j/saga/SagaState.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,5 @@ | ||
package io.es4j.saga; | ||
|
||
public enum SagaState { | ||
INITIALIZED, STAGED, COMMITTED, REVERTED, FAILED | ||
} |
12 changes: 12 additions & 0 deletions
12
es4j-extensions/es4j-saga/src/main/java/io/es4j/saga/SagaStore.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,12 @@ | ||
package io.es4j.saga; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
|
||
|
||
public interface SagaStore { | ||
|
||
|
||
Uni<SagaRecord> fetchSaga(String id); | ||
Uni<Void> update(SagaRecord command); | ||
|
||
} |
Oops, something went wrong.