Skip to content

Commit 52e44ee

Browse files
authored
Merge pull request #468 from AxonIQ/order-fulfillment-workflow-console
Upgrade fulfillment workflow
2 parents bbdb06c + 8d1bb97 commit 52e44ee

33 files changed

Lines changed: 7133 additions & 221 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: 263 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: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,17 @@
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.3.2</axon.version>
20-
<axoniq-framework.version>5.3.2</axoniq-framework.version>
21-
<axon-workflow.version>0.2.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.1.1</spring-boot.version>
2321
<testcontainers.version>2.0.5</testcontainers.version>
2422
</properties>
2523

2624
<dependencyManagement>
2725
<dependencies>
28-
<dependency>
29-
<groupId>org.springframework.boot</groupId>
30-
<artifactId>spring-boot-dependencies</artifactId>
31-
<version>${spring-boot.version}</version>
32-
<type>pom</type>
33-
<scope>import</scope>
34-
</dependency>
3526
<dependency>
3627
<groupId>org.axonframework</groupId>
3728
<artifactId>axon-framework-bom</artifactId>
@@ -46,6 +37,14 @@
4637
<type>pom</type>
4738
<scope>import</scope>
4839
</dependency>
40+
<!-- Keep Axoniq's connector-compatible Protobuf version ahead of Spring Boot's default. -->
41+
<dependency>
42+
<groupId>org.springframework.boot</groupId>
43+
<artifactId>spring-boot-dependencies</artifactId>
44+
<version>${spring-boot.version}</version>
45+
<type>pom</type>
46+
<scope>import</scope>
47+
</dependency>
4948
<dependency>
5049
<groupId>org.testcontainers</groupId>
5150
<artifactId>testcontainers-bom</artifactId>
@@ -57,11 +56,15 @@
5756
</dependencyManagement>
5857

5958
<dependencies>
60-
<!-- Axoniq Workflow Engine: auto-configures @Workflow beans through Spring Boot. -->
59+
<!-- Java workflow DSL; WorkflowHistoryConfig registers the module explicitly. -->
60+
<dependency>
61+
<groupId>io.axoniq.framework</groupId>
62+
<artifactId>axoniq-workflow-dsl</artifactId>
63+
<version>${axoniq-framework.version}</version>
64+
</dependency>
6165
<dependency>
6266
<groupId>io.axoniq.framework</groupId>
63-
<artifactId>axoniq-workflow-spring-boot</artifactId>
64-
<version>${axon-workflow.version}</version>
67+
<artifactId>axoniq-spring-boot-starter</artifactId>
6568
</dependency>
6669

6770
<!-- Axon Framework Spring Boot integration -->
@@ -83,12 +86,21 @@
8386
<groupId>com.fasterxml.jackson.core</groupId>
8487
<artifactId>jackson-databind</artifactId>
8588
</dependency>
89+
<dependency>
90+
<groupId>org.springframework.boot</groupId>
91+
<artifactId>spring-boot-starter-jdbc</artifactId>
92+
</dependency>
93+
<dependency>
94+
<groupId>com.h2database</groupId>
95+
<artifactId>h2</artifactId>
96+
<scope>runtime</scope>
97+
</dependency>
8698

8799
<!-- Test -->
88100
<dependency>
89101
<groupId>io.axoniq.framework</groupId>
90102
<artifactId>axoniq-workflow-test</artifactId>
91-
<version>${axon-workflow.version}</version>
103+
<version>${axoniq-framework.version}</version>
92104
<scope>test</scope>
93105
</dependency>
94106
<dependency>
@@ -140,8 +152,7 @@
140152
<artifactId>maven-compiler-plugin</artifactId>
141153
<version>3.16.0</version>
142154
<configuration>
143-
<source>${java.version}</source>
144-
<target>${java.version}</target>
155+
<release>${java.version}</release>
145156
<parameters>true</parameters>
146157
</configuration>
147158
</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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
// A method reference keeps this interceptor local to the history processor.
51+
// An interceptor bean would be auto-registered on every event processor.
52+
.withInterceptor(history::interceptOnHandle));
53+
}
54+
}
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)