Skip to content
bitzorcas
中EN

Concept

Workflow Engine Architecture: DAG State Machines & Scheduling

Deep dive into the BitzOrcas.Modern self-hosted workflow engine. Learn WorkflowDefinition topologies, WorkflowInstance state machines, Activity execution, and human approvals.

Last updated

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

Yes (Requires Approval)No (Fast Track)Approver Approves/Rejects

1. Business Triggers Workflow (StartWorkflowCommand)

2. WorkflowEngine (Load WorkflowDefinition)

3. Instantiate WorkflowInstance (State: Running, Persisted)

4. Execute Automated Activity (e.g. Validation / Quota Hold)

5. Decision Gateway Evaluation (Amount > 50,000?)

6. Create ApprovalTask & Suspend (State: Suspended)

7. Execute Next Automated Step

8. ApproveTaskCommand (Resumes Workflow Execution)

9. Archive Workflow Instance (State: Completed)


Step 1: Core Domain Entity WorkflowInstance

The workflow instance adopts the Unified Aggregate pattern, mapping directly to WfInstance:

WorkflowInstance.cs: Workflow Instance Aggregate Root
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

ApproveTaskCommandHandler.cs: Approval Handler
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.

100%

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