Skip to content

A-N-O-D-E-R/workflow-springboot-starter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Workflow Spring Boot Starter

Spring Boot starter for OpenEvolve Workflow Engine with auto-configuration support and fluent API.

Tests Grade Production Ready

Overview

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 @Task annotation
  • 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.

Requirements

  • Java 17+
  • Spring Boot 3.2.0+

Installation

Add the dependency to your pom.xml:

<dependency>
    <groupId>com.anode</groupId>
    <artifactId>workflow-spring-boot-starter</artifactId>
    <version>0.0.1</version>
</dependency>

Quick Start

1. Define Your Workflow Tasks

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);
    }
}

2. Configure Your Application

# application.yml - minimal configuration
workflow:
  engines:
    - name: default-engine
      storage:
        type: memory  # or jpa, file, custom
      sla:
        enabled: false

3. Use the Fluent Workflow Builder

@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.


Configuration

Multiple Workflow Engines

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: false

Use specific engines:

workflowFactory.builder(caseId)
    .engine("order-engine")  // Select specific engine
    .task("validateOrder")
    .start();

Storage Options

JPA Storage (Production)

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: update

In-Memory Storage (Testing)

Fast, volatile storage for development:

workflow:
  engines:
    - name: test-engine
      storage:
        type: memory

⚠️ Warning: All data is lost on application restart.

File-Based Storage (Development)

JSON file persistence:

workflow:
  engines:
    - name: dev-engine
      storage:
        type: file
        file-path: ./workflow-data

⚠️ Warning: Not recommended for production use.

Custom Storage

Provide 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
}

Task Configuration

@Task Annotation

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: MyWorkflowTaskmyworkflowtask
  • Bean name is auto-derived from class name (e.g., MyWorkflowTaskmyWorkflowTask)

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 { ... }

Task Scanning Configuration

Control which packages are scanned for @Task annotations:

workflow:
  scan-base-package: com.mycompany.workflows

Default: com.anode


Fluent Workflow Builder API

Basic Usage

@Autowired
private FluentWorkflowBuilderFactory workflowFactory;

public void executeWorkflow() {
    workflowFactory.builder("case-123")
        .task("validateordertask")
        .task("processpaymenttask")
        .task("sendconfirmationtask")
        .variable("orderId", "12345")
        .variable("customerId", "67890")
        .start();
}

Building Without Execution

// 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);

Multiple Engines

workflowFactory.builder("case-123")
    .engine("production-engine")  // Select specific engine
    .task("task1")
    .start();

Bulk Task Addition

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();

Custom Components

Custom Event Handler

Monitor workflow events:

@Component
@WorkflowEventHandler
public class CustomEventHandler implements EventHandler {

    @Override
    public void onEvent(WorkflowEvent event) {
        // Handle workflow events
        logger.info("Event: {}", event.getType());
    }
}

Custom Component Factory

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);
    }
}

Custom SLA Queue Manager

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
    }
}

Advanced Configuration

Complete Configuration Example

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

Features

Core Features

  • ✅ Auto-configuration for workflow engine components
  • ✅ Multiple isolated workflow engines
  • ✅ Fluent workflow builder API
  • ✅ Automatic task discovery with @Task annotation
  • ✅ Thread-safe storage implementations

Storage Backends

  • ✅ JPA/Hibernate (Production, Thread-safe)
  • ✅ In-Memory (Testing)
  • ✅ File-based JSON (Development)
  • ✅ Custom storage support

Advanced Features

  • ✅ Flexible event handling system
  • ✅ SLA monitoring support
  • ✅ Custom component factory support
  • ✅ Spring Boot configuration properties
  • ✅ Multiple engine isolation
  • ✅ Configurable task scanning

Building

mvn clean install

Testing

mvn test

Tests: 18 passing


Example Application

A complete working example is available in the example directory.

The example demonstrates:

  • ✅ Task registration with @Task annotation
  • ✅ Fluent workflow builder usage
  • ✅ REST API integration
  • ✅ Multi-engine configuration
  • ✅ In-memory storage
  • ✅ Order processing workflow

Run the Example

# 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}'

Architecture

Component Structure

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

Thread Safety

All storage implementations are thread-safe:

  • JPA: Creates new EntityManager per operation, closes after use
  • Memory: Synchronized methods with concurrent-safe collections
  • File: Synchronized file operations

Migration from Direct WorkflowService Usage

Before (Old API):

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);

After (New API):

workflowFactory.builder(orderId)
    .task("validateordertask")
    .task("processpaymenttask")
    .variable("order", order)
    .start();

Troubleshooting

Common Issues

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

Performance Considerations

  • 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

License

See LICENSE file for details.

Support

For issues and feature requests, please use the GitHub issue tracker.

About

Spring Boot starter for the Workflow Engine, providing auto-configuration and seamless integration with Spring Boot applications.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors