extended analysis

This commit is contained in:
2026-06-14 16:32:51 +02:00
parent 2251a587d9
commit ab474c0766
125 changed files with 4180 additions and 317 deletions

View File

@@ -0,0 +1,6 @@
build/
out/
.gradle/
.idea/
*.class
*.log

View File

@@ -0,0 +1,48 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.3'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'click.kamil'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.statemachine:spring-statemachine-starter:3.2.0'
implementation 'io.projectreactor:reactor-core'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
bootJar {
enabled = false
}
jar {
enabled = true
}

View File

@@ -0,0 +1,55 @@
package click.kamil.examples.statemachine.extended.config;
import click.kamil.examples.statemachine.extended.constants.MyEvents;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.beans.factory.annotation.Value;
@Configuration
@EnableStateMachine(name = "extendedStateMachine")
public class ExtendedStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
@Value("${app.states.initial:START}")
private String initialState;
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial(initialState)
.state("PROCESSING")
.state("COMPLETED")
.state("CANCELLED");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("START").target("PROCESSING")
.event(MyEvents.SUBMIT)
.and()
.withExternal()
.source("PROCESSING").target("COMPLETED")
.event("FINISH")
.and()
.withExternal()
.source("PROCESSING").target("CANCELLED")
.event(MyEvents.CANCEL)
.and()
.withExternal()
.source("START").target("PROCESSING")
.event("REACTIVE_EVENT")
.and()
.withExternal()
.source("START").target("START")
.event("AUDIT_EVENT")
.and()
.withExternal()
.source("START").target("PROCESSING")
.event("EXTERNAL_TRIGGER");
}
}

View File

@@ -0,0 +1,6 @@
package click.kamil.examples.statemachine.extended.constants;
public class MyEvents {
public static final String SUBMIT = "SUBMIT_EVENT";
public static final String CANCEL = "CANCEL_EVENT";
}

View File

@@ -0,0 +1,11 @@
package click.kamil.examples.statemachine.extended.service;
import org.springframework.stereotype.Service;
@Service
public class ExternalNotificationService {
public void sendExternalNotification(String message) {
// Simulated third-party call that triggers a state change in an external system
System.out.println("Notifying external system: " + message);
}
}

View File

@@ -0,0 +1,31 @@
package click.kamil.examples.statemachine.extended.service;
import click.kamil.examples.statemachine.extended.constants.MyEvents;
import lombok.RequiredArgsConstructor;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class OrderService {
private final StateMachine<String, String> stateMachine;
private final StateMachinePersister<String, String, String> persister;
private final ExternalNotificationService notificationService;
public void processSubmit() {
// Business logic here...
notificationService.sendExternalNotification("Submit processed");
stateMachine.sendEvent(MyEvents.SUBMIT);
}
public void processCancel() {
// More business logic...
stateMachine.sendEvent(MyEvents.CANCEL);
}
public void resumeOrder(String orderId) throws Exception {
persister.restore(stateMachine, orderId);
}
}

View File

@@ -0,0 +1,22 @@
package click.kamil.examples.statemachine.extended.service;
import lombok.RequiredArgsConstructor;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
@RequiredArgsConstructor
public class ReactiveOrderService {
private final StateMachine<String, String> stateMachine;
public Mono<Void> processReactive(String orderId) {
return Mono.just(orderId)
.doOnNext(id -> {
// Hidden trigger inside a lambda!
stateMachine.sendEvent("REACTIVE_EVENT");
})
.then();
}
}

View File

@@ -0,0 +1,19 @@
package click.kamil.examples.statemachine.extended.web;
import lombok.RequiredArgsConstructor;
import org.springframework.statemachine.StateMachine;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@RequiredArgsConstructor
public class AuditInterceptor implements HandlerInterceptor {
private final StateMachine<String, String> stateMachine;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
stateMachine.sendEvent("AUDIT_EVENT");
return true;
}
}

View File

@@ -0,0 +1,46 @@
package click.kamil.examples.statemachine.extended.web;
import click.kamil.examples.statemachine.extended.constants.MyEvents;
import click.kamil.examples.statemachine.extended.service.OrderService;
import click.kamil.examples.statemachine.extended.service.ReactiveOrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.statemachine.StateMachine;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final StateMachine<String, String> stateMachine;
private final OrderService orderService;
private final ReactiveOrderService reactiveOrderService;
@PostMapping("/submit")
public void submitOrder() {
orderService.processSubmit();
}
@PostMapping("/cancel")
public void cancelOrder() {
orderService.processCancel();
}
@PostMapping("/finish")
public void finishOrder() {
stateMachine.sendEvent("FINISH");
}
@PostMapping("/resume")
public void resumeOrder(String orderId) throws Exception {
orderService.resumeOrder(orderId);
}
@PostMapping("/reactive")
public Mono<Void> reactiveOrder(String orderId) {
return reactiveOrderService.processReactive(orderId);
}
}

View File

@@ -0,0 +1 @@
app.states.initial=PROD_INITIAL

View File

@@ -0,0 +1 @@
app.states.initial=INIT_STATE

View File

@@ -0,0 +1,6 @@
[
{
"methodFqn": "ExternalNotificationService.sendExternalNotification",
"event": "EXTERNAL_TRIGGER"
}
]