In traditional enterprise systems, business process automation frequently relies on heavy external BPMN frameworks (such as Camunda / Flowable), introducing serious architectural overhead:
- Heavy Java/Spring Dependencies: Inflated infrastructure bills, preventing seamless integration with .NET 10 Native AOT and dual ORMs;
- Brittle Long-Running Waiting States: Polling databases for multi-day executive approval gates consumes unnecessary CPU cycles;
- Opaque Audit Trails: Inability to reconstruct point-in-time branch variables during failure investigations.
BitzOrcas.Modern includes an embedded, pure C#, Native AOT-ready workflow engine: Powered by DAG graph topologies and persistent state machines, supporting instant recovery and long-running approval suspensions.
Workflow Execution State Machine Topology
Step 1: Core Domain Entity WorkflowInstance
The workflow instance adopts the Unified Aggregate pattern, mapping directly to WfInstance:
using System;using System.Collections.Generic;using System.ComponentModel;using BitzOrcas.Domain.Entities;using BitzOrcas.Domain.Results;using BitzOrcas.Persistence.Metadata;
namespace BitzOrcas.Workflow.Domain;
public static class WorkflowErrors{ public static readonly Error InvalidState = Error.Conflict("Workflow.InvalidState", "Non-running workflows cannot be suspended.");
public static readonly Error NotFound = Error.NotFound("Workflow.NotFound", "Workflow instance not found.");}
[BitzTable("WfInstance", IsTenant = true, IsSoftDelete = true, Description = "Workflow Instances Table")]public sealed class WorkflowInstance : TenantAggregateRoot<string>{ [BitzColumn(Length = 64, IsRequired = true)] public string DefinitionId { get; private set; } = string.Empty;
[BitzColumn(IsRequired = true)] public WorkflowState State { get; private set; } = WorkflowState.Pending;
[BitzColumn(Length = 64)] public string CurrentNodeId { get; private set; } = string.Empty;
// JSON workflow variables payload [BitzColumn(IsJson = true)] public Dictionary<string, object?> ContextVariables { get; private set; } = [];
[Obsolete("For ORM materialization only. Use Create.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public WorkflowInstance() : base("0") { }
// Domain method: suspends workflow for human approval public Result SuspendForApproval(string approvalNodeId, string candidateRole) { // 1. Evaluate invariant state if (State != WorkflowState.Running) { return Result.Failure(WorkflowErrors.InvalidState); }
// 2. State transition CurrentNodeId = approvalNodeId; State = WorkflowState.Suspended;
// 3. Emit approval task event AddDomainEvent(new ApprovalTaskCreatedDomainEvent(Id, TenantId, approvalNodeId, candidateRole));
return Result.Success(); }}Step 2: Human Approval Command Slice
using System.Threading;using System.Threading.Tasks;using BitzOrcas.Application.Abstractions.Security;using BitzOrcas.Domain.Abstractions;using BitzOrcas.Domain.Results;using BitzOrcas.Workflow.Domain;
public sealed class ApproveTaskCommandHandler( ICommandRepository<WorkflowInstance, string> workflowRepo, IWorkflowEngine workflowEngine, ICurrentUser currentUser){ public async ValueTask<Result> Handle(ApproveTaskCommand command, CancellationToken ct) { // 1. Retrieve target workflow instance var instanceResult = await workflowRepo.FindAsync(command.WorkflowInstanceId, ct); if (instanceResult.IsFailure) { return Result.Failure(WorkflowErrors.NotFound); }
var instance = instanceResult.Value;
// 2. Attach operator decision and comments to context instance.ContextVariables["LastApproverId"] = currentUser.UserId; instance.ContextVariables["ApprovalComment"] = command.Comment;
// 3. Resume DAG topology execution in workflow engine var resumeResult = await workflowEngine.ResumeAsync(instance, command.Decision, ct); if (resumeResult.IsFailure) return resumeResult;
// 4. Persist aggregate state (auto-committed by TransactionPipeline) await workflowRepo.UpdateAsync(instance, ct);
return Result.Success(); }}Summary
The BitzOrcas Workflow Engine delivers pure cloud-native automation:
- Native AOT Compatible: Pure C# with zero external JVM dependencies;
- Transactional Invariance: Every transition commits in isolated database transactions;
- Zero-Resource Suspensions: Multi-day approvals consume zero CPU while idle.