Fluent Commerce Logo
Docs

Core Module 2.3.0 - Enhanced Workflow Stability and Event Reliability

Release

Author:

Kirill Gaiduk

Changed on:

7 Aug 2026

Target release date:2026-08-13
Release status:Released

Description

Core Module 2.3.0 introduces a new event scheduling rule that enables randomized event execution delays for workflows that require protection against concurrent execution.The release also improves the reliability of event-sending rules when processing events with incomplete context.🔎 See the Changelog for details.

Changelog

RulesCore Utilities
  • Updated the `EventUtils` to safely handle events where the context `rootEntityType` is undefined. When determining whether an event is cross-workflow or inline, the utility now assumes the event is inline if the context does not define a `rootEntityType`, preventing a potential `NullPointerException`
Released capability depth:Enhancement
Release bundle / Capability type:Module

Use case

Prevent Duplicate Fulfillments During Concurrent Re-Sourcing

Problem
When a fulfillment expires, the workflow sends a re-sourcing event on the parent order. If the same order has multiple fulfillments that all share the same Pick/Pack deadline, all of them can expire exactly at the same time - triggering multiple re-sourcing attempts for the same order simultaneously. Each attempt independently loads the current unfulfilled item state of the order. Because all attempts load the same state before any of them has completed, each attempt calculates the same set of items to fulfill and tries to create fulfillments for all of them.Without a mechanism to prevent this, concurrent re-sourcing can produce the following failure modes:
  • Duplicate fulfillments created for the same items. Multiple concurrent attempts each try to create fulfillments for the same unfulfilled items. Without deduplication, all attempts succeed, resulting in more fulfillments than intended for the same order items.
  • Inventory reserved multiple times for the same demand. Each concurrent attempt that successfully creates fulfillments also triggers inventory reservations. The same stock is reserved multiple times, reducing availability for other orders beyond what the actual demand justifies.
  • Negative unallocated item quantities. Because each concurrent attempt subtracts from the same unfulfilled count, the combined effect of all completed attempts can drive the unallocated quantity below zero, producing an inconsistent order state.
  • Operational overhead to recover. Identifying and removing duplicate fulfillments, correcting reservation quantities, and restoring the order to a consistent state requires manual investigation that scales with order volume.
A fashion retailer processes an order that contains two items:
  • a jacket (Item A) 
  • a pair of boots (Item B)
The order is sourced and two separate fulfillments are created - one for each item - at two different store locations. Both fulfillments share the same Pick/Pack deadline of 9:00 AM on Monday. Both locations miss the deadline, and both fulfillments expire at exactly the same time.When a fulfillment expires, the workflow fires a re-sourcing event on the parent order. With two fulfillments expiring simultaneously, two re-sourcing attempts are triggered for the same order at the same moment. Both attempts load the current state of the order before either has made any changes. At that point, both Item A and Item B are still unfulfilled. Each attempt independently calculates that fulfillments need to be created for both items, and both proceed to create them.The result is four fulfillments instead of two - duplicate fulfillments for both Item A and Item B. Inventory is reserved twice for each item, and the order's unallocated quantity drops below zero. The order is now in an inconsistent state that requires manual correction.
Solution Overview
Reference order workflow addresses concurrent re-sourcing through two complementary mechanisms:
  • The first reduces the probability that two re-sourcing attempts execute at the same time
  • The second ensures that even when they do, only one set of fulfillments is persisted
How It Works at a glance
  • Stagger Concurrent Attempts with Two Scheduled Events
    Rather than triggering re-sourcing immediately when a fulfillment expires, the `ReSourceOrder` ruleset schedules two `SourceOrder` events with different configurable randomized delays:
    • The first event fires with a base delay of approximately 30 seconds, with a random offset applied around that value. When two fulfillments expire at the same time, the randomization makes it likely that their re-sourcing attempts will land in different execution windows. If the first attempt completes before the second begins, the second finds no unfulfilled items remaining and exits cleanly
    • The second event fires approximately 5 minutes after expiry. By that point, all in-flight attempts from the original expiry wave have either succeeded or failed. Any items that remain unfulfilled are picked up and processed. In the large majority of cases, this event fires, finds nothing to do, and exits. Its value lies in the edge cases where it is the only path to recovery
    • The two events serve distinct purposes:
      • the first reduces the probability of concurrent execution
      • the second provides a self-correcting fallback for the rare cases where the first is not sufficient
  • Deduplicate at the Database Level Using Deterministic References
    Scheduling alone reduces but does not eliminate the risk of concurrent execution. To handle the cases where two attempts do run simultaneously and both reach fulfillment creation, fulfillments in the reference workflow are assigned deterministic, index-based references rather than random identifiers. When two concurrent attempts start from the same loaded order state - meaning the same number of existing fulfillments - they calculate identical references for the new fulfillments they are about to create.

    When the `fc.api.fulfilment.uniqueness` setting is configured to `REF_ONLY`, the fulfillment's external reference is copied to its `unique_reference` field before persistence. A database-level unique constraint on the combination of order and unique reference then ensures that only one of the concurrent attempts can persist its records. The second attempt's identical references are rejected at the database level - no duplicate fulfillments are created, and no additional inventory is reserved.
  • This Pattern Applies Only to Fulfillment Expiry Re-Sourcing
    The `ReSourceOrder` ruleset and the `ScheduleEventWithRandomOffset` pattern are applied exclusively to the re-sourcing flow triggered by fulfillment expiry. All other sourcing entry points - initial sourcing at order creation, partial fulfillment sourcing, and sourcing triggered by rejection and reassignment - route directly to `SourceOrder`.

    Initial sourcing does not face the same concurrency risk. For CC and HD orders, a single sourcing event is triggered per order. For Multi orders, sourcing is triggered once per fulfillment choice. In both cases, there is no competing parallel execution path that would produce the same race condition.
Required Configuration
Both mechanisms must be in place for full protection. The scheduled delay pattern and the `fc.api.fulfilment.uniqueness` = `REF_ONLY` setting work together:
  • The delay reduces the likelihood of concurrent execution
  • The uniqueness constraint handles the cases where concurrent execution still occurs
Configuring only one without the other leaves a gap.
Intended Use
`ScheduleEventWithRandomOffset` is a targeted pattern for a specific concurrency risk. It is included in the reference workflow because fulfillment expiry is a known scenario where multiple fulfillments on the same order can expire simultaneously. Applying this pattern to other rulesets - particularly the initial sourcing flow or standard order lifecycle events - adds latency and complexity without addressing a real problem. Before using `ScheduleEventWithRandomOffset` outside the expiry context, confirm that a genuine concurrent execution risk exists for the scenario you are configuring.
Solution