{"id":31999572,"url":"https://github.com/mahdibohloul/statemachine","last_synced_at":"2026-05-01T23:30:56.377Z","repository":{"id":318614258,"uuid":"1071490526","full_name":"mahdibohloul/statemachine","owner":"mahdibohloul","description":"Lightweight, reactive state-machine building blocks for Jvm and Spring.","archived":false,"fork":false,"pushed_at":"2025-11-30T14:54:08.000Z","size":110,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-02T21:37:05.741Z","etag":null,"topics":["java","kotlin","project-reactor","reactive-programming","spring","statemachine"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mahdibohloul.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-07T12:24:03.000Z","updated_at":"2025-11-30T14:53:21.000Z","dependencies_parsed_at":"2025-10-08T08:33:38.441Z","dependency_job_id":"001a63cc-a142-48dc-a5c2-d15be410625f","html_url":"https://github.com/mahdibohloul/statemachine","commit_stats":null,"previous_names":["mahdibohloul/statemachine"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mahdibohloul/statemachine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahdibohloul%2Fstatemachine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahdibohloul%2Fstatemachine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahdibohloul%2Fstatemachine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahdibohloul%2Fstatemachine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mahdibohloul","download_url":"https://codeload.github.com/mahdibohloul/statemachine/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mahdibohloul%2Fstatemachine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32517070,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","kotlin","project-reactor","reactive-programming","spring","statemachine"],"created_at":"2025-10-15T14:32:22.419Z","updated_at":"2026-05-01T23:30:56.363Z","avatar_url":"https://github.com/mahdibohloul.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"## statemachine\n\nLightweight, reactive state-machine building blocks for Kotlin and Spring.\n\nThis library helps you implement robust, composable state transitions with Project Reactor, providing a clear separation\nof concerns across three phases: before, during, and after transformation.\n\n### Why this library?\n\n- **Reactive-first**: Built on Reactor `Mono` for non-blocking flows.\n- **Composable**: Chain actions, guards, and error handlers with simple operators.\n- **Type-safe**: Generics enforce correct container/state usage.\n- **Spring-friendly**: Factories and annotations integrate with Spring's `ApplicationContext`.\n- **Transaction-aware**: Execute after-commit actions only when a transaction successfully commits.\n- **Production-ready**: Used in production at Tapsi for complex delivery request state management.\n\n### At a glance\n\n```kotlin\n@StateMachineState\nclass OrderProcessingTransformer(\n  private val containerProvider: OrderContainerProvider,\n  private val responseProvider: OrderResponseProvider,\n  private val actionFactory: OnTransformationActionFactory,\n  private val guardFactory: OnTransformationGuardFactory,\n) : StateTransformerAdapter\u003cOrderRequest, OrderContainer, Order, OrderStatus\u003e() {\n  \n  override fun getState(): OrderStatus = OrderStatus.Processing\n  override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE\n\n  override fun configure(configurer: StateMachineConfigurer\u003cOrderRequest, OrderContainer, Order, OrderStatus\u003e) {\n    configurer.apply {\n      sourceState = OrderStatus.Created\n      targetState = OrderStatus.Processing\n      transformationContainerProvider = containerProvider\n      transformationResponseProvider = responseProvider\n      onTransformationErrorHandler = OrderErrorHandler()\n    }\n  }\n\n  override fun configure(configurer: BeforeTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      beforeTransformationGuard = guardFactory.getGuard(\n        PaymentValidationGuard::class,\n        InventoryCheckGuard::class\n      )\n      beforeTransformationAction = actionFactory.getAction(\n        EnrichOrderAction::class,\n        ReserveInventoryAction::class\n      )\n    }\n  }\n\n  override fun configure(configurer: DuringTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      duringTransformationAction = actionFactory.getAction(\n        ProcessPaymentAction::class,\n        SaveOrderAction::class\n      )\n    }\n  }\n\n  override fun configure(configurer: AfterTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      afterTransformationAction = actionFactory.getAction(\n        SendConfirmationAction::class\n      )\n      afterCommitTransactionAction = actionFactory.getAction(\n        NotifyWarehouseAction::class\n      )\n    }\n  }\n}\n```\n\n---\n\n## Installation\n\nAvailable as a Maven artifact.\n\nMaven:\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.mahdibohloul\u003c/groupId\u003e\n    \u003cartifactId\u003estatemachine\u003c/artifactId\u003e\n    \u003cversion\u003e0.11.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nMinimums:\n\n- Kotlin 1.9+\n- Java 21+\n- Spring 6.1+/Boot 3.3+ (optional, for Spring integration)\n- Reactor 3.7+\n\n---\n\n## Core Concepts\n\n### Basic Types\n- **States (`TEnum : Enum\u003c*\u003e`)**: Your domain states (e.g., `Created`, `Processing`, `Completed`).\n- **Request (`TransformationRequest`)**: Marker type representing the input to a transformation.\n- **Container (`TransformationContainer\u003cTEnum\u003e`)**: Holds the working state and data during a transformation, including\n  `source` and `target` states.\n- **Response**: The final output type returned after successful transformation.\n\n### Behaviors\n- **Actions (`OnTransformationAction\u003cTContainer\u003e`)**: Mutate or enrich the container. Can be chained using `andThen`.\n- **Guards (`OnTransformationGuard\u003cTContainer\u003e`)**: Validate whether execution should proceed. Return `Mono\u003cBoolean\u003e`.\n- **Error Handler (`OnTransformationErrorHandler\u003cTRequest, TResponse\u003e`)**: Maps errors to a fallback `Mono\u003cTResponse\u003e`.\n\n### Providers\n- **Container Provider (`TransformationContainerProvider\u003cTRequest, TContainer, TEnum\u003e`)**: Builds the container from the incoming request and desired states.\n- **Response Provider (`TransformationResponseProvider\u003cTRequest, TContainer, TResponse\u003e`)**: Maps the final container to your response type.\n\n### Configuration Phases\n- **Before Transformation**: Validation guards and pre-processing actions\n- **During Transformation**: Core domain logic and state changes\n- **After Transformation**: Post-processing, notifications, and after-commit hooks\n\n### Factories (Spring Integration)\n- **Action Factory (`OnTransformationActionFactory`)**: Composes multiple actions from Spring beans\n- **Guard Factory (`OnTransformationGuardFactory`)**: Composes multiple guards from Spring beans\n- **Error Handler Factory (`OnTransformationErrorHandlerFactory`)**: Composes multiple error handlers from Spring beans\n\n---\n\n## Quickstart\n\n### 1. Define Your Domain Types\n\n```kotlin\n// States\nenum class OrderStatus { \n  Created, Processing, Shipped, Delivered, Cancelled \n}\n\n// Request\nsealed class OrderRequest : TransformationRequest {\n  data class Create(val customerId: String, val items: List\u003cItem\u003e) : OrderRequest()\n  data class Cancel(val orderId: String, val reason: String) : OrderRequest()\n}\n\n// Container\ndata class OrderContainer(\n  val order: Order,\n  val customer: Customer,\n  override val source: OrderStatus? = null,\n  override val target: OrderStatus? = null,\n) : TransformationContainer\u003cOrderStatus\u003e\n```\n\n### 2. Implement Actions and Guards\n\n```kotlin\n@Component\nclass ValidatePaymentAction : OnTransformationAction\u003cOrderContainer\u003e {\n  override fun execute(container: OrderContainer): Mono\u003cOrderContainer\u003e =\n    Mono.fromCallable {\n      // Validate payment logic\n      container.copy(order = container.order.copy(paymentValidated = true))\n    }\n}\n\n@Component\nclass InventoryCheckGuard : OnTransformationGuard\u003cOrderContainer\u003e {\n  // Requires: import io.github.mahdibohloul.statemachine.guards.GuardDecision\n  //          import io.github.mahdibohloul.statemachine.StateMachineErrorCodeString\n  override fun executeDecision(container: OrderContainer): Mono\u003cGuardDecision\u003e =\n    Mono.fromCallable {\n      val allItemsAvailable = container.order.items.all { item -\u003e checkInventory(item) }\n      if (allItemsAvailable) {\n        GuardDecision.Allow\n      } else {\n        GuardDecision.Deny(\n          errorCode = StateMachineErrorCodeString.GuardValidationFailed,\n          cause = InsufficientInventoryException(container.order.items)\n        )\n      }\n    }\n}\n```\n\n### 3. Create Container and Response Providers\n\n```kotlin\n@Component\nclass OrderContainerProvider : TransformationContainerProvider\u003cOrderRequest, OrderContainer, OrderStatus\u003e {\n  override fun provideContainer(\n    request: OrderRequest,\n    source: OrderStatus?,\n    target: OrderStatus?\n  ): Mono\u003cOrderContainer\u003e = when (request) {\n    is OrderRequest.Create -\u003e Mono.fromCallable {\n      OrderContainer(\n        order = Order.fromRequest(request),\n        customer = getCustomer(request.customerId),\n        source = source,\n        target = target\n      )\n    }\n    is OrderRequest.Cancel -\u003e Mono.fromCallable {\n      OrderContainer(\n        order = getOrder(request.orderId),\n        customer = getCustomerByOrder(request.orderId),\n        source = source,\n        target = target\n      )\n    }\n  }\n}\n\n@Component\nclass OrderResponseProvider : TransformationResponseProvider\u003cOrderRequest, OrderContainer, Order\u003e {\n  override fun provideResponse(request: OrderRequest, container: OrderContainer): Mono\u003cOrder\u003e =\n    Mono.just(container.order)\n}\n```\n\n### 4. Implement the Transformer\n\n```kotlin\n@StateMachineState\nclass OrderProcessingTransformer(\n  private val containerProvider: OrderContainerProvider,\n  private val responseProvider: OrderResponseProvider,\n  private val actionFactory: OnTransformationActionFactory,\n  private val guardFactory: OnTransformationGuardFactory,\n) : StateTransformerAdapter\u003cOrderRequest.Create, OrderContainer, Order, OrderStatus\u003e() {\n  \n  override fun getState(): OrderStatus = OrderStatus.Processing\n  override fun getOrder(): Int = Ordered.HIGHEST_PRECEDENCE\n\n  override fun configure(configurer: StateMachineConfigurer\u003cOrderRequest.Create, OrderContainer, Order, OrderStatus\u003e) {\n    configurer.apply {\n      sourceState = OrderStatus.Created\n      targetState = OrderStatus.Processing\n      transformationContainerProvider = containerProvider\n      transformationResponseProvider = responseProvider\n      onTransformationErrorHandler = OrderErrorHandler()\n    }\n  }\n\n  override fun configure(configurer: BeforeTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      beforeTransformationGuard = guardFactory.getGuard(\n        InventoryCheckGuard::class,\n        PaymentValidationGuard::class\n      )\n      beforeTransformationAction = actionFactory.getAction(\n        ValidatePaymentAction::class,\n        ReserveInventoryAction::class\n      )\n    }\n  }\n\n  override fun configure(configurer: DuringTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      duringTransformationAction = actionFactory.getAction(\n        ProcessPaymentAction::class,\n        SaveOrderAction::class\n      )\n    }\n  }\n\n  override fun configure(configurer: AfterTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      afterTransformationAction = actionFactory.getAction(\n        SendConfirmationAction::class\n      )\n      afterCommitTransactionAction = actionFactory.getAction(\n        NotifyWarehouseAction::class\n      )\n    }\n  }\n}\n```\n\n### 5. Execute the Transformation\n\n```kotlin\n@RestController\nclass OrderController(\n  private val orderProcessingTransformer: OrderProcessingTransformer\n) {\n  \n  @PostMapping(\"/orders\")\n  fun createOrder(@RequestBody request: CreateOrderRequest): Mono\u003cOrder\u003e =\n    orderProcessingTransformer.transform(\n      OrderRequest.Create(request.customerId, request.items)\n    )\n}\n```\n\n---\n\n## Advanced Usage\n\n### Composing Behaviors\n\nUse `andThen` to chain actions and error handlers:\n\n```kotlin\n// Chain multiple actions\nval compositeAction = ValidatePaymentAction() \n  .andThen(ReserveInventoryAction())\n  .andThen(SendNotificationAction())\n\n// Chain multiple error handlers\nval compositeErrorHandler = LogErrorHandler()\n  .andThen(FallbackResponseHandler())\n  .andThen(RetryHandler())\n```\n\n### Transformer Ordering\n\nControl execution order using `getOrder()`:\n\n```kotlin\nobject TransformerOrder {\n  const val HIGH_PRIORITY = Ordered.HIGHEST_PRECEDENCE\n  const val MEDIUM_PRIORITY = Ordered.HIGHEST_PRECEDENCE + 10\n  const val LOW_PRIORITY = Ordered.LOWEST_PRECEDENCE\n}\n\nclass HighPriorityTransformer : StateTransformerAdapter\u003c...\u003e() {\n  override fun getOrder(): Int = TransformerOrder.HIGH_PRIORITY\n}\n```\n\n### Custom Validation in canHandle()\n\n```kotlin\nabstract class AbstractOrderTransformer\u003cTRequest : OrderRequest\u003e : \n  StateTransformerAdapter\u003cTRequest, OrderContainer, Order, OrderStatus\u003e() {\n  \n  override fun canHandle(transformerIdentifier: StateMachineStateFactory.TransformerIdentifier) {\n    super.canHandle(transformerIdentifier)\n    val request = transformerIdentifier.getTransformationRequest\u003cOrderRequest\u003e()\n    \n    require(isSupportedOrderType(request)) {\n      \"Order type ${request.type} is not supported by this transformer\"\n    }\n  }\n  \n  protected abstract fun isSupportedOrderType(request: OrderRequest): Boolean\n}\n```\n\n### Domain-Specific Error Handling\n\n```kotlin\n@Component\nclass OrderErrorHandler : OnTransformationErrorHandler\u003cOrderRequest, Order\u003e {\n  override fun onError(request: OrderRequest, error: Throwable): Mono\u003cOrder\u003e =\n    when (error) {\n      is InsufficientInventoryException -\u003e \n        Mono.error(OrderException.InventoryUnavailable(error.itemIds))\n      is PaymentFailedException -\u003e \n        Mono.error(OrderException.PaymentDeclined(error.reason))\n      is DuplicateKeyException -\u003e \n        Mono.error(OrderException.DuplicateOrder(request.orderId))\n      else -\u003e Mono.error(error)\n    }\n}\n```\n\n### Guard Decision API and Thread Safety\n\nThe library provides a thread-safe guard validation API through the `GuardDecision` sealed interface. This prevents concurrency issues when multiple threads access stateless guards.\n\n**Why GuardDecision?**\n\nIn concurrent environments, stateless guards that rely on instance methods to provide error codes can experience race conditions. The `GuardDecision` API captures error codes and causes in immutable data structures at decision time, ensuring thread-safe error handling.\n\n**Using GuardDecision:**\n\n```kotlin\n@Component\nclass PaymentValidationGuard : OnTransformationGuard\u003cOrderContainer\u003e {\n  override fun executeDecision(container: OrderContainer): Mono\u003cGuardDecision\u003e =\n    Mono.fromCallable {\n      val isValid = validatePayment(container.order.paymentMethod)\n      if (isValid) {\n        GuardDecision.Allow\n      } else {\n        GuardDecision.Deny(\n          errorCode = CustomErrorCode.PaymentValidationFailed,\n          cause = PaymentValidationException(\"Invalid payment method\")\n        )\n      }\n    }\n}\n```\n\n**Migrating from 0.10.x:** If you previously implemented `execute(container): Mono\u003cBoolean\u003e` (and optional `getValidationFailure*` hooks), replace that with `executeDecision(container): Mono\u003cGuardDecision\u003e` and return `GuardDecision.Allow` or `GuardDecision.Deny(errorCode, cause)`.\n\n**Benefits:**\n- **Thread-safe**: Error codes are captured in immutable `GuardDecision.Deny` objects\n- **Type-safe**: Sealed interface ensures exhaustive handling\n- **Rich error information**: Error codes and causes are explicitly captured\n\n---\n\n## Spring Integration\n\n### Automatic Bean Discovery\n\nAnnotate state-specific components with `@StateMachineState`:\n\n```kotlin\n@StateMachineState(\"Processing\")\nclass OrderProcessingTransformer(\n  // dependencies injected by Spring\n) : StateTransformerAdapter\u003c...\u003e()\n\n@StateMachineState(\"Approved\") \nclass OrderApprovedTransformer(\n  // dependencies injected by Spring\n) : StateTransformerAdapter\u003c...\u003e()\n```\n\n### Factory-Based Composition\n\nUse factories to compose multiple behaviors from Spring beans:\n\n```kotlin\n@Configuration\nclass StateMachineConfiguration {\n  \n  @Bean\n  fun actionFactory(applicationContext: ApplicationContext) = \n    OnTransformationActionFactory(applicationContext)\n    \n  @Bean  \n  fun guardFactory(applicationContext: ApplicationContext) = \n    OnTransformationGuardFactory(applicationContext)\n}\n\n// In your transformer\noverride fun configure(configurer: BeforeTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n  configurer.apply {\n    // Compose multiple guards from Spring beans\n    beforeTransformationGuard = guardFactory.getGuard(\n      PaymentValidationGuard::class,\n      InventoryCheckGuard::class,\n      CustomerEligibilityGuard::class\n    )\n    \n    // Compose multiple actions from Spring beans  \n    beforeTransformationAction = actionFactory.getAction(\n      ValidatePaymentAction::class,\n      ReserveInventoryAction::class,\n      SendNotificationAction::class\n    )\n  }\n}\n```\n\n### Manual Wiring (Non-Spring)\n\nYou can use the library without Spring by manually wiring dependencies:\n\n```kotlin\nclass ManualOrderTransformer : StateTransformerAdapter\u003cOrderRequest, OrderContainer, Order, OrderStatus\u003e() {\n  \n  override fun configure(configurer: BeforeTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n    configurer.apply {\n      // Manually compose behaviors\n      beforeTransformationGuard = PaymentValidationGuard()\n        .andThen(InventoryCheckGuard())\n        .andThen(CustomerEligibilityGuard())\n        \n      beforeTransformationAction = ValidatePaymentAction()\n        .andThen(ReserveInventoryAction())\n        .andThen(SendNotificationAction())\n    }\n  }\n}\n```\n\n---\n\n## Transactions and After-Commit Hooks\n\nThe library provides transaction-aware execution for after-commit actions. When a reactive transaction is active, \n`afterCommitTransactionAction` will only execute after the transaction successfully commits.\n\n### Basic Usage\n\n```kotlin\noverride fun configure(configurer: AfterTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n  configurer.apply {\n    // This runs immediately after the main transformation\n    afterTransformationAction = actionFactory.getAction(\n      SendConfirmationAction::class,\n      UpdateInventoryAction::class\n    )\n    \n    // This runs only after transaction commit (if transaction is active)\n    afterCommitTransactionAction = actionFactory.getAction(\n      NotifyWarehouseAction::class,\n      SendExternalNotificationAction::class\n    )\n  }\n}\n```\n\n### Transaction Scenarios\n\n```kotlin\n// Scenario 1: No active transaction\n// - afterTransformationAction executes\n// - afterCommitTransactionAction is skipped\n\n// Scenario 2: Active transaction that commits successfully  \n// - afterTransformationAction executes\n// - Transaction commits\n// - afterCommitTransactionAction executes\n\n// Scenario 3: Active transaction that rolls back\n// - afterTransformationAction executes  \n// - Transaction rolls back\n// - afterCommitTransactionAction is skipped\n```\n\n### Use Cases for After-Commit Actions\n\n- **External API calls**: Only notify external systems after data is persisted\n- **Event publishing**: Publish domain events only after successful persistence\n- **Audit logging**: Log state changes only after they're committed\n- **Cache invalidation**: Update caches only after database changes are committed\n\n### Example: Order Processing with Notifications\n\n```kotlin\noverride fun configure(configurer: AfterTransformationConfigurer\u003cOrderContainer, OrderStatus\u003e) {\n  configurer.apply {\n    // Immediate actions (run before commit)\n    afterTransformationAction = actionFactory.getAction(\n      UpdateOrderStatusAction::class,\n      SendCustomerEmailAction::class\n    )\n    \n    // After-commit actions (run only after successful commit)\n    afterCommitTransactionAction = actionFactory.getAction(\n      NotifyWarehouseAction::class,        // External system notification\n      PublishOrderCreatedEvent::class,     // Event publishing\n      UpdateAnalyticsAction::class,        // Analytics tracking\n      InvalidateCacheAction::class         // Cache invalidation\n    )\n  }\n}\n```\n\n---\n\n## Error Handling\n\n### Global Error Handler\n\nSet a global error handler via `StateMachineConfigurer.onTransformationErrorHandler`:\n\n```kotlin\noverride fun configure(configurer: StateMachineConfigurer\u003cOrderRequest, OrderContainer, Order, OrderStatus\u003e) {\n  configurer.apply {\n    onTransformationErrorHandler = object : OnTransformationErrorHandler\u003cOrderRequest, Order\u003e {\n      override fun onError(request: OrderRequest, error: Throwable): Mono\u003cOrder\u003e =\n        when (error) {\n          is InsufficientInventoryException -\u003e \n            Mono.error(OrderException.InventoryUnavailable(error.itemIds))\n          is PaymentFailedException -\u003e \n            Mono.error(OrderException.PaymentDeclined(error.reason))\n          is DuplicateKeyException -\u003e \n            Mono.error(OrderException.DuplicateOrder(request.orderId))\n          else -\u003e Mono.error(error)\n        }\n    }\n  }\n}\n```\n\n### Composed Error Handlers\n\nUse factories to compose multiple error handlers:\n\n```kotlin\noverride fun configure(configurer: StateMachineConfigurer\u003cOrderRequest, OrderContainer, Order, OrderStatus\u003e) {\n  configurer.apply {\n    onTransformationErrorHandler = errorHandlerFactory.getErrorHandler(\n      LogErrorHandler::class,\n      FallbackResponseHandler::class,\n      RetryHandler::class\n    )\n  }\n}\n```\n\n### Common Exceptions\n\n- `StateMachineException.SourceAndTargetAreEqualException`: Source and target states are identical\n- `StateMachineException.NoContainerProviderConfiguredException`: No container provider configured\n\n### Error Handler Examples\n\n```kotlin\n@Component\nclass LogErrorHandler : OnTransformationErrorHandler\u003cOrderRequest, Order\u003e {\n  override fun onError(request: OrderRequest, error: Throwable): Mono\u003cOrder\u003e =\n    Mono.fromRunnable {\n      logger.error(\"Order transformation failed for request: $request\", error)\n    }.then(Mono.error(error))\n}\n\n@Component  \nclass FallbackResponseHandler : OnTransformationErrorHandler\u003cOrderRequest, Order\u003e {\n  override fun onError(request: OrderRequest, error: Throwable): Mono\u003cOrder\u003e =\n    Mono.just(Order.failed(error.message ?: \"Unknown error\"))\n}\n\n@Component\nclass RetryHandler : OnTransformationErrorHandler\u003cOrderRequest, Order\u003e {\n  override fun onError(request: OrderRequest, error: Throwable): Mono\u003cOrder\u003e =\n    if (isRetryableError(error)) {\n      Mono.error(error) // Let retry mechanism handle it\n    } else {\n      Mono.error(error)\n    }\n}\n```\n\n---\n\n## Testing\n\n### Unit Testing Actions and Guards\n\n```kotlin\nclass OrderActionTest {\n  \n  @Test\n  fun `should validate payment successfully`() {\n    // Given\n    val container = OrderContainer(\n      order = Order(paymentValidated = false),\n      customer = Customer(id = \"123\")\n    )\n    val action = ValidatePaymentAction()\n    \n    // When \u0026 Then\n    action.execute(container)\n      .`as`(StepVerifier::create)\n      .expectNextMatches { it.order.paymentValidated }\n      .verifyComplete()\n  }\n  \n  @Test\n  fun `should fail when payment is invalid`() {\n    // Given\n    val container = OrderContainer(\n      order = Order(paymentValidated = false, paymentMethod = \"INVALID\"),\n      customer = Customer(id = \"123\")\n    )\n    val action = ValidatePaymentAction()\n    \n    // When \u0026 Then\n    action.execute(container)\n      .`as`(StepVerifier::create)\n      .verifyError(PaymentValidationException::class.java)\n  }\n}\n\n@Test\nfun `should validate guard successfully with GuardDecision`() {\n  // Given\n  val container = OrderContainer(\n    order = Order(items = listOf(Item(id = \"item1\", quantity = 2))),\n    customer = Customer(id = \"123\")\n  )\n  val guard = InventoryCheckGuard()\n  \n  // When \u0026 Then\n  guard.executeDecision(container)\n    .`as`(StepVerifier::create)\n    .expectNextMatches { it is GuardDecision.Allow }\n    .verifyComplete()\n}\n\n@Test\nfun `should deny guard validation with error code`() {\n  // Given\n  val container = OrderContainer(\n    order = Order(items = listOf(Item(id = \"out-of-stock\", quantity = 100))),\n    customer = Customer(id = \"123\")\n  )\n  val guard = InventoryCheckGuard()\n  \n  // When \u0026 Then\n  guard.executeDecision(container)\n    .`as`(StepVerifier::create)\n    .expectNextMatches { decision -\u003e\n      decision is GuardDecision.Deny \u0026\u0026\n      decision.errorCode == StateMachineErrorCodeString.GuardValidationFailed\n    }\n    .verifyComplete()\n}\n```\n\n### Testing Composed Behaviors\n\n```kotlin\n@Test\nfun `should execute multiple actions in sequence`() {\n  // Given\n  val container = OrderContainer(order = Order(value = 100))\n  val action1 = MultiplyByTwoAction()\n  val action2 = AddTenAction()\n  val compositeAction = action1.andThen(action2)\n  \n  // When \u0026 Then\n  compositeAction.execute(container)\n    .`as`(StepVerifier::create)\n    .expectNextMatches { it.order.value == 210 } // (100 * 2) + 10\n    .verifyComplete()\n}\n```\n\n### Integration Testing Transformers\n\n```kotlin\n@SpringBootTest\nclass OrderTransformerIntegrationTest {\n  \n  @Autowired\n  private lateinit var orderProcessingTransformer: OrderProcessingTransformer\n  \n  @Test\n  fun `should process order successfully`() {\n    // Given\n    val request = OrderRequest.Create(\n      customerId = \"123\",\n      items = listOf(Item(id = \"item1\", quantity = 2))\n    )\n    \n    // When \u0026 Then\n    orderProcessingTransformer.transform(request)\n      .`as`(StepVerifier::create)\n      .expectNextMatches { order -\u003e \n        order.status == OrderStatus.Processing \u0026\u0026\n        order.paymentValidated \u0026\u0026\n        order.inventoryReserved\n      }\n      .verifyComplete()\n  }\n  \n  @Test\n  fun `should handle insufficient inventory`() {\n    // Given\n    val request = OrderRequest.Create(\n      customerId = \"123\", \n      items = listOf(Item(id = \"out-of-stock\", quantity = 100))\n    )\n    \n    // When \u0026 Then\n    orderProcessingTransformer.transform(request)\n      .`as`(StepVerifier::create)\n      .verifyError(OrderException.InventoryUnavailable::class.java)\n  }\n}\n```\n\n### Testing Error Handlers\n\n```kotlin\n@Test\nfun `should handle payment failure gracefully`() {\n  // Given\n  val request = OrderRequest.Create(customerId = \"123\", items = emptyList())\n  val error = PaymentFailedException(\"Card declined\")\n  val errorHandler = OrderErrorHandler()\n  \n  // When \u0026 Then\n  errorHandler.onError(request, error)\n    .`as`(StepVerifier::create)\n    .verifyError(OrderException.PaymentDeclined::class.java)\n}\n```\n\n### Mocking Dependencies\n\n```kotlin\n@ExtendWith(MockitoExtension::class)\nclass OrderTransformerTest {\n  \n  @Mock\n  private lateinit var orderService: OrderService\n  \n  @Mock\n  private lateinit var paymentService: PaymentService\n  \n  @Test\n  fun `should save order after processing`() {\n    // Given\n    val container = OrderContainer(order = Order(id = \"123\"))\n    val action = SaveOrderAction(orderService)\n    \n    whenever(orderService.save(any())).thenReturn(Mono.just(Order(id = \"123\")))\n    \n    // When\n    action.execute(container)\n      .`as`(StepVerifier::create)\n      .expectNext(container)\n      .verifyComplete()\n    \n    // Then\n    verify(orderService).save(container.order)\n  }\n}\n```\n\n---\n\n## Best Practices\n\n### 1. State Design\n- Use enums for states to ensure type safety\n- Keep state names descriptive and domain-specific\n- Avoid too many states - consider if some can be combined\n\n### 2. Container Design\n- Keep containers immutable when possible\n- Include all necessary domain data in the container\n- Use sealed classes for different request types\n\n### 3. Action Composition\n- Keep actions focused on single responsibilities\n- Use `andThen` to compose related actions\n- Prefer small, testable actions over large monolithic ones\n\n### 4. Guard Design\n- Guards should be pure validation logic\n- Implement `executeDecision()` returning `GuardDecision` (`Allow` or `Deny` with error code and cause)\n- Use `GuardDecision.Deny` with specific error codes and causes for failed validations\n- Return meaningful error codes and exception causes for better error handling\n- Consider using domain-specific exception types in `GuardDecision.Deny`\n\n### 5. Error Handling\n- Use domain-specific exceptions\n- Provide fallback responses when appropriate\n- Log errors with sufficient context\n\n### 6. Transaction Management\n- Use after-commit actions for external system notifications\n- Keep transaction boundaries clear and minimal\n- Test both transactional and non-transactional scenarios\n\n### 7. Testing Strategy\n- Test each action and guard in isolation\n- Use integration tests for full transformation flows\n- Mock external dependencies appropriately\n\n### 8. Performance Considerations\n- Use reactive patterns throughout\n- Avoid blocking operations in actions and guards\n- Consider caching for frequently accessed data\n\n---\n\n## Real-World Example: Delivery Request State Machine\n\nThis library is used in production at Tapsi for managing delivery request state transitions. The delivery domain includes:\n\n- **States**: `Created`, `Processing`, `Assigned`, `PickedUp`, `Delivered`, `Cancelled`\n- **Request Types**: Creation, modification, cancellation\n- **Complex Validation**: Payment validation, inventory checks, driver availability\n- **External Integrations**: Payment gateways, driver apps, customer notifications\n- **Transaction Management**: Database persistence with after-commit notifications\n\nKey patterns from the production implementation:\n- Abstract base transformers for common functionality\n- Factory-based composition of behaviors\n- Comprehensive error handling with domain-specific exceptions\n- Transaction-aware notifications to external systems\n- Extensive logging and monitoring\n\n---\n\n## Version Matrix\n\n- Kotlin: 1.9.x\n- Reactor: 3.7.x\n- Spring: 6.1+/Boot 3.3+\n- Java: 21\n\n---\n\n## License\n\nMIT License. See `LICENSE` for details.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmahdibohloul%2Fstatemachine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmahdibohloul%2Fstatemachine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmahdibohloul%2Fstatemachine/lists"}