| | | 1 | | namespace AsyncResponse; |
| | | 2 | | |
| | | 3 | | /// <summary>What a <see cref="IDurableFlowContext"/> step is.</summary> |
| | | 4 | | public enum DurableFlowStepKind |
| | | 5 | | { |
| | | 6 | | /// <summary>A local unit of work (<c>StepAsync</c>).</summary> |
| | | 7 | | Local = 0, |
| | | 8 | | |
| | | 9 | | /// <summary>A remote operation awaited by correlation id (<c>AwaitStepAsync</c>).</summary> |
| | | 10 | | Awaited = 1, |
| | | 11 | | |
| | | 12 | | /// <summary>A durable timer (<c>DelayAsync</c> / <c>DelayUntilAsync</c>).</summary> |
| | | 13 | | Timer = 2, |
| | | 14 | | |
| | | 15 | | /// <summary>A child durable flow (<c>AwaitChildFlowAsync</c>).</summary> |
| | | 16 | | ChildFlow = 3 |
| | | 17 | | } |
| | | 18 | | |
| | | 19 | | /// <summary>One step-lifecycle observation of a durable-flow execution.</summary> |
| | | 20 | | /// <param name="FlowId">The flow run id.</param> |
| | | 21 | | /// <param name="StepName">The step's stable name.</param> |
| | | 22 | | /// <param name="Kind">What the step is.</param> |
| | | 23 | | /// <param name="CorrelationId"> |
| | | 24 | | /// The awaited step's correlation id (the in-flight breadcrumb), when <paramref name="Kind"/> is |
| | | 25 | | /// <see cref="DurableFlowStepKind.Awaited"/>. |
| | | 26 | | /// </param> |
| | | 27 | | /// <param name="WakeAtUtc"> |
| | | 28 | | /// The timer step's due time, when <paramref name="Kind"/> is <see cref="DurableFlowStepKind.Timer"/>. |
| | | 29 | | /// </param> |
| | | 30 | | public readonly record struct DurableFlowStepEvent( |
| | | 31 | | string FlowId, |
| | | 32 | | string StepName, |
| | | 33 | | DurableFlowStepKind Kind, |
| | | 34 | | string? CorrelationId = null, |
| | | 35 | | DateTime? WakeAtUtc = null); |
| | | 36 | | |
| | | 37 | | /// <summary>A terminal run observation of a durable-flow execution.</summary> |
| | | 38 | | /// <param name="FlowId">The flow run id.</param> |
| | | 39 | | /// <param name="Status">The terminal status the run reached.</param> |
| | | 40 | | /// <param name="Message">The run's last operator-facing message.</param> |
| | | 41 | | public readonly record struct DurableFlowRunEvent( |
| | 464 | 42 | | string FlowId, |
| | 96 | 43 | | FlowRunStatus Status, |
| | 0 | 44 | | string? Message); |
| | | 45 | | |
| | | 46 | | /// <summary> |
| | | 47 | | /// Observes durable-flow execution from inside the executor: step activations, waits, checkpoint |
| | | 48 | | /// completions, and terminal run transitions. Register implementations in DI (any number, <b>as |
| | | 49 | | /// singletons</b> — the singleton flow executor resolves them once from the root provider and |
| | | 50 | | /// holds them for its lifetime; a scoped or transient registration fails resolution with an error |
| | | 51 | | /// naming this requirement). The flow executor invokes every registered observer synchronously on |
| | | 52 | | /// the execution path. |
| | | 53 | | /// <para> |
| | | 54 | | /// Intended for test instrumentation (AsyncResponse.Testing's harness is built on it) and |
| | | 55 | | /// lightweight production telemetry. Because observers run <em>on</em> the execution path, an |
| | | 56 | | /// observer that throws fails the current execution attempt exactly like a step failure — the |
| | | 57 | | /// delivery retries from the last checkpoint. AsyncResponse.Testing uses precisely that contract |
| | | 58 | | /// to inject deterministic crashes at step boundaries; telemetry observers must not throw. |
| | | 59 | | /// </para> |
| | | 60 | | /// <para> |
| | | 61 | | /// All methods have no-op default implementations, so an observer overrides only what it needs. |
| | | 62 | | /// </para> |
| | | 63 | | /// </summary> |
| | | 64 | | public interface IDurableFlowExecutionObserver |
| | | 65 | | { |
| | | 66 | | /// <summary> |
| | | 67 | | /// A not-yet-completed step is about to execute its body (local step), trigger or re-attach |
| | | 68 | | /// (awaited step), start or continue its wait (timer), or start/await its child (child-flow |
| | | 69 | | /// step). Memoized (completed) steps are skipped without an event. Fires before the step's |
| | | 70 | | /// first side effect of this execution. |
| | | 71 | | /// </summary> |
| | | 72 | | ValueTask OnStepStartingAsync(DurableFlowStepEvent step) => default; |
| | | 73 | | |
| | | 74 | | /// <summary> |
| | | 75 | | /// The step is now durably parked: an awaited step's trigger completed (or its re-attach began) |
| | | 76 | | /// and the run is waiting on <see cref="DurableFlowStepEvent.CorrelationId"/>; a timer step is |
| | | 77 | | /// waiting for <see cref="DurableFlowStepEvent.WakeAtUtc"/>; a child-flow step is about to |
| | | 78 | | /// suspend this run for its child. |
| | | 79 | | /// </summary> |
| | | 80 | | ValueTask OnStepWaitingAsync(DurableFlowStepEvent step) => default; |
| | | 81 | | |
| | | 82 | | /// <summary>The step's completion checkpoint was persisted.</summary> |
| | | 83 | | ValueTask OnStepCompletedAsync(DurableFlowStepEvent step) => default; |
| | | 84 | | |
| | | 85 | | /// <summary> |
| | | 86 | | /// The current execution attempt ended without the run reaching a terminal status: the |
| | | 87 | | /// attempt failed (or lost its execution lease) and the delivery retries from the last |
| | | 88 | | /// checkpoint. Any step this attempt had reported waiting is no longer parked in this |
| | | 89 | | /// process; the retry re-raises whatever still applies. Best-effort, unlike the other |
| | | 90 | | /// events: the attempt's own exception is already propagating, so an observer throw here is |
| | | 91 | | /// swallowed rather than allowed to mask it. |
| | | 92 | | /// </summary> |
| | | 93 | | ValueTask OnRunAttemptFailedAsync(DurableFlowRunEvent run) => default; |
| | | 94 | | |
| | | 95 | | /// <summary> |
| | | 96 | | /// The run reached a terminal status (<see cref="FlowRunStatus.Succeeded"/> or |
| | | 97 | | /// <see cref="FlowRunStatus.Failed"/>). Delivered <b>at least once</b> per terminal run: a |
| | | 98 | | /// redelivered duplicate of an already-terminal wake-up re-notifies before acking, because the |
| | | 99 | | /// original notification may be exactly what failed the prior delivery — implementations must |
| | | 100 | | /// tolerate duplicates. |
| | | 101 | | /// </summary> |
| | | 102 | | ValueTask OnRunFinishedAsync(DurableFlowRunEvent run) => default; |
| | | 103 | | } |