Replace .get() heuristics with type-based access classification and extend literal dataflow resolution.
Introduce ExpressionAccessClassifier to distinguish map lookups from Supplier/bean accessors, fold Map.of and string/array literals at compile time, and add regression tests for dispatcher and dataflow paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
26
state_machines/layered_dispatcher_sample/build.gradle
Normal file
26
state_machines/layered_dispatcher_sample/build.gradle
Normal file
@@ -0,0 +1,26 @@
|
||||
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()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.statemachine:spring-statemachine-starter:3.2.0'
|
||||
}
|
||||
|
||||
bootJar { enabled = false }
|
||||
jar { enabled = true }
|
||||
@@ -0,0 +1,12 @@
|
||||
package click.kamil.examples.statemachine.layered;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LayeredDispatcherApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LayeredDispatcherApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package click.kamil.examples.statemachine.layered.command;
|
||||
|
||||
/**
|
||||
* Intermediate command enum: endpoints map HTTP string keys to these values first,
|
||||
* then {@link click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher}
|
||||
* maps them to concrete state-machine transition events.
|
||||
*/
|
||||
public enum DomainCommand {
|
||||
ORDER_PAY,
|
||||
ORDER_SHIP,
|
||||
ORDER_CANCEL,
|
||||
DOCUMENT_SUBMIT,
|
||||
DOCUMENT_APPROVE,
|
||||
DOCUMENT_REJECT
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package click.kamil.examples.statemachine.layered.dispatch;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.command.DomainCommand;
|
||||
import click.kamil.examples.statemachine.layered.document.DocumentState;
|
||||
import click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent;
|
||||
import click.kamil.examples.statemachine.layered.order.OrderState;
|
||||
import click.kamil.examples.statemachine.layered.order.OrderTransitionEvent;
|
||||
import click.kamil.examples.statemachine.layered.order.PayRichOrderEvent;
|
||||
import click.kamil.examples.statemachine.layered.order.ShipRichOrderEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CentralEventDispatcher {
|
||||
|
||||
private final StateMachineFactory<OrderState, OrderTransitionEvent> orderStateMachineFactory;
|
||||
private final StateMachineFactory<DocumentState, DocumentTransitionEvent> documentStateMachineFactory;
|
||||
|
||||
@Autowired
|
||||
public CentralEventDispatcher(
|
||||
StateMachineFactory<OrderState, OrderTransitionEvent> orderStateMachineFactory,
|
||||
StateMachineFactory<DocumentState, DocumentTransitionEvent> documentStateMachineFactory) {
|
||||
this.orderStateMachineFactory = orderStateMachineFactory;
|
||||
this.documentStateMachineFactory = documentStateMachineFactory;
|
||||
}
|
||||
|
||||
public void dispatch(DomainCommand command) {
|
||||
switch (command) {
|
||||
case ORDER_PAY -> orderPay();
|
||||
case ORDER_SHIP -> orderShip();
|
||||
case ORDER_CANCEL -> orderCancel();
|
||||
case DOCUMENT_SUBMIT -> documentSubmit();
|
||||
case DOCUMENT_APPROVE -> documentApprove();
|
||||
case DOCUMENT_REJECT -> documentReject();
|
||||
}
|
||||
}
|
||||
|
||||
public void orderPay() {
|
||||
sendOrderEvent(OrderTransitionEvent.PAY);
|
||||
}
|
||||
|
||||
public void orderShip() {
|
||||
sendOrderEvent(OrderTransitionEvent.SHIP);
|
||||
}
|
||||
|
||||
public void orderCancel() {
|
||||
sendOrderEvent(OrderTransitionEvent.CANCEL);
|
||||
}
|
||||
|
||||
public void orderPayViaRichEvent(PayRichOrderEvent event) {
|
||||
sendOrderEvent(event.getType());
|
||||
}
|
||||
|
||||
public void orderShipViaRichEvent(ShipRichOrderEvent event) {
|
||||
sendOrderEvent(event.getType());
|
||||
}
|
||||
|
||||
public void documentSubmit() {
|
||||
sendDocumentEvent(DocumentTransitionEvent.SUBMIT);
|
||||
}
|
||||
|
||||
public void documentApprove() {
|
||||
sendDocumentEvent(DocumentTransitionEvent.APPROVE);
|
||||
}
|
||||
|
||||
public void documentReject() {
|
||||
sendDocumentEvent(DocumentTransitionEvent.REJECT);
|
||||
}
|
||||
|
||||
private void sendOrderEvent(OrderTransitionEvent event) {
|
||||
StateMachine<OrderState, OrderTransitionEvent> machine = orderStateMachineFactory.getStateMachine();
|
||||
machine.start();
|
||||
machine.sendEvent(event);
|
||||
}
|
||||
|
||||
private void sendDocumentEvent(DocumentTransitionEvent event) {
|
||||
StateMachine<DocumentState, DocumentTransitionEvent> machine = documentStateMachineFactory.getStateMachine();
|
||||
machine.start();
|
||||
machine.sendEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package click.kamil.examples.statemachine.layered.dispatch;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.command.DomainCommand;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class StringCommandMapper {
|
||||
|
||||
public DomainCommand fromString(String commandKey) {
|
||||
if (commandKey == null) {
|
||||
throw new IllegalArgumentException("commandKey must not be null");
|
||||
}
|
||||
return switch (commandKey) {
|
||||
case "order.pay" -> DomainCommand.ORDER_PAY;
|
||||
case "order.ship" -> DomainCommand.ORDER_SHIP;
|
||||
case "order.cancel" -> DomainCommand.ORDER_CANCEL;
|
||||
case "document.submit" -> DomainCommand.DOCUMENT_SUBMIT;
|
||||
case "document.approve" -> DomainCommand.DOCUMENT_APPROVE;
|
||||
case "document.reject" -> DomainCommand.DOCUMENT_REJECT;
|
||||
default -> throw new IllegalArgumentException("Unknown command: " + commandKey);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package click.kamil.examples.statemachine.layered.document;
|
||||
|
||||
public enum DocumentState {
|
||||
DRAFT,
|
||||
SUBMITTED,
|
||||
APPROVED,
|
||||
REJECTED
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package click.kamil.examples.statemachine.layered.document;
|
||||
|
||||
public enum DocumentTransitionEvent {
|
||||
SUBMIT,
|
||||
APPROVE,
|
||||
REJECT
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package click.kamil.examples.statemachine.layered.document.config;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.document.DocumentState;
|
||||
import click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
public abstract class AbstractDocumentStateMachineConfiguration
|
||||
extends EnumStateMachineConfigurerAdapter<DocumentState, DocumentTransitionEvent> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<DocumentState, DocumentTransitionEvent> states)
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(DocumentState.DRAFT)
|
||||
.state(DocumentState.SUBMITTED)
|
||||
.state(DocumentState.APPROVED);
|
||||
configureAdditionalStates(states);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<DocumentState, DocumentTransitionEvent> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(DocumentState.DRAFT)
|
||||
.target(DocumentState.SUBMITTED)
|
||||
.event(DocumentTransitionEvent.SUBMIT)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(DocumentState.SUBMITTED)
|
||||
.target(DocumentState.APPROVED)
|
||||
.event(DocumentTransitionEvent.APPROVE);
|
||||
configureAdditionalTransitions(transitions);
|
||||
}
|
||||
|
||||
protected void configureAdditionalStates(StateMachineStateConfigurer<DocumentState, DocumentTransitionEvent> states)
|
||||
throws Exception {
|
||||
}
|
||||
|
||||
protected void configureAdditionalTransitions(
|
||||
StateMachineTransitionConfigurer<DocumentState, DocumentTransitionEvent> transitions) throws Exception {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package click.kamil.examples.statemachine.layered.document.config;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.document.DocumentState;
|
||||
import click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory(name = "documentStateMachineFactory")
|
||||
public class StandardDocumentStateMachineConfiguration extends AbstractDocumentStateMachineConfiguration {
|
||||
|
||||
@Override
|
||||
protected void configureAdditionalStates(StateMachineStateConfigurer<DocumentState, DocumentTransitionEvent> states)
|
||||
throws Exception {
|
||||
states.state(DocumentState.REJECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureAdditionalTransitions(
|
||||
StateMachineTransitionConfigurer<DocumentState, DocumentTransitionEvent> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(DocumentState.SUBMITTED)
|
||||
.target(DocumentState.REJECTED)
|
||||
.event(DocumentTransitionEvent.REJECT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package click.kamil.examples.statemachine.layered.order;
|
||||
|
||||
public enum OrderState {
|
||||
NEW,
|
||||
PAID,
|
||||
SHIPPED,
|
||||
CANCELLED
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package click.kamil.examples.statemachine.layered.order;
|
||||
|
||||
public enum OrderTransitionEvent {
|
||||
PAY,
|
||||
SHIP,
|
||||
CANCEL
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package click.kamil.examples.statemachine.layered.order;
|
||||
|
||||
public class PayRichOrderEvent implements RichOrderEvent {
|
||||
@Override
|
||||
public OrderTransitionEvent getType() {
|
||||
return OrderTransitionEvent.PAY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package click.kamil.examples.statemachine.layered.order;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.order.OrderTransitionEvent;
|
||||
|
||||
public interface RichOrderEvent {
|
||||
OrderTransitionEvent getType();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package click.kamil.examples.statemachine.layered.order;
|
||||
|
||||
public class ShipRichOrderEvent implements RichOrderEvent {
|
||||
@Override
|
||||
public OrderTransitionEvent getType() {
|
||||
return OrderTransitionEvent.SHIP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package click.kamil.examples.statemachine.layered.order.config;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.order.OrderState;
|
||||
import click.kamil.examples.statemachine.layered.order.OrderTransitionEvent;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
public abstract class AbstractOrderStateMachineConfiguration
|
||||
extends EnumStateMachineConfigurerAdapter<OrderState, OrderTransitionEvent> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<OrderState, OrderTransitionEvent> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(OrderState.NEW)
|
||||
.state(OrderState.PAID)
|
||||
.state(OrderState.SHIPPED);
|
||||
configureAdditionalStates(states);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<OrderState, OrderTransitionEvent> transitions)
|
||||
throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(OrderState.NEW)
|
||||
.target(OrderState.PAID)
|
||||
.event(OrderTransitionEvent.PAY)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(OrderState.PAID)
|
||||
.target(OrderState.SHIPPED)
|
||||
.event(OrderTransitionEvent.SHIP);
|
||||
configureAdditionalTransitions(transitions);
|
||||
}
|
||||
|
||||
protected void configureAdditionalStates(StateMachineStateConfigurer<OrderState, OrderTransitionEvent> states)
|
||||
throws Exception {
|
||||
}
|
||||
|
||||
protected void configureAdditionalTransitions(
|
||||
StateMachineTransitionConfigurer<OrderState, OrderTransitionEvent> transitions) throws Exception {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package click.kamil.examples.statemachine.layered.order.config;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.order.OrderState;
|
||||
import click.kamil.examples.statemachine.layered.order.OrderTransitionEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory(name = "orderStateMachineFactory")
|
||||
public class StandardOrderStateMachineConfiguration extends AbstractOrderStateMachineConfiguration {
|
||||
|
||||
@Override
|
||||
protected void configureAdditionalStates(StateMachineStateConfigurer<OrderState, OrderTransitionEvent> states)
|
||||
throws Exception {
|
||||
states.state(OrderState.CANCELLED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureAdditionalTransitions(
|
||||
StateMachineTransitionConfigurer<OrderState, OrderTransitionEvent> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(OrderState.PAID)
|
||||
.target(OrderState.CANCELLED)
|
||||
.event(OrderTransitionEvent.CANCEL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package click.kamil.examples.statemachine.layered.web;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.command.DomainCommand;
|
||||
import click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher;
|
||||
import click.kamil.examples.statemachine.layered.dispatch.StringCommandMapper;
|
||||
import click.kamil.examples.statemachine.layered.order.PayRichOrderEvent;
|
||||
import click.kamil.examples.statemachine.layered.order.ShipRichOrderEvent;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CommandGateway {
|
||||
|
||||
private final StringCommandMapper stringCommandMapper;
|
||||
private final CentralEventDispatcher centralEventDispatcher;
|
||||
|
||||
public CommandGateway(StringCommandMapper stringCommandMapper, CentralEventDispatcher centralEventDispatcher) {
|
||||
this.stringCommandMapper = stringCommandMapper;
|
||||
this.centralEventDispatcher = centralEventDispatcher;
|
||||
}
|
||||
|
||||
public void payOrder() {
|
||||
centralEventDispatcher.orderPay();
|
||||
}
|
||||
|
||||
public void shipOrder() {
|
||||
centralEventDispatcher.orderShip();
|
||||
}
|
||||
|
||||
public void cancelOrder() {
|
||||
centralEventDispatcher.orderCancel();
|
||||
}
|
||||
|
||||
public void payOrderViaRichEvent(PayRichOrderEvent event) {
|
||||
centralEventDispatcher.orderPayViaRichEvent(event);
|
||||
}
|
||||
|
||||
public void shipOrderViaRichEvent(ShipRichOrderEvent event) {
|
||||
centralEventDispatcher.orderShipViaRichEvent(event);
|
||||
}
|
||||
|
||||
public void submitDocument() {
|
||||
centralEventDispatcher.documentSubmit();
|
||||
}
|
||||
|
||||
public void approveDocument() {
|
||||
centralEventDispatcher.documentApprove();
|
||||
}
|
||||
|
||||
public void rejectDocument() {
|
||||
centralEventDispatcher.documentReject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Full two-hop dispatch used by {@link GenericCommandController}:
|
||||
* string key → {@link DomainCommand} → transition event.
|
||||
*/
|
||||
public void executeViaMapper(String commandKey) {
|
||||
DomainCommand command = stringCommandMapper.fromString(commandKey);
|
||||
centralEventDispatcher.dispatch(command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package click.kamil.examples.statemachine.layered.web;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/documents")
|
||||
public class DocumentController {
|
||||
|
||||
private final CommandGateway commandGateway;
|
||||
|
||||
public DocumentController(CommandGateway commandGateway) {
|
||||
this.commandGateway = commandGateway;
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
public void submit() {
|
||||
commandGateway.submitDocument();
|
||||
}
|
||||
|
||||
@PostMapping("/approve")
|
||||
public void approve() {
|
||||
commandGateway.approveDocument();
|
||||
}
|
||||
|
||||
@PostMapping("/reject")
|
||||
public void reject() {
|
||||
commandGateway.rejectDocument();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package click.kamil.examples.statemachine.layered.web;
|
||||
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/commands")
|
||||
public class GenericCommandController {
|
||||
|
||||
private final CommandGateway commandGateway;
|
||||
|
||||
public GenericCommandController(CommandGateway commandGateway) {
|
||||
this.commandGateway = commandGateway;
|
||||
}
|
||||
|
||||
@PostMapping("/{commandKey}")
|
||||
public void execute(@PathVariable String commandKey) {
|
||||
commandGateway.executeViaMapper(commandKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package click.kamil.examples.statemachine.layered.web;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/orders")
|
||||
public class OrderController {
|
||||
|
||||
private final CommandGateway commandGateway;
|
||||
|
||||
public OrderController(CommandGateway commandGateway) {
|
||||
this.commandGateway = commandGateway;
|
||||
}
|
||||
|
||||
@PostMapping("/pay")
|
||||
public void pay() {
|
||||
commandGateway.payOrder();
|
||||
}
|
||||
|
||||
@PostMapping("/ship")
|
||||
public void ship() {
|
||||
commandGateway.shipOrder();
|
||||
}
|
||||
|
||||
@PostMapping("/cancel")
|
||||
public void cancel() {
|
||||
commandGateway.cancelOrder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package click.kamil.examples.statemachine.layered.web;
|
||||
|
||||
import click.kamil.examples.statemachine.layered.order.PayRichOrderEvent;
|
||||
import click.kamil.examples.statemachine.layered.order.ShipRichOrderEvent;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/orders/rich")
|
||||
public class RichOrderController {
|
||||
|
||||
private final CommandGateway commandGateway;
|
||||
|
||||
public RichOrderController(CommandGateway commandGateway) {
|
||||
this.commandGateway = commandGateway;
|
||||
}
|
||||
|
||||
@PostMapping("/pay")
|
||||
public void payViaRichEvent() {
|
||||
commandGateway.payOrderViaRichEvent(new PayRichOrderEvent());
|
||||
}
|
||||
|
||||
@PostMapping("/ship")
|
||||
public void shipViaRichEvent() {
|
||||
commandGateway.shipOrderViaRichEvent(new ShipRichOrderEvent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package click.kamil.examples.statemachine.layered.web;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Demonstrates the full two-hop dispatch chain with string literals:
|
||||
* string key → {@link click.kamil.examples.statemachine.layered.command.DomainCommand}
|
||||
* → transition event.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/string-dispatch")
|
||||
public class StringDispatchController {
|
||||
|
||||
private final CommandGateway commandGateway;
|
||||
|
||||
public StringDispatchController(CommandGateway commandGateway) {
|
||||
this.commandGateway = commandGateway;
|
||||
}
|
||||
|
||||
@PostMapping("/orders/pay")
|
||||
public void payWithStringKey() {
|
||||
commandGateway.executeViaMapper("order.pay");
|
||||
}
|
||||
|
||||
@PostMapping("/orders/ship")
|
||||
public void shipWithStringKey() {
|
||||
commandGateway.executeViaMapper("order.ship");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user