UI はローカライズ済みです。スキルの紹介と説明文書は選択した言語へ AI が自動翻訳し、コード・コマンド・パス・専門用語は原文のままです。原作者の文書が優先されます。
スキルを探す
ドキュメント · SKILL.md

spring-statemachine

spring-statemachine

Spring Statemachine for building finite state machine applications. Covers states, transitions, guards, actions, persistence, and hierarchical states. USE WHEN: user mentions "spring statemachine", "state machine Spring", "workflow Spring", "finite state machine", "order state", "document lifecycle", "guards actions transitions" DO NOT USE FOR: simple sta…

Independent GitHub snapshot · Automated classification · Original documentation governs

スキルの説明

GitHub

name: spring-statemachine description: | Spring Statemachine for building finite state machine applications. Covers states, transitions, guards, actions, persistence, and hierarchical states.

USE WHEN: user mentions "spring statemachine", "state machine Spring", "workflow Spring", "finite state machine", "order state", "document lifecycle", "guards actions transitions"

DO NOT USE FOR: simple status flags - use enum fields, complex workflow orchestration - use spring-integration or Camunda, business rules engine - use Drools allowed-tools: Read, Grep, Glob, Write, Edit

Spring Statemachine - Quick Reference

Full Reference: See advanced.md for @WithStateMachine annotation, hierarchical states, choice pseudostates, timer transitions, listeners, persistence, and testing.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-statemachine for comprehensive documentation.

Dependencies

<!-- Spring Statemachine 4.0+ for Spring Boot 3.x -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>4.0.0</version>
</dependency>
<!-- For persistence -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-data-jpa</artifactId>
    <version>4.0.0</version>
</dependency>

Core Concepts

┌─────────────────────────────────────────────────────────────┐
│                    State Machine                            │
│                                                             │
│  ┌──────────┐    EVENT_A     ┌──────────┐                  │
│  │  STATE_1 │ ─────────────▶ │  STATE_2 │                  │
│  │ (initial)│                │          │                  │
│  └──────────┘                └────┬─────┘                  │
│                                   │                         │
│                              EVENT_B                        │
│                                   │                         │
│                                   ▼                         │
│                             ┌──────────┐                    │
│                             │  STATE_3 │                    │
│                             │  (final) │                    │
│                             └──────────┘                    │
└─────────────────────────────────────────────────────────────┘

Basic Configuration

States and Events

public enum OrderStates {
    CREATED,
    PENDING_PAYMENT,
    PAID,
    PROCESSING,
    SHIPPED,
    DELIVERED,
    CANCELLED,
    REFUNDED
}

public enum OrderEvents {
    SUBMIT,
    PAY,
    PROCESS,
    SHIP,
    DELIVER,
    CANCEL,
    REFUND
}

State Machine Configuration

@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig
        extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states)
            throws Exception {
        states
            .withStates()
                .initial(OrderStates.CREATED)
                .state(OrderStates.PENDING_PAYMENT)
                .state(OrderStates.PAID)
                .state(OrderStates.PROCESSING)
                .state(OrderStates.SHIPPED)
                .end(OrderStates.DELIVERED)
                .end(OrderStates.CANCELLED)
                .end(OrderStates.REFUNDED);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions)
            throws Exception {
        transitions
            .withExternal()
                .source(OrderStates.CREATED)
                .target(OrderStates.PENDING_PAYMENT)
                .event(OrderEvents.SUBMIT)
            .and()
            .withExternal()
                .source(OrderStates.PENDING_PAYMENT)
                .target(OrderStates.PAID)
                .event(OrderEvents.PAY)
                .guard(paymentValidGuard())
                .action(paymentAction())
            .and()
            .withExternal()
                .source(OrderStates.PAID)
                .target(OrderStates.PROCESSING)
                .event(OrderEvents.PROCESS)
            .and()
            .withExternal()
                .source(OrderStates.PROCESSING)
                .target(OrderStates.SHIPPED)
                .event(OrderEvents.SHIP)
                .action(shipAction())
            .and()
            .withExternal()
                .source(OrderStates.SHIPPED)
                .target(OrderStates.DELIVERED)
                .event(OrderEvents.DELIVER)
            .and()
            // Cancel from multiple states
            .withExternal()
                .source(OrderStates.CREATED)
                .target(OrderStates.CANCELLED)
                .event(OrderEvents.CANCEL)
            .and()
            .withExternal()
                .source(OrderStates.PENDING_PAYMENT)
                .target(OrderStates.CANCELLED)
                .event(OrderEvents.CANCEL);
    }
}

Guards and Actions

Guards (Conditions)

@Configuration
public class OrderGuards {

    @Bean
    public Guard<OrderStates, OrderEvents> paymentValidGuard() {
        return context -> {
            Order order = (Order) context.getExtendedState()
                .getVariables().get("order");
            PaymentInfo payment = (PaymentInfo) context.getMessage()
                .getHeaders().get("payment");

            return payment != null &&
                   payment.getAmount().compareTo(order.getTotal()) >= 0;
        };
    }

    @Bean
    public Guard<OrderStates, OrderEvents> refundEligibleGuard() {
        return context -> {
            Order order = (Order) context.getExtendedState()
                .getVariables().get("order");
            LocalDateTime deliveredAt = order.getDeliveredAt();

            // Refund within 30 days
            return deliveredAt != null &&
                   deliveredAt.plusDays(30).isAfter(LocalDateTime.now());
        };
    }
}

Actions

@Configuration
public class OrderActions {

    @Bean
    public Action<OrderStates, OrderEvents> paymentAction() {
        return context -> {
            Order order = (Order) context.getExtendedState()
                .getVariables().get("order");
            PaymentInfo payment = (PaymentInfo) context.getMessage()
                .getHeaders().get("payment");

            order.setPaymentId(payment.getTransactionId());
            order.setPaidAt(LocalDateTime.now());
            orderRepository.save(order);

            log.info("Payment processed for order: {}", order.getId());
        };
    }

    @Bean
    public Action<OrderStates, OrderEvents> shipAction() {
        return context -> {
            Order order = (Order) context.getExtendedState()
                .getVariables().get("order");

            String trackingNumber = shippingService.createShipment(order);
            order.setTrackingNumber(trackingNumber);
            order.setShippedAt(LocalDateTime.now());
            orderRepository.save(order);

            notificationService.sendShippingNotification(order);
        };
    }

    // Error action
    @Bean
    public Action<OrderStates, OrderEvents> errorAction() {
        return context -> {
            Exception exception = context.getException();
            log.error("State machine error: {}", exception.getMessage());
        };
    }
}

State Machine Service

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderStateMachineService {

    private final StateMachineFactory<OrderStates, OrderEvents> factory;
    private final OrderRepository orderRepository;

    public void processEvent(Long orderId, OrderEvents event, Map<String, Object> headers) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));

        StateMachine<OrderStates, OrderEvents> sm = build(order);

        Message<OrderEvents> message = MessageBuilder
            .withPayload(event)
            .copyHeaders(headers)
            .setHeader("orderId", orderId)
            .build();

        // Reactive event sending (Spring Statemachine 4.0+)
        sm.sendEvent(Mono.just(message))
            .doOnComplete(() -> log.info("Event {} processed for order {}", event, orderId))
            .doOnError(e -> log.error("Error processing event: {}", e.getMessage()))
            .subscribe();
    }

    private StateMachine<OrderStates, OrderEvents> build(Order order) {
        StateMachine<OrderStates, OrderEvents> sm = factory.getStateMachine(
            order.getId().toString()
        );

        sm.stopReactively().block();

        sm.getStateMachineAccessor()
            .doWithAllRegions(accessor -> {
                accessor.resetStateMachineReactively(
                    new DefaultStateMachineContext<>(
                        order.getState(), null, null, null
                    )
                ).block();
            });

        sm.getExtendedState().getVariables().put("order", order);
        sm.startReactively().block();

        return sm;
    }

    public boolean canTransition(Long orderId, OrderEvents event) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        StateMachine<OrderStates, OrderEvents> sm = build(order);

        return sm.getTransitions().stream()
            .anyMatch(t -> t.getSource().getId() == order.getState() &&
                          t.getTrigger().getEvent() == event);
    }
}

Best Practices

DoDon't
Define clear states and eventsUse generic state names
Use guards for validationPut business logic in transitions
Persist state machine stateKeep state only in memory
Handle all edge casesAssume happy path only
Use listeners for monitoringIgnore state changes

Production Checklist

  • All states and transitions defined
  • Guards validate business rules
  • Actions handle side effects
  • State persistence configured
  • Error handling implemented
  • Listeners for monitoring/logging
  • Concurrent access handled
  • Timeout transitions if needed
  • State machine tested thoroughly
  • Documentation of state diagram

When NOT to Use This Skill

  • Simple status fields - Use enum with database column
  • Complex orchestration - Use Spring Integration or Camunda BPM
  • Business rules - Use Drools or similar rules engine
  • Event-driven sagas - Consider Spring Cloud Stream

Anti-Patterns

Anti-PatternProblemSolution
State in memory onlyLost on restartUse JPA persistence
Complex logic in guardsHard to testExtract to services
Missing error actionsSilent failuresAdd error handling actions
Single machine instanceConcurrency issuesUse factory pattern
No state validationInvalid transitionsImplement guards properly

Quick Troubleshooting

ProblemDiagnosticFix
Event not acceptedCheck current stateVerify transition exists
Guard always falseDebug guard logicLog guard evaluation
Action not executedCheck transition configVerify action is attached
State not persistedCheck persister configConfigure JPA persister
Machine not startingCheck initial stateVerify initial() configured

Reference Documentation

保存したスキル