Skip to content

Commit ef13892

Browse files
committed
Add persistent workflow history console and simplify fulfillment flow
1 parent ded8093 commit ef13892

32 files changed

Lines changed: 7088 additions & 220 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.data/

‎order-fulfillment-workflow/README.md‎

Lines changed: 258 additions & 55 deletions
Large diffs are not rendered by default.

‎order-fulfillment-workflow/docker-compose.yaml‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
name: order-fulfillment-workflow-rc1
2+
13
services:
24
axon-server:
35
image: docker.axoniq.io/axoniq/axonserver:2026.0.0
4-
container_name: order-fulfillment-axon-server
6+
container_name: order-fulfillment-rc1-axon-server
57
ports:
68
- "8024:8024"
79
- "8124:8124"
@@ -12,9 +14,12 @@ services:
1214
volumes:
1315
- data:/axonserver/data
1416
- events:/axonserver/events
17+
- log:/axonserver/log
1518

1619
volumes:
1720
data:
1821
driver: local
1922
events:
2023
driver: local
24+
log:
25+
driver: local

‎order-fulfillment-workflow/pom.xml‎

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@
1212

1313
<properties>
1414
<java.version>21</java.version>
15-
<maven.compiler.source>21</maven.compiler.source>
16-
<maven.compiler.target>21</maven.compiler.target>
15+
<maven.compiler.release>${java.version}</maven.compiler.release>
1716
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1817

19-
<axon.version>5.1.0</axon.version>
20-
<axoniq-framework.version>5.1.0</axoniq-framework.version>
21-
<axon-workflow.version>0.1.0</axon-workflow.version>
18+
<axon.version>5.4.0-RC1</axon.version>
19+
<axoniq-framework.version>5.4.0-RC1</axoniq-framework.version>
2220
<spring-boot.version>4.0.6</spring-boot.version>
2321
<testcontainers.version>2.0.5</testcontainers.version>
2422
</properties>
@@ -57,11 +55,15 @@
5755
</dependencyManagement>
5856

5957
<dependencies>
60-
<!-- Axoniq Workflow Engine: auto-configures @Workflow beans through Spring Boot. -->
58+
<!-- Java workflow DSL; WorkflowHistoryConfig registers the module explicitly. -->
6159
<dependency>
62-
<groupId>io.axoniq.framework.workflow</groupId>
63-
<artifactId>axon-workflow-spring-boot</artifactId>
64-
<version>${axon-workflow.version}</version>
60+
<groupId>io.axoniq.framework</groupId>
61+
<artifactId>axoniq-workflow-dsl</artifactId>
62+
<version>${axoniq-framework.version}</version>
63+
</dependency>
64+
<dependency>
65+
<groupId>io.axoniq.framework</groupId>
66+
<artifactId>axoniq-spring-boot-starter</artifactId>
6567
</dependency>
6668

6769
<!-- Axon Framework Spring Boot integration -->
@@ -83,12 +85,21 @@
8385
<groupId>com.fasterxml.jackson.core</groupId>
8486
<artifactId>jackson-databind</artifactId>
8587
</dependency>
88+
<dependency>
89+
<groupId>org.springframework.boot</groupId>
90+
<artifactId>spring-boot-starter-jdbc</artifactId>
91+
</dependency>
92+
<dependency>
93+
<groupId>com.h2database</groupId>
94+
<artifactId>h2</artifactId>
95+
<scope>runtime</scope>
96+
</dependency>
8697

8798
<!-- Test -->
8899
<dependency>
89-
<groupId>io.axoniq.framework.workflow</groupId>
90-
<artifactId>axon-workflow-test</artifactId>
91-
<version>${axon-workflow.version}</version>
100+
<groupId>io.axoniq.framework</groupId>
101+
<artifactId>axoniq-workflow-test</artifactId>
102+
<version>${axoniq-framework.version}</version>
92103
<scope>test</scope>
93104
</dependency>
94105
<dependency>
@@ -140,8 +151,7 @@
140151
<artifactId>maven-compiler-plugin</artifactId>
141152
<version>3.14.1</version>
142153
<configuration>
143-
<source>${java.version}</source>
144-
<target>${java.version}</target>
154+
<release>${java.version}</release>
145155
<parameters>true</parameters>
146156
</configuration>
147157
</plugin>

‎order-fulfillment-workflow/src/main/java/io/axoniq/demo/orderfulfillment/api/InitiatingPaymentForCustomerStarted.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
* already registered its {@code awaitPayment} wait (the wait is registered at the very top
77
* of the workflow, before any {@code awaitExecute} step runs).
88
*/
9-
public record InitiatingPaymentForCustomerStarted(String orderId) {
9+
public record InitiatingPaymentForCustomerStarted(String orderId, String scenario) {
1010
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.axoniq.demo.orderfulfillment.config;
2+
3+
import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository;
4+
import io.axoniq.demo.orderfulfillment.history.WorkflowHistoryUpdates;
5+
import io.axoniq.demo.orderfulfillment.workflow.OrderFulfillmentWorkflow;
6+
import io.axoniq.framework.workflow.configuration.WorkflowModule;
7+
import io.axoniq.framework.workflow.dsl.simple.SimpleWorkflowContext;
8+
import org.axonframework.conversion.GeneralConverter;
9+
import org.axonframework.messaging.core.unitofwork.UnitOfWorkFactory;
10+
import org.axonframework.messaging.core.unitofwork.transaction.jdbc.JdbcTransactionalExecutorProvider;
11+
import org.axonframework.messaging.eventhandling.conversion.EventConverter;
12+
import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.GenericTokenTableFactory;
13+
import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.JdbcTokenStore;
14+
import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.JdbcTokenStoreConfiguration;
15+
import org.springframework.context.annotation.Bean;
16+
import org.springframework.context.annotation.Configuration;
17+
18+
import javax.sql.DataSource;
19+
20+
/** The two framework extension points demonstrated by this sample. */
21+
@Configuration(proxyBeanMethods = false)
22+
public class WorkflowHistoryConfig {
23+
24+
@Bean
25+
JdbcWorkflowHistoryRepository workflowHistoryRepository(DataSource dataSource, EventConverter converter,
26+
WorkflowHistoryUpdates updates) {
27+
return new JdbcWorkflowHistoryRepository(dataSource, converter, updates::publish);
28+
}
29+
30+
@Bean
31+
WorkflowModule<SimpleWorkflowContext> orderFulfillmentModule(
32+
OrderFulfillmentWorkflow workflow, JdbcWorkflowHistoryRepository history,
33+
DataSource dataSource, GeneralConverter converter) {
34+
// Only the history processor uses durable tokens. The map projection and demo
35+
// simulators retain their existing in-memory token store.
36+
var historyTokens = new JdbcTokenStore(new JdbcTransactionalExecutorProvider(dataSource),
37+
converter, JdbcTokenStoreConfiguration.DEFAULT);
38+
historyTokens.createSchema(GenericTokenTableFactory.INSTANCE);
39+
40+
return WorkflowModule.defaults("OrderFulfillment", SimpleWorkflowContext.class)
41+
.definition(definitions -> definitions.autodetected(configuration -> workflow))
42+
.withHistory(configuration -> {
43+
history.initialize(configuration.getComponent(UnitOfWorkFactory.class));
44+
return history;
45+
})
46+
.historyProcessorConfiguration(processor -> processor
47+
.tokenStore(historyTokens)
48+
.initialSegmentCount(1)
49+
.batchSize(1)
50+
.withInterceptor(history));
51+
}
52+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.axoniq.demo.orderfulfillment.controller;
2+
3+
import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository;
4+
import io.axoniq.demo.orderfulfillment.history.WorkflowHistoryUpdates;
5+
import io.axoniq.framework.workflow.dsl.api.WorkflowStatus;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.MediaType;
8+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.PathVariable;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RequestParam;
13+
import org.springframework.web.bind.annotation.RestController;
14+
import org.springframework.web.server.ResponseStatusException;
15+
16+
import java.util.List;
17+
import java.util.Map;
18+
19+
@RestController
20+
@RequestMapping("/api/workflows")
21+
public class WorkflowHistoryController {
22+
23+
private final JdbcWorkflowHistoryRepository history;
24+
private final WorkflowHistoryUpdates updates;
25+
26+
public WorkflowHistoryController(JdbcWorkflowHistoryRepository history, WorkflowHistoryUpdates updates) {
27+
this.history = history;
28+
this.updates = updates;
29+
}
30+
31+
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
32+
public SseEmitter stream() {
33+
return updates.subscribe();
34+
}
35+
36+
@GetMapping
37+
public Map<String, Object> list(@RequestParam(defaultValue = "") String search,
38+
@RequestParam(required = false) WorkflowStatus status,
39+
@RequestParam(defaultValue = "50") int limit,
40+
@RequestParam(defaultValue = "0") int offset) {
41+
if (limit < 1 || limit > 200 || offset < 0) {
42+
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Use limit 1–200 and offset >= 0");
43+
}
44+
return history.list(search, status == null ? "" : status.name(), limit, offset);
45+
}
46+
47+
@GetMapping("/{workflowId}")
48+
public Object detail(@PathVariable String workflowId) {
49+
return history.detail(workflowId)
50+
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Workflow not found"));
51+
}
52+
53+
@GetMapping("/{workflowId}/events")
54+
public List<Map<String, Object>> events(@PathVariable String workflowId,
55+
@RequestParam(defaultValue = "0") long after,
56+
@RequestParam(defaultValue = "200") int limit) {
57+
if (after < 0 || limit < 1 || limit > 500) {
58+
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Use limit 1–500 and after >= 0");
59+
}
60+
detail(workflowId);
61+
return history.timeline(workflowId, after, limit);
62+
}
63+
}

0 commit comments

Comments
 (0)