Benevia.Core.API.Workflows 0.8.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package Benevia.Core.API.Workflows --version 0.8.3
                    
NuGet\Install-Package Benevia.Core.API.Workflows -Version 0.8.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Benevia.Core.API.Workflows" Version="0.8.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Benevia.Core.API.Workflows" Version="0.8.3" />
                    
Directory.Packages.props
<PackageReference Include="Benevia.Core.API.Workflows" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Benevia.Core.API.Workflows --version 0.8.3
                    
#r "nuget: Benevia.Core.API.Workflows, 0.8.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Benevia.Core.API.Workflows@0.8.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Benevia.Core.API.Workflows&version=0.8.3
                    
Install as a Cake Addin
#tool nuget:?package=Benevia.Core.API.Workflows&version=0.8.3
                    
Install as a Cake Tool

Benevia Core API Workflows

This project provides stateful, header-driven, opt‑in workflows layer on top of an otherwise stateless OData API. Lets a client build or validate complex aggregates over multiple requests in an isolated, temporary workspace before committing changes atomically to the main database.


Table of Contents

  1. Quick Start
  2. Concepts
  3. Request Flow & Lifecycle
  4. Headers Contract
  5. Responses & Validation Messages
  6. Implementation Notes
  7. Design Choices & Trade‑Offs
  8. When (Not) to Use
  9. Summary

1. Quick Start

  1. Add a using directive:
    using Benevia.Core.API.Workflows;
    
  2. Register workflow services (default options):
    builder.Services.AddCoreApiWorkflows();
    
    Or bind custom options from configuration:
     builder.Services.AddCoreApiWorkflows(o => builder.Configuration
         .GetSection("WorkflowOptions")
         .Bind(o));
    
  3. Create a workflow via POST /api/workflow → receive a new Workflow-Id.
  4. Include Workflow-Id and incrementing Workflow-Index headers on subsequent OData operations (PATCH/POST/GET) to work against the isolated in‑memory workflow state.
  5. Commit with POST /api/workflow/commit (same headers) to persist changes atomically. Or let it expire.

That's it — normal API usage (no headers) remains unchanged.


2. Concepts

Concept Description
Workflow A short‑lived server-side session capturing provisional entity state & messages.
Workflow State Cache In‑memory keyed store (Workflow-Id → entity snapshots & validation messages).
Index (Workflow-Index) Monotonically increasing integer guaranteeing ordered, exactly-once semantics per workflow.
Commit Atomic application of cached changes to the primary EF Core DbContext in a single transaction.
Expiration Background service marks stale (unused) workflows expired and clears cache.
Status Likely values: Active → Committed / Expired (see lifecycle below).

3. Request Flow & Lifecycle

Workflow lifecycle diagram:

flowchart LR
    A[Created<br/>Active] --> B[Active Editing]
    B --> C[Commit]
    C --> D[Committed<br/>Cache cleared]
    A --> E[TTL exceeded /<br/>abandoned]
    E --> F[Expired<br/>Cache cleared]

High‑level sequence:

  1. Client POSTs /api/workflow (empty body). Server creates cache entry and returns Workflow-Id. Next expected index is 0.
  2. Client issues OData PATCH/POST/GET with headers. Mutations write to cache only; reads merge DB + cache.
  3. Each mutating request increments Workflow-Index by exactly 1. Replays (lower index) return the prior response; higher index → 409 Conflict.
  4. Commit request validates accumulated state; on success: applies changes transactionally, clears cache, marks workflow committed.
  5. Abandoned workflows are cleaned by background service after TTL.

4. Headers Contract

Header Required Purpose
Workflow-Id Yes (for stateful mode) Correlates requests to a specific workflow session. GUID.
Workflow-Index Yes (for stateful mode) Enforces ordering & idempotency. Must increment exactly by 1 per new logical operation.

Example: Create Workflow

curl -X POST http://localhost:5090/api/workflow \
  -H "Authorization: Bearer {token}"

Response (example):

{ 
  "message": "Workflow created successfully",
  "workflowId": "1c9f2f65-7d3c-4f6b-b0d1-a2efa4a56c0d"
}

Example: Patch Entity Within Workflow

curl -X PATCH "http://localhost:5090/api/Product({guid})" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer {token}" \
  -H "Workflow-Id: 1c9f2f65-7d3c-4f6b-b0d1-a2efa4a56c0d" \
  -H "Workflow-Index: 1" \
  -d '{ "Name": "Updated Name" }'

Example: Commit

curl -X POST http://localhost:5090/api/workflow/commit \
  -H "Authorization: Bearer {token}" \
  -H "Workflow-Id: 1c9f2f65-7d3c-4f6b-b0d1-a2efa4a56c0d" \
  -H "Workflow-Index: 7"

5. Responses & Validation Messages

Workflow responses include an optional @workflow.messages annotation with the OData response body. Keys follow either global or {entityGuid}.{PropertyName}.

Example validation (entity-level):

{
  "@workflow.messages": {
    "85f611d9-2724-4555-8ede-78f47440a3d4.Address": [
      {
        "level": 0,
        "text": "Address must be at least 5 characters long when provided",
        "details": "",
        "entityGuid": "85f611d9-2724-4555-8ede-78f47440a3d4",
        "propertyName": "Address"
      }
    ]
  },
  "result": { 
    "Guid": "85f611d9-2724-4555-8ede-78f47440a3d4"
  }
}

Global message example (commit failure):

{
  "@workflow.messages": {
    "global": [
      { 
        "level": 0, 
        "text": "Workflow with given ID is not active or does not exist", 
        "details": "", 
        "entityGuid": null, 
        "propertyName": null
      }
    ]
  },
  "result": "Workflow is not active"
}

Contract summary:

  • result always contains the standard OData payload (entity / collection / scalar / error message string).
  • Messages persist in cache until cleared or overwritten.
  • Replayed (duplicate index) requests SHOULD return the original response for idempotency (implementation dependent—verify actual behavior).

6. Implementation Notes

  • Database table Workflow stores minimal metadata: GUID, CreatedUtc, UpdatedUtc, Status, NextExpectedIndex, etc.
  • In‑memory state (entities + messages) held via IMemoryCache keyed by workflow id.
  • Repository abstraction (IWorkflowRepository) allows swapping storage without leaking internal details.
  • Ordering & idempotency enforced by an action filter (WorkflowIndexActionFilter).
  • During a workflow, a specialized context (WorkflowDataContext) is chosen instead of the normal DbDataContext based on presence of headers.
  • Commit groups application of staged changes into one EF Core transaction for consistency.

Concurrency & Safety

  • The incrementing index protects against out‑of‑order execution & duplicate network retries.
  • Atomic commit reduces window for partial persistence.
  • Does NOT provide multi‑writer conflict resolution; single logical client is assumed.

Performance Characteristics

  • Workflow cache lookups are O(1) in-memory; dominant cost is final commit DB transaction.
  • Cleanup service keeps memory bounded.

7. Design Choices & Trade‑Offs

Choice Rationale Trade‑Off
Header-based activation Keeps base API unchanged; explicit opt‑in. Client complexity (must manage headers & index).
In-memory cache Lowest latency; simplest implementation path. Single-instance affinity; volatile on restart.
Index-based idempotency Deterministic replay logic; no request journals needed. Client must track and retry on 409.
Short TTL Encourages prompt commits; reduces stale memory usage. Not suitable for long drafts.
Repository abstraction Future proofing for Redis / distributed cache. Slight additional indirection.

8. When (Not) to Use

Use for:

  • Multistep aggregate construction with server-side validation.
  • Wizard flows needing interim server validation.
  • Pre‑commit business logic evaluation (simulate before persist).

Avoid for:

  • Long-lived drafts (days) → design dedicated draft storage.
  • Collaborative multi-user editing → need concurrency model / locking.
  • Large binary/object payloads → store externally (blob storage) and reference.

9. Summary

The workflow layer provides an opt‑in, low‑latency staging area for complex multi-request edits with deterministic ordering, idempotent retries, and atomic final persistence — all while leaving the base OData API untouched for simple CRUD scenarios. Starting small with in-memory mode; future plans include distributed cache support for scaling out.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.7 73 4/2/2026
0.8.7-ci.46 30 4/2/2026
0.8.7-ci.45 32 4/2/2026
0.8.7-ci.44 29 4/1/2026
0.8.7-ci.43 32 3/31/2026
0.8.7-ci.42 29 3/31/2026
0.8.6 141 3/25/2026
0.8.6-ci.40 39 3/25/2026
0.8.5 91 3/25/2026
0.8.5-ci.38 36 3/25/2026
0.8.5-ci.37 37 3/25/2026
0.8.5-ci.36 41 3/24/2026
0.8.4 152 3/23/2026
0.8.4-ci.34 39 3/23/2026
0.8.4-ci.33 33 3/23/2026
0.8.3 88 3/19/2026
0.8.3-ci.31 41 3/19/2026
0.8.3-ci.30 45 3/19/2026
0.8.3-ci.29 39 3/19/2026
0.8.3-ci.28 35 3/19/2026
Loading failed