Skip to content

Commit 22da021

Browse files
committed
Address PR #444 review feedback
- Bump axon-workflow dependency from 1.0.0-SNAPSHOT to released 0.1.0 in both demo poms; drop the central-portal-snapshots repository block now that the engine is on Maven Central. Fixes the red CI job. - Rename "Axon Workflow Engine" to "Axoniq Workflow Engine" across the remaining poms, READMEs, and inline comments. - Drop the now-obsolete "build the workflow engine locally first" snippet from order-fulfillment-workflow/README.md and update the Prerequisites version to 0.1.0. - Rebalance the workflow-saga README's comparison section: rename "What got simpler" to "Comparison: @saga vs @workflow", add a framing paragraph that this is a trade-off, and add explicit "Where @workflow reads more naturally" and "Where @saga reads more naturally" subsections that acknowledge the single ~70-line execute() method and the value of per-handler decomposition. - OrderEventStream: replace the manual HashMap.put chain in the OrderPlaced handler with ObjectMapper.convertValue(event, ...) plus the two extra "type"/"timestamp" entries. Uses the Jackson 3 ObjectMapper (tools.jackson.databind) autoconfigured by Spring Boot 4.
1 parent 8ddd507 commit 22da021

5 files changed

Lines changed: 52 additions & 62 deletions

File tree

order-fulfillment-workflow/README.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,7 @@ public void execute(SimpleWorkflowContext ctx) {
3737
- Java 21+
3838
- Maven 3.9+
3939
- Docker (for Axon Server)
40-
- The Axon Workflow Engine (`io.axoniq.framework.workflow:*:1.0.0-SNAPSHOT`) installed in the local Maven repository
41-
42-
If the workflow engine isn't published yet, build it locally first:
43-
44-
```bash
45-
git clone git@github.com:AxonIQ/extension-workflow.git
46-
cd extension-workflow
47-
mvn clean install -DskipTests
48-
```
40+
- The Axoniq Workflow Engine (`io.axoniq.framework.workflow:*:0.1.0`), available on Maven Central
4941

5042
## Running the application
5143

order-fulfillment-workflow/pom.xml

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<artifactId>order-fulfillment-workflow</artifactId>
99
<version>0.0.1-SNAPSHOT</version>
1010
<name>Order Fulfillment Workflow</name>
11-
<description>Sample showing how to model an Order Fulfillment process with the Axon Workflow Engine.</description>
11+
<description>Sample showing how to model an Order Fulfillment process with the Axoniq Workflow Engine.</description>
1212

1313
<properties>
1414
<java.version>21</java.version>
@@ -18,7 +18,7 @@
1818

1919
<axon.version>5.1.0</axon.version>
2020
<axoniq-framework.version>5.1.0</axoniq-framework.version>
21-
<axon-workflow.version>1.0.0-SNAPSHOT</axon-workflow.version>
21+
<axon-workflow.version>0.1.0</axon-workflow.version>
2222
<spring-boot.version>4.0.6</spring-boot.version>
2323
<testcontainers.version>2.0.5</testcontainers.version>
2424
</properties>
@@ -57,7 +57,7 @@
5757
</dependencyManagement>
5858

5959
<dependencies>
60-
<!-- Axon Workflow Engine: auto-configures @Workflow beans through Spring Boot. -->
60+
<!-- Axoniq Workflow Engine: auto-configures @Workflow beans through Spring Boot. -->
6161
<dependency>
6262
<groupId>io.axoniq.framework.workflow</groupId>
6363
<artifactId>axon-workflow-spring-boot</artifactId>
@@ -170,18 +170,4 @@
170170
</plugin>
171171
</plugins>
172172
</build>
173-
174-
<repositories>
175-
<repository>
176-
<id>central-portal-snapshots</id>
177-
<name>Central Portal Snapshots</name>
178-
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
179-
<releases>
180-
<enabled>false</enabled>
181-
</releases>
182-
<snapshots>
183-
<enabled>true</enabled>
184-
</snapshots>
185-
</repository>
186-
</repositories>
187173
</project>

order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/projection/OrderEventStream.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.axoniq.demo.orderfulfillment.projection;
22

3+
import tools.jackson.core.type.TypeReference;
4+
import tools.jackson.databind.ObjectMapper;
35
import io.axoniq.demo.orderfulfillment.api.InitiatingPaymentForCustomerStarted;
46
import io.axoniq.demo.orderfulfillment.api.OrderDelivered;
57
import io.axoniq.demo.orderfulfillment.api.OrderFailed;
@@ -14,7 +16,6 @@
1416

1517
import java.io.IOException;
1618
import java.time.Instant;
17-
import java.util.HashMap;
1819
import java.util.List;
1920
import java.util.Map;
2021
import java.util.concurrent.CopyOnWriteArrayList;
@@ -31,9 +32,11 @@ public class OrderEventStream {
3132

3233
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
3334
private final OrderStatusProjection projection;
35+
private final ObjectMapper objectMapper;
3436

35-
public OrderEventStream(OrderStatusProjection projection) {
37+
public OrderEventStream(OrderStatusProjection projection, ObjectMapper objectMapper) {
3638
this.projection = projection;
39+
this.objectMapper = objectMapper;
3740
}
3841

3942
public SseEmitter subscribe() {
@@ -52,19 +55,8 @@ public SseEmitter subscribe() {
5255

5356
@EventHandler
5457
public void on(OrderPlaced event) {
55-
var payload = new HashMap<String, Object>();
58+
Map<String, Object> payload = objectMapper.convertValue(event, new TypeReference<>() {});
5659
payload.put("type", "PLACED");
57-
payload.put("orderId", event.orderId());
58-
payload.put("customerId", event.customerId());
59-
payload.put("email", event.email());
60-
payload.put("amount", event.amount());
61-
payload.put("originCity", event.originCity());
62-
payload.put("originLat", event.originLat());
63-
payload.put("originLng", event.originLng());
64-
payload.put("destinationCity", event.destinationCity());
65-
payload.put("destinationLat", event.destinationLat());
66-
payload.put("destinationLng", event.destinationLng());
67-
payload.put("scenario", event.scenario());
6860
payload.put("timestamp", Instant.now().toString());
6961
broadcast("order", payload);
7062
}

workflow-saga/README.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ Workflow Engine** instead of `@Saga`. The exact same orchestration — start on
55
request payment + shipment in parallel, react to either of two payment outcomes, complete the
66
process — is expressed as plain imperative Java.
77

8-
## What got simpler
8+
## Comparison: `@Saga` vs `@Workflow`
99

10-
Side-by-side: same business behaviour, expressed two different ways.
10+
Same business behaviour, expressed two different ways. Neither model is a strict
11+
upgrade over the other — they make different trade-offs, and which one fits
12+
better depends on the orchestration you're modelling.
1113

1214
| | `saga` module (`@Saga`) | `workflow-saga` module (`@Workflow`) |
1315
|----------------------------------|---|---|
@@ -18,6 +20,36 @@ Side-by-side: same business behaviour, expressed two different ways.
1820
| Race / parallelism | Implicit, via independent event handlers and shared mutable fields | Explicit, via `ctx.anyMatch(WorkflowStepResult::success, paid, paymentCancelled)` |
1921
| Cancellation of pending waits | Manual: cancel deadlines via `DeadlineManager`, end saga via `SagaLifecycle.end()` | `delivered.cancel("payment cancelled")` and the workflow simply returns |
2022

23+
### Where `@Workflow` reads more naturally
24+
25+
- The control flow is one ordinary Java method. `if`/`else`, early `return`, and
26+
local variables describe the orchestration directly, without translating it
27+
into a state machine spread across multiple handler methods.
28+
- Correlation, timeouts, and cancellation are local to the call site
29+
(`waitForEvent("...", ..., Duration.ofDays(5))`, `delivered.cancel(...)`),
30+
rather than spread between annotations, deadline managers, and lifecycle
31+
calls.
32+
- The engine event-sources every step automatically, so there's no
33+
`eventGateway.publish` inside the orchestration and nothing to keep in sync
34+
between the orchestration and a projection.
35+
36+
### Where `@Saga` reads more naturally
37+
38+
- A saga naturally decomposes into small, individually-named methods — one per
39+
`@SagaEventHandler`. The workflow's `execute(SimpleWorkflowContext ctx)` is a
40+
single ~70-line method, which is harder to scan at a glance and harder to
41+
unit-test in isolation. For sagas with more branches than this one, that
42+
single method grows fast.
43+
- Saga state lives in named class fields, so "what does this saga remember
44+
between events?" is answered by reading the class. In the workflow, the
45+
equivalent information is encoded in local variables and the engine's stored
46+
state — less direct when debugging.
47+
- `@SagaEventHandler` makes each correlation explicit at the method level,
48+
which is convenient when you want to grep the codebase for every place a
49+
particular event participates in an orchestration.
50+
- The annotation-driven model has been the Axon Framework idiom for years —
51+
existing teams won't need to learn a new programming model to maintain it.
52+
2153
The whole orchestration is one `execute(SimpleWorkflowContext ctx)` method:
2254

2355
```java
@@ -65,10 +97,12 @@ public void execute(SimpleWorkflowContext ctx) {
6597
}
6698
```
6799

68-
There is no `@StartSaga`, no `@EndSaga`, no `@DeadlineHandler`, no `SagaLifecycle.associateWith`, no
69-
serialized saga state, and no `eventGateway.publish` calls — the workflow never publishes events
70-
itself. The engine event-sources every step and emits a Started/Completed event pair for each
71-
`awaitExecute`; projections subscribe to those naturally-emitted events.
100+
In the workflow model, lifecycle and persistence are handled by the engine rather than by code in
101+
the orchestration class: there's no `@StartSaga`, `@EndSaga`, `@DeadlineHandler`,
102+
`SagaLifecycle.associateWith`, or `eventGateway.publish`. The engine event-sources every step and
103+
emits a Started/Completed event pair for each `awaitExecute`, which projections subscribe to.
104+
That moves boilerplate out of the orchestration, at the cost of a single longer `execute(...)`
105+
method — see the trade-off table above.
72106

73107
## Running the application
74108

workflow-saga/pom.xml

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<artifactId>workflow-saga</artifactId>
99
<version>0.0.1-SNAPSHOT</version>
1010
<name>Workflow Saga</name>
11-
<description>Same order-process saga as the `saga` module — rewritten with the Axon Workflow Engine.</description>
11+
<description>Same order-process saga as the `saga` module — rewritten with the Axoniq Workflow Engine.</description>
1212

1313
<properties>
1414
<java.version>21</java.version>
@@ -18,7 +18,7 @@
1818

1919
<axon.version>5.1.0</axon.version>
2020
<axoniq-framework.version>5.1.0</axoniq-framework.version>
21-
<axon-workflow.version>1.0.0-SNAPSHOT</axon-workflow.version>
21+
<axon-workflow.version>0.1.0</axon-workflow.version>
2222
<spring-boot.version>4.0.6</spring-boot.version>
2323
<testcontainers.version>2.0.5</testcontainers.version>
2424
</properties>
@@ -166,18 +166,4 @@
166166
</plugin>
167167
</plugins>
168168
</build>
169-
170-
<repositories>
171-
<repository>
172-
<id>central-portal-snapshots</id>
173-
<name>Central Portal Snapshots</name>
174-
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
175-
<releases>
176-
<enabled>false</enabled>
177-
</releases>
178-
<snapshots>
179-
<enabled>true</enabled>
180-
</snapshots>
181-
</repository>
182-
</repositories>
183169
</project>

0 commit comments

Comments
 (0)