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: Created → Completed / 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