Skip to main content

System Event Pattern

Overview

The System Event pattern is used to dispatch complex or long-running operations asynchronously by creating a special SystemEvent instance. A PostCreate trigger on the SystemEvent type then reads the event's payload and invokes the appropriate common program to execute the actual logic.

This pattern is useful when a synchronous trigger (like a PromoteActionCommand) needs to trigger heavyweight processing without blocking the user-facing operation.

When to Use

  • A PromoteActionCommand or PostCreate trigger needs to trigger a complex batch operation (e.g., recalculate all related records) but cannot block the user-facing request.
  • Business logic must be triggered indirectly — the "what to do" is encoded in data (the SystemEvent instance) rather than hardcoded in the calling program.
  • A router pattern is needed: one PostCreate trigger dispatches different common programs based on a type discriminator attribute.

How It Works

User Action

PromoteActionCommand / PostCreate (fast)
→ Creates a SystemEvent instance with:
- Name = "operation name"
- InstanceType = "CommonProgramClassNameToRun"
- InstanceId = "target instance ID"
- ProgramData = "optional JSON payload"

PostCreate on SystemEvent type (async)
→ Reads InstanceType attribute
→ Calls ProgramService.ExecuteCommonProgram(instanceType, args)
→ If success: promotes SystemEvent to "Completed"
→ If error: populates Error attribute, promotes SystemEvent to "Failed"

Applicable Program Types

RoleProgram TypeInterface
Dispatcher (creates the event)PostCreate, PromoteActionCommandAny
Router (handles SystemEvent)PostCreate on SystemEvent typeIPostCreateTriggerProgramAsync
Worker (does the actual work)Common ProgramICommonProgramAsync

Code Example — Dispatcher (creates a SystemEvent)

When a log entry is created, dispatches an async status recalculation on the linked project.

using Prorigo.Protrak.API.Contracts;
using Prorigo.Protrak.API.Contracts.Enum;
using Prorigo.Protrak.API.Services;
using Prorigo.Protrak.Programs;
using System;
using System.Threading.Tasks;
using Attribute = Prorigo.Protrak.API.Contracts.Attribute;

namespace YourNamespace.Programs
{
/// <summary>
/// Type: Post Create Trigger
/// Configured for: Type ActivityLog
/// Trigger: After an activity log entry is created
/// Summary: Creates a SystemEvent to trigger async status recalculation on the linked project.
/// </summary>
public class TriggerStatusRecalculationOnLogCreate : IPostCreateTriggerProgramAsync
{
public IInstanceService InstanceService { get; set; }

private readonly string TYPE_SYSTEM_EVENT = "SystemEvent";
private readonly string COMMON_RECALCULATE_STATUS = "RecalculateProjectStatus";

private readonly string ATTRIBUTE_PROJECT_LOG_REF = "ProjectToLogRef";
private readonly string ATTRIBUTE_NAME = "Name";
private readonly string ATTRIBUTE_INSTANCE_ID = "InstanceId";
private readonly string ATTRIBUTE_INSTANCE_TYPE = "InstanceType";

public async Task RunAsync(Guid instanceId)
{
var logEntry = await InstanceService.GetInstanceAsync(instanceId, new[] { ATTRIBUTE_PROJECT_LOG_REF });
var projectRef = logEntry.GetReferenceAttributeValue(ATTRIBUTE_PROJECT_LOG_REF);
if (projectRef == null || projectRef.Length == 0) return;

var projectId = projectRef.First().Id;

// Create a SystemEvent to dispatch the work asynchronously
var systemEvent = new Instance
{
InstanceTypeName = TYPE_SYSTEM_EVENT,
Attributes = new[]
{
new Attribute { Name = ATTRIBUTE_NAME, Type = AttributeType.Text, TextValue = "Recalculate Project Status" },
new Attribute { Name = ATTRIBUTE_INSTANCE_ID, Type = AttributeType.Text, TextValue = projectId.ToString() },
new Attribute { Name = ATTRIBUTE_INSTANCE_TYPE, Type = AttributeType.Text, TextValue = COMMON_RECALCULATE_STATUS }
}
};
await InstanceService.CreateInstanceAsync(systemEvent);
}
}
}

Code Example — Router (PostCreate on SystemEvent)

Reads the InstanceType discriminator, routes to the matching common program, and promotes the event to Completed or Failed.

/// <summary>
/// Type: Post Create Trigger
/// Configured for: Type SystemEvent
/// Trigger: After a SystemEvent instance is created
/// Summary: Reads InstanceType, calls the matching common program, and promotes to Completed or Failed.
/// </summary>
public class OperationsOnCreateOfSystemEvent : IPostCreateTriggerProgramAsync
{
public IProgramService ProgramService { get; set; }
public IInstanceService InstanceService { get; set; }

private readonly string ATTRIBUTE_INSTANCE_TYPE = "InstanceType";
private readonly string ATTRIBUTE_INSTANCE_ID = "InstanceId";
private readonly string ATTRIBUTE_PROGRAM_DATA = "ProgramData";
private readonly string ATTRIBUTE_ERROR = "Error";
private readonly string STATE_CREATED = "Created";
private readonly string ACTION_COMPLETED = "MarkAsCompleted";
private readonly string ACTION_FAILED = "MarkAsFailed";

// Map InstanceType values to common program class names
private static string GetProgramName(string instanceType) => instanceType switch
{
"RecalculateProjectStatus" => "RecalculateProjectStatus",
"BatchUpdateStatus" => "BatchUpdateStatus",
_ => null
};

public async Task RunAsync(Guid instanceId)
{
var systemEvent = await InstanceService.GetInstanceAsync(instanceId,
new[] { ATTRIBUTE_INSTANCE_TYPE, ATTRIBUTE_INSTANCE_ID, ATTRIBUTE_PROGRAM_DATA, ATTRIBUTE_ERROR });

if (systemEvent.State.Name != STATE_CREATED) return;

var instanceType = systemEvent.GetTextAttributeValue(ATTRIBUTE_INSTANCE_TYPE);
var targetId = systemEvent.GetTextAttributeValue(ATTRIBUTE_INSTANCE_ID);
var programData = systemEvent.GetTextAttributeValue(ATTRIBUTE_PROGRAM_DATA);

var programName = GetProgramName(instanceType);
if (programName == null) return;

try
{
await ProgramService.ExecuteCommonProgramAsync(programName,
new object[] { targetId, programData });

await InstanceService.PromoteInstanceAsync(instanceId, ACTION_COMPLETED, "Done");
}
catch (Exception ex)
{
systemEvent.SetTextAttributeValue(ATTRIBUTE_ERROR, ex.Message);
await InstanceService.UpdateInstanceAsync(systemEvent, null);
await InstanceService.PromoteInstanceAsync(instanceId, ACTION_FAILED, "Error during execution");
}
}
}

SystemEvent Schema Convention

The SystemEvent type typically has these attributes:

AttributeTypeDescription
NameTextHuman-readable label for the event
InstanceTypeTextMaps to the common program class name to invoke
InstanceIdTextThe target instance ID (usually a Guid as string)
ProgramDataTextOptional JSON payload passed to the common program
ErrorTextError message populated if the common program fails

The lifecycle usually has states: CreatedCompleted / Failed.


When to Use vs. Direct Async Trigger

ApproachUse when
Direct PostCreate/PostUpdate triggerLogic is specific to one event; no need to reuse or route
System Event patternLogic is long-running or must be decoupled; same logic is triggered from multiple places; you need visibility into success/failure state via the SystemEvent instance