Benevia.Core.API.Workflows
0.8.5
See the version list below for details.
dotnet add package Benevia.Core.API.Workflows --version 0.8.5
NuGet\Install-Package Benevia.Core.API.Workflows -Version 0.8.5
<PackageReference Include="Benevia.Core.API.Workflows" Version="0.8.5" />
<PackageVersion Include="Benevia.Core.API.Workflows" Version="0.8.5" />
<PackageReference Include="Benevia.Core.API.Workflows" />
paket add Benevia.Core.API.Workflows --version 0.8.5
#r "nuget: Benevia.Core.API.Workflows, 0.8.5"
#:package Benevia.Core.API.Workflows@0.8.5
#addin nuget:?package=Benevia.Core.API.Workflows&version=0.8.5
#tool nuget:?package=Benevia.Core.API.Workflows&version=0.8.5
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
- Quick Start
- Concepts
- Request Flow & Lifecycle
- Headers Contract
- Responses & Validation Messages
- Implementation Notes
- Design Choices & Trade‑Offs
- When (Not) to Use
- Summary
1. Quick Start
- Add a using directive:
using Benevia.Core.API.Workflows; - Register workflow services (default options):
Or bind custom options from configuration:builder.Services.AddCoreApiWorkflows();builder.Services.AddCoreApiWorkflows(o => builder.Configuration .GetSection("WorkflowOptions") .Bind(o)); - Create a workflow via
POST /api/workflow→ receive a newWorkflow-Id. - Include
Workflow-Idand incrementingWorkflow-Indexheaders on subsequent OData operations (PATCH/POST/GET) to work against the isolated in‑memory workflow state. - 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:
- Client POSTs
/api/workflow(empty body). Server creates cache entry and returnsWorkflow-Id. Next expected index is 0. - Client issues OData PATCH/POST/GET with headers. Mutations write to cache only; reads merge DB + cache.
- Each mutating request increments
Workflow-Indexby exactly 1. Replays (lower index) return the prior response; higher index →409 Conflict. - Commit request validates accumulated state; on success: applies changes transactionally, clears cache, marks workflow committed.
- 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:
resultalways 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
Workflowstores minimal metadata: GUID, CreatedUtc, UpdatedUtc, Status, NextExpectedIndex, etc. - In‑memory state (entities + messages) held via
IMemoryCachekeyed 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 normalDbDataContextbased 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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Benevia.Core.API (>= 0.8.5)
- Benevia.Core.Events (>= 0.8.5)
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.8-ci.48 | 0 | 4/6/2026 |
| 0.8.7 | 73 | 4/2/2026 |
| 0.8.7-ci.46 | 30 | 4/2/2026 |
| 0.8.7-ci.45 | 33 | 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 | 89 | 3/19/2026 |
| 0.8.3-ci.31 | 42 | 3/19/2026 |
| 0.8.3-ci.30 | 46 | 3/19/2026 |
| 0.8.3-ci.29 | 40 | 3/19/2026 |