Spring Boot starter for OpenEvolve Workflow Engine with auto-configuration support and fluent API.
This starter provides seamless integration of the OpenEvolve Workflow Engine into Spring Boot applications with:
- Zero-configuration defaults
- Flexible customization options
- Multiple workflow engines support
- Fluent workflow builder API
- Automatic task discovery with
@Taskannotation - Thread-safe storage implementations with per-file locking
- Comprehensive input validation
- Production-ready with excellent test coverage
📝 See CHANGELOG.md for recent improvements and performance optimizations.
- Java 17+
- Spring Boot 3.2.0+
Add the dependency to your pom.xml:
<dependency>
<groupId>com.anode</groupId>
<artifactId>workflow-spring-boot-starter</artifactId>
<version>0.0.1</version>
</dependency>Use the @Task annotation to register workflow tasks:
@Task("validateOrder") // Automatically discovered and registered
public class ValidateOrderTask implements InvokableTask {
@Override
public TaskResponse executeStep() {
// Your task logic here
return new TaskResponse(StepResponseType.OK_PROCEED, null, null);
}
}# application.yml - minimal configuration
workflow:
engines:
- name: default-engine
storage:
type: memory # or jpa, file, custom
sla:
enabled: false@Service
@RequiredArgsConstructor
public class OrderService {
private final FluentWorkflowBuilderFactory workflowFactory;
public void processOrder(String orderId, Order order) {
workflowFactory.builder(orderId)
.task("validateOrder") // Task defined with @Task annotation
.task("processPayment") // Task defined with @Task annotation
.variable("order", order)
.start();
}
}See the example application for a complete working demo.
You can configure multiple isolated workflow engines:
workflow:
engines:
- name: order-engine
storage:
type: jpa
sla:
enabled: true
- name: shipping-engine
storage:
type: memory
sla:
enabled: falseUse specific engines:
workflowFactory.builder(caseId)
.engine("order-engine") // Select specific engine
.task("validateOrder")
.start();Thread-safe, database-backed storage:
workflow:
engines:
- name: production-engine
storage:
type: jpa
jpa:
enabled: true
entity-manager-factory-ref: entityManagerFactory
spring:
datasource:
url: jdbc:postgresql://localhost:5432/workflow
username: user
password: pass
jpa:
hibernate:
ddl-auto: updateFast, volatile storage for development:
workflow:
engines:
- name: test-engine
storage:
type: memoryJSON file persistence:
workflow:
engines:
- name: dev-engine
storage:
type: file
file-path: ./workflow-dataProvide your own CommonService implementation:
workflow:
engines:
- name: custom-engine
storage:
type: custom
custom-bean-name: myCustomStorage@Service("myCustomStorage")
public class MyCustomStorage implements CommonService {
// Your implementation
}The @Task annotation automatically registers tasks with the workflow engine:
@Task // Automatically discovered and registered as a Spring bean
public class MyWorkflowTask implements InvokableTask {
@Override
public TaskResponse executeStep() {
// Task logic
}
}Important: @Task is meta-annotated with @Component, so you don't need both annotations. Using @Task alone is sufficient for Spring bean registration and auto-discovery.
Task Naming:
- Task name defaults to lowercase class name:
MyWorkflowTask→myworkflowtask - Bean name is auto-derived from class name (e.g.,
MyWorkflowTask→myWorkflowTask)
Custom Task Properties:
@Task(
value = "customBeanName", // Override bean name (optional)
order = 10, // Execution order hint (optional)
userData = "metadata" // Custom metadata (optional)
)
public class MyWorkflowTask implements InvokableTask { ... }Control which packages are scanned for @Task annotations:
workflow:
scan-base-package: com.mycompany.workflowsDefault: com.anode
@Autowired
private FluentWorkflowBuilderFactory workflowFactory;
public void executeWorkflow() {
workflowFactory.builder("case-123")
.task("validateordertask")
.task("processpaymenttask")
.task("sendconfirmationtask")
.variable("orderId", "12345")
.variable("customerId", "67890")
.start();
}// Build definition only
WorkflowDefinition def = workflowFactory.builder("case-123")
.task("task1")
.task("task2")
.buildDefinition();
// Build variables only
WorkflowVariables vars = workflowFactory.builder("case-123")
.variable("key", "value")
.buildVariables();
// Start with pre-built definition and variables
workflowFactory.builder("case-123")
.start(def, vars);workflowFactory.builder("case-123")
.engine("production-engine") // Select specific engine
.task("task1")
.start();List<String> taskNames = Arrays.asList("task1", "task2", "task3");
Map<String, Object> variables = Map.of("key1", "value1", "key2", "value2");
workflowFactory.builder("case-123")
.tasks(taskNames)
.variables(variables)
.start();Monitor workflow events:
@Component
@WorkflowEventHandler
public class CustomEventHandler implements EventHandler {
@Override
public void onEvent(WorkflowEvent event) {
// Handle workflow events
logger.info("Event: {}", event.getType());
}
}Control how workflow components are instantiated:
@Component
@WorkflowComponentFactory
public class CustomComponentFactory implements WorkflowComponantFactory {
@Autowired
private ApplicationContext context;
@Override
public Object getObject(WorkflowContext ctx) {
// Custom component resolution logic
String componentName = ctx.getCompName();
return context.getBean(componentName);
}
}Implement SLA monitoring:
@Component
@SlaQueueManagerComponent
public class CustomSlaQueueManager implements SlaQueueManager {
@Override
public void enqueue(WorkflowContext ctx, List<Milestone> milestones) {
// Queue SLA milestones
}
@Override
public void dequeue(WorkflowContext ctx, String milestoneId) {
// Remove completed milestones
}
@Override
public void dequeueAll(WorkflowContext ctx) {
// Clear all milestones for workflow
}
}workflow:
# Multiple workflow engines
engines:
- name: production-engine
storage:
type: jpa
jpa:
enabled: true
entity-manager-factory-ref: entityManagerFactory
auto-create-schema: false
sla:
enabled: true
queue-manager-bean: customSlaManager
event:
enabled: true
factory:
enabled: true
- name: background-engine
storage:
type: file
file-path: ./background-workflows
sla:
enabled: false
# Task scanning configuration
scan-base-package: com.mycompany.workflows
# Component factory scanning
component-factory:
scan-base-package: com.mycompany.factories
# Spring configuration
spring:
datasource:
url: jdbc:postgresql://localhost:5432/workflow
username: workflow_user
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
default_schema: workflow
# Logging
logging:
level:
com.anode.workflow: INFO
com.anode.workflow.spring: DEBUG- ✅ Auto-configuration for workflow engine components
- ✅ Multiple isolated workflow engines
- ✅ Fluent workflow builder API
- ✅ Automatic task discovery with
@Taskannotation - ✅ Thread-safe storage implementations
- ✅ JPA/Hibernate (Production, Thread-safe)
- ✅ In-Memory (Testing)
- ✅ File-based JSON (Development)
- ✅ Custom storage support
- ✅ Flexible event handling system
- ✅ SLA monitoring support
- ✅ Custom component factory support
- ✅ Spring Boot configuration properties
- ✅ Multiple engine isolation
- ✅ Configurable task scanning
mvn clean installmvn testTests: 18 passing ✅
A complete working example is available in the example directory.
The example demonstrates:
- ✅ Task registration with
@Taskannotation - ✅ Fluent workflow builder usage
- ✅ REST API integration
- ✅ Multi-engine configuration
- ✅ In-memory storage
- ✅ Order processing workflow
# Build and install the starter
mvn clean install
# Run the example application
cd example
mvn spring-boot:run
# Test the API
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"customerName": "John Doe", "amount": 99.99}'workflow-spring-boot-starter/
├── @AutoConfiguration # Auto-configuration classes
│ ├── WorkflowAutoConfiguration # Creates RuntimeServices
│ ├── WorkflowComponentFactoryAutoConfiguration # Task scanning & engine
│ └── Storage configurations (JPA, Memory, File)
│
├── @Component # Core components
│ ├── TaskScanner # Discovers @Task annotated classes
│ ├── WorkflowEngine # Workflow execution engine
│ └── FluentWorkflowBuilderFactory # Creates fluent builders
│
└── Storage Implementations # Thread-safe storage
├── JpaCommonServiceAdapter # EntityManagerFactory per operation
├── MemoryCommonService # HashMap with synchronization
└── FileCommonService # JSON file persistence
All storage implementations are thread-safe:
- JPA: Creates new
EntityManagerper operation, closes after use - Memory: Synchronized methods with concurrent-safe collections
- File: Synchronized file operations
RuntimeService rs = workflowService.getRunTimeService(storage, factory, handler, sla);
WorkflowDefinition def = new WorkflowDefinition();
def.addStep(new Task("validate", "validateOrderTask", "payment", null));
def.addStep(new Task("payment", "processPaymentTask", null, null));
WorkflowVariables vars = new WorkflowVariables();
vars.setValue("order", WorkflowVariableType.OBJECT, order);
rs.startCase(orderId, def, vars, null);workflowFactory.builder(orderId)
.task("validateordertask")
.task("processpaymenttask")
.variable("order", order)
.start();1. NoSuchBeanDefinitionException: No bean named 'myTask'
Ensure your task class has the @Task annotation with the correct bean name:
@Task("myTask") // Specifies custom bean name
public class MyTask implements InvokableTask { ... }2. IllegalArgumentException: No @Task registered with name: mytask
Task names are lowercase by default. Check the task name:
workflowFactory.builder("case").task("myTask").start();3. Duplicate task bean name
Two tasks have the same derived bean name. Use explicit naming:
@Task("validateOrderV1")
public class ValidateOrderTask implements InvokableTask { ... }4. No RuntimeService available
Ensure you have at least one engine configured:
workflow:
engines:
- name: default-engine
storage:
type: memory- JPA Storage: Creates EntityManager per operation - suitable for high concurrency
- Task Lookup: O(1) with secondary index - scales to 1000+ tasks
- Memory Storage: Fast but volatile - ideal for testing
- File Storage: Slower I/O - use only for development
See LICENSE file for details.
For issues and feature requests, please use the GitHub issue tracker.