Skip to content
bitzorcas
中EN

Guide

Embed Workflow Engine Standalone

Compose the engine outside BitzOrcas with correct persistence, query, permission, participant, service-task, notification, timer, cache, and operations boundaries.

Last updated

Workflow Engine can run without ASP.NET Core and Platform modules, but it is not a bundled database product. The host owns IPersistenceStore, transactions, schema, tenancy, and expression evaluation. Optional-port absence has concrete degradation.

Standalone host composition

Workflow.Engine

IPersistenceStore

IExpressionEvaluator

IFlowDataPermissionChecker

IWorkflowQueryStore

notification / participant / service-task ports

tenant-isolated database

Persistence and expression evaluation are hard build dependencies. Permission, query, notification, participant, and service-task ports are optional in construction but determine fail-closed writes, query cost, delivery, assignment, and automation. Production acceptance must distinguish “builds” from “operates safely.”

1. Packages

PackagePurpose
Workflow.Abstractionsdefinitions, runtime models, and ports
Workflow.Enginecompiler, behavior, runtime, services
Workflow.DependencyInjectionIServiceCollection composition
Workflow.FusionCacheoptional L1/L2 cache
Framework infrastructure adaptersEF Core/SqlSugar write stores and read adapters

Do not reference Platform.Workflow.Application unless adopting BitzOrcas current-user, Mediator, authorization, and endpoint conventions too.

2. Builder

Minimum production-oriented builder
var engine = new WorkflowEngineBuilder()
// Required: all workflow tables, transaction, and optimistic concurrency.
.UsePersistence(persistenceStore)
// Required: controlled expression evaluation.
.UseExpressionEvaluator(new DefaultExpressionEvaluator())
// Strongly required for user interaction; otherwise writes fail closed.
.UseFlowDataPermissionChecker(permissionChecker)
.Build();
IWorkflowTransitionFlow transitions = engine.RuntimeTransition;
ITaskService tasks = engine.Tasks;

Missing persistence or expression evaluator throws. Noop cache, absent query store, notifications, or participant provider still builds but degrades capability.

3. DI

Service collection composition
// Write and read stores must enforce identical tenant semantics.
services.AddScoped<IPersistenceStore, TenantAwareWorkflowStore>();
services.AddScoped<IWorkflowQueryStore, WorkflowReadStore>();
services.AddScoped<IFlowDataPermissionChecker, WorkflowPermissionChecker>();
services.AddScoped<IParticipantProvider, DirectoryParticipantProvider>();
services.AddScoped<INotificationChannel, DurableWorkflowNotificationChannel>();
services.AddScoped<IServiceTaskHandler, ApplicationServiceTaskHandler>();
// Composition registers the graph; schema creation remains the host's job.
services.AddWorkflowEngine();

IWorkflowEngine is Scoped to avoid capturing a scoped DbContext. WorkflowEngineConfiguration switches are not currently enforced.

4. Persistence contract

An adapter must provide:

  • one transaction across operation writes;
  • expectedVersion on instance and task updates;
  • tenant filtering on every read and write;
  • complete definition, binding, runtime, task, activity, timer, history, and statistic semantics;
  • cancellation and UTC DateTimeOffset;
  • recognizable uniqueness and concurrency exceptions;
  • stable query ordering.

Prefer official adapters and run parity tests instead of implementing only enough to compile.

5. QueryStore

Without IWorkflowQueryStore, task fallbacks can scan all data, done count can be full-memory, reports return empty, and statistics aggregation has no source. Production hosts need database assigned/candidate union and report range queries.

Advanced data-scope filtering still needs framework-level pushdown; the embedding guide cannot claim it is solved by injecting a QueryStore.

6. Participants and permission

IParticipantProvider expands directory rules; custom IParticipantResolver handles domain-specific assignment. Both must enforce tenant scope and return stable user IDs.

IFlowDataPermissionChecker maps View, Operate, and Manage to the host’s authorization. Actor and tenant must be trusted server context. Missing checker is intentionally fail-closed.

7. Service tasks

Map Implementation to an allowlist handler key; never execute arbitrary reflection or script.

Controlled service-task dispatch
// Treat implementation as an allowlisted key, never a reflected type name.
return implementation switch
{
"matter.reserve-case-number" =>
reserveCaseNumber.ExecuteAsync(context, cancellationToken),
"conflict.register-waiver" =>
registerWaiver.ExecuteAsync(context, cancellationToken),
// Unknown keys fail explicitly so operations can observe configuration drift.
_ => throw new InvalidOperationException(
$"Unsupported service task: {implementation}")
};

External calls require idempotency, timeout, retry, and compensation. Do not keep a database transaction open across the network.

8. Notifications and callback

INotificationChannel is optional; without one nothing is sent. A custom channel should mirror the platform adapter’s contract — persist per-recipient inbox plus Outbox facts and throw after counting any failure — because the engine dispatcher now rethrows channel exceptions and fails the transition; swallowing them mid-way would fake success. Direct Builder can still connect template and preference stores; AddWorkflowEngine currently does not forward those two services. IBusinessIntegrationCallback is not an Outbox. Prefer writing a local durable intent and processing it asynchronously.

9. Timers

The host must provide trusted tenant scope, atomic claim/lease, attempts and backoff, multi-instance exclusion, a notification channel, system permission for escalation/transfer, and crash recovery. The current official handler marks Fired before action and is not sufficient for a reliable SLA.

10. Cache

Noop is simplest and correct. Once caching is enabled, definition, binding, and todo-count invalidation become correctness. FusionCache is L1-only without Redis; multi-node products need L2/backplane and outage tests.

11. Health and observability

Readiness checks database/schema, query adapter, tenant context, and required ports. Timer and notification should have separate component health. Metrics cover command latency, conflicts, task age, timer lag, notification backlog, report scans, and cache. Do not label metrics with task, business, comment, or user identifiers.

12. Acceptance

  • Workflow unit and integration tests pass;
  • custom adapter parity covers transaction, concurrency, tenancy, and every table;
  • missing QueryStore is prevented or surfaced;
  • participant and permission ports reject cross-tenant access;
  • service tasks are replay-safe;
  • timer crash and retry recover;
  • notification and business integration use durable intents;
  • migration, backup, restore, archive, and cleanup runbooks work.

13. Commands

Terminal window
dotnet test tests/BitzOrcas.Workflow.Tests/BitzOrcas.Workflow.Tests.csproj
dotnet test tests/BitzOrcas.Workflow.Integration.Tests/BitzOrcas.Workflow.Integration.Tests.csproj
rg -n "class .*PersistenceStore|IWorkflowQueryStore" src/Framework tests -g '*.cs' --glob '!**/bin/**' --glob '!**/obj/**'

Back to Workflow manual

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%