Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions order-fulfillment-workflow/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.data/
318 changes: 263 additions & 55 deletions order-fulfillment-workflow/README.md

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion order-fulfillment-workflow/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
name: order-fulfillment-workflow-rc1

services:
axon-server:
image: docker.axoniq.io/axoniq/axonserver:2026.0.0
container_name: order-fulfillment-axon-server
container_name: order-fulfillment-rc1-axon-server
ports:
- "8024:8024"
- "8124:8124"
Expand All @@ -12,9 +14,12 @@ services:
volumes:
- data:/axonserver/data
- events:/axonserver/events
- log:/axonserver/log

volumes:
data:
driver: local
events:
driver: local
log:
driver: local
47 changes: 29 additions & 18 deletions order-fulfillment-workflow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,17 @@

<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>${java.version}</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<axon.version>5.3.2</axon.version>
<axoniq-framework.version>5.3.2</axoniq-framework.version>
<axon-workflow.version>0.2.0</axon-workflow.version>
<axon.version>5.4.0-RC1</axon.version>
<axoniq-framework.version>5.4.0-RC1</axoniq-framework.version>
<spring-boot.version>4.1.1</spring-boot.version>
<testcontainers.version>2.0.5</testcontainers.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-framework-bom</artifactId>
Expand All @@ -46,6 +37,14 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Keep Axoniq's connector-compatible Protobuf version ahead of Spring Boot's default. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
Expand All @@ -57,11 +56,15 @@
</dependencyManagement>

<dependencies>
<!-- Axoniq Workflow Engine: auto-configures @Workflow beans through Spring Boot. -->
<!-- Java workflow DSL; WorkflowHistoryConfig registers the module explicitly. -->
<dependency>
<groupId>io.axoniq.framework</groupId>
<artifactId>axoniq-workflow-dsl</artifactId>
<version>${axoniq-framework.version}</version>
</dependency>
<dependency>
<groupId>io.axoniq.framework</groupId>
<artifactId>axoniq-workflow-spring-boot</artifactId>
<version>${axon-workflow.version}</version>
<artifactId>axoniq-spring-boot-starter</artifactId>
</dependency>

<!-- Axon Framework Spring Boot integration -->
Expand All @@ -83,12 +86,21 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Test -->
<dependency>
<groupId>io.axoniq.framework</groupId>
<artifactId>axoniq-workflow-test</artifactId>
<version>${axon-workflow.version}</version>
<version>${axoniq-framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -140,8 +152,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>${java.version}</release>
<parameters>true</parameters>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
* already registered its {@code awaitPayment} wait (the wait is registered at the very top
* of the workflow, before any {@code awaitExecute} step runs).
*/
public record InitiatingPaymentForCustomerStarted(String orderId) {
public record InitiatingPaymentForCustomerStarted(String orderId, String scenario) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.axoniq.demo.orderfulfillment.config;

import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository;
import io.axoniq.demo.orderfulfillment.history.WorkflowHistoryUpdates;
import io.axoniq.demo.orderfulfillment.workflow.OrderFulfillmentWorkflow;
import io.axoniq.framework.workflow.configuration.WorkflowModule;
import io.axoniq.framework.workflow.dsl.simple.SimpleWorkflowContext;
import org.axonframework.conversion.GeneralConverter;
import org.axonframework.messaging.core.unitofwork.UnitOfWorkFactory;
import org.axonframework.messaging.core.unitofwork.transaction.jdbc.JdbcTransactionalExecutorProvider;
import org.axonframework.messaging.eventhandling.conversion.EventConverter;
import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.GenericTokenTableFactory;
import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.JdbcTokenStore;
import org.axonframework.messaging.eventhandling.processing.streaming.token.store.jdbc.JdbcTokenStoreConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/** The two framework extension points demonstrated by this sample. */
@Configuration(proxyBeanMethods = false)
public class WorkflowHistoryConfig {

@Bean
JdbcWorkflowHistoryRepository workflowHistoryRepository(DataSource dataSource, EventConverter converter,
WorkflowHistoryUpdates updates) {
return new JdbcWorkflowHistoryRepository(dataSource, converter, updates::publish);
}

@Bean
WorkflowModule<SimpleWorkflowContext> orderFulfillmentModule(
OrderFulfillmentWorkflow workflow, JdbcWorkflowHistoryRepository history,
DataSource dataSource, GeneralConverter converter) {
// Only the history processor uses durable tokens. The map projection and demo
// simulators retain their existing in-memory token store.
var historyTokens = new JdbcTokenStore(new JdbcTransactionalExecutorProvider(dataSource),
converter, JdbcTokenStoreConfiguration.DEFAULT);
historyTokens.createSchema(GenericTokenTableFactory.INSTANCE);

return WorkflowModule.defaults("OrderFulfillment", SimpleWorkflowContext.class)
.definition(definitions -> definitions.autodetected(configuration -> workflow))
.withHistory(configuration -> {
history.initialize(configuration.getComponent(UnitOfWorkFactory.class));
return history;
})
.historyProcessorConfiguration(processor -> processor
.tokenStore(historyTokens)
.initialSegmentCount(1)
.batchSize(1)
// A method reference keeps this interceptor local to the history processor.
// An interceptor bean would be auto-registered on every event processor.
.withInterceptor(history::interceptOnHandle));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.axoniq.demo.orderfulfillment.controller;

import io.axoniq.demo.orderfulfillment.history.JdbcWorkflowHistoryRepository;
import io.axoniq.demo.orderfulfillment.history.WorkflowHistoryUpdates;
import io.axoniq.framework.workflow.dsl.api.WorkflowStatus;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/workflows")
public class WorkflowHistoryController {

private final JdbcWorkflowHistoryRepository history;
private final WorkflowHistoryUpdates updates;

public WorkflowHistoryController(JdbcWorkflowHistoryRepository history, WorkflowHistoryUpdates updates) {
this.history = history;
this.updates = updates;
}

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream() {
return updates.subscribe();
}

@GetMapping
public Map<String, Object> list(@RequestParam(defaultValue = "") String search,
@RequestParam(required = false) WorkflowStatus status,
@RequestParam(defaultValue = "50") int limit,
@RequestParam(defaultValue = "0") int offset) {
if (limit < 1 || limit > 200 || offset < 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Use limit 1–200 and offset >= 0");
}
return history.list(search, status == null ? "" : status.name(), limit, offset);
}

@GetMapping("/{workflowId}")
public Object detail(@PathVariable String workflowId) {
return history.detail(workflowId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Workflow not found"));
}

@GetMapping("/{workflowId}/events")
public List<Map<String, Object>> events(@PathVariable String workflowId,
@RequestParam(defaultValue = "0") long after,
@RequestParam(defaultValue = "200") int limit) {
if (after < 0 || limit < 1 || limit > 500) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Use limit 1–500 and after >= 0");
}
detail(workflowId);
return history.timeline(workflowId, after, limit);
}
}
Loading
Loading