Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
227 lines
7.8 KiB
Markdown
227 lines
7.8 KiB
Markdown
# Workflow State Management Guidelines
|
|
|
|
## Overview
|
|
|
|
This document provides guidelines on managing state in workflows, specifically addressing when to use the workflow data manager (`context.data.get`/`context.data.set`) versus local variables within workflow functions.
|
|
|
|
## Understanding Workflow State
|
|
|
|
The workflow system uses an event sourcing pattern where the state of a workflow is derived by replaying events. The `WorkflowDataManager` provided via `context.data` is a key part of this system:
|
|
|
|
- It provides access to state that persists across workflow execution sessions
|
|
- It is included in snapshots for performance optimization
|
|
- It is automatically restored when a workflow is resumed after a pause
|
|
|
|
## When to Use Workflow Data Manager (`context.data`)
|
|
|
|
Use the workflow data manager for any data that needs to be:
|
|
|
|
1. **Persisted across workflow sessions or pauses**
|
|
- Information needed when a workflow resumes after waiting for an event
|
|
- Data that must survive if the application or server restarts
|
|
|
|
2. **Included in the workflow's audit trail**
|
|
- Key business data relevant to the workflow's history
|
|
- Information needed for understanding past decisions
|
|
|
|
3. **Available to event handlers**
|
|
- Data needed by event handlers when responding to events
|
|
- Context information for actions executed by the workflow
|
|
|
|
4. **Part of the workflow's final state**
|
|
- Results or outcomes of the workflow execution
|
|
- Data that might be queried after the workflow completes
|
|
|
|
## When to Use Local Variables
|
|
|
|
Use local variables within your workflow function for:
|
|
|
|
1. **Temporary computation results**
|
|
- Intermediate values used only within a specific code block
|
|
- Results of calculations that don't need to be persisted
|
|
|
|
2. **Loop counters and iterators**
|
|
- Variables used for iteration control that don't represent workflow state
|
|
|
|
3. **Helper function parameters**
|
|
- Arguments passed to helper functions that don't need to be part of state
|
|
|
|
4. **Short-lived data**
|
|
- Information only needed during the current execution session
|
|
- Data that's irrelevant after a specific step is complete
|
|
|
|
## Best Practices
|
|
|
|
### 1. Be Explicit About State
|
|
|
|
- Clearly separate data that's part of the persistent workflow state from temporary variables
|
|
- Consider using naming conventions to distinguish between the two (e.g., prefixes for workflow state variables)
|
|
|
|
### 2. Initialize Workflow State Early
|
|
|
|
```typescript
|
|
// Good practice: Initialize all expected state at the beginning
|
|
async function workflow(context: WorkflowContext): Promise<void> {
|
|
const { data } = context;
|
|
|
|
// Initialize workflow state
|
|
if (!data.get('status')) {
|
|
data.set('status', 'new');
|
|
}
|
|
if (!data.get('attempts')) {
|
|
data.set('attempts', 0);
|
|
}
|
|
|
|
// Rest of the workflow...
|
|
}
|
|
```
|
|
|
|
### 3. Minimize Workflow State Size
|
|
|
|
- Store only what's necessary in workflow state to avoid performance issues
|
|
- For large data structures, consider storing references or IDs instead of the entire object
|
|
- Remove temporary data from workflow state when it's no longer needed
|
|
|
|
```typescript
|
|
// Instead of storing the entire result:
|
|
data.set('apiResponse', await actions.fetchLargeData());
|
|
|
|
// Consider storing just what you need:
|
|
const apiResponse = await actions.fetchLargeData();
|
|
data.set('resultId', apiResponse.id);
|
|
data.set('resultStatus', apiResponse.status);
|
|
```
|
|
|
|
### 4. Use Type Safety with Workflow Data
|
|
|
|
```typescript
|
|
// Define interface for your workflow state
|
|
interface ApprovalWorkflowState {
|
|
requestId: string;
|
|
requestorId: string;
|
|
approvalStatus: 'pending' | 'approved' | 'rejected';
|
|
reviewers: string[];
|
|
approvals: Array<{reviewerId: string, timestamp: string, approved: boolean}>;
|
|
}
|
|
|
|
// Use type safety when accessing data
|
|
const approvals = data.get<ApprovalWorkflowState['approvals']>('approvals');
|
|
```
|
|
|
|
### 5. Keep Function-Specific Logic in Local Variables
|
|
|
|
```typescript
|
|
async function workflow(context: WorkflowContext): Promise<void> {
|
|
// Processing logic with local variables
|
|
const response = await actions.fetchData();
|
|
const processedItems = response.items.filter(item => item.isValid);
|
|
let totalValue = 0;
|
|
|
|
for (const item of processedItems) {
|
|
totalValue += item.value;
|
|
}
|
|
|
|
// Store only the final result in workflow state
|
|
data.set('totalValue', totalValue);
|
|
}
|
|
```
|
|
|
|
## Example: Balancing Workflow State and Local Variables
|
|
|
|
```typescript
|
|
async function approvalWorkflow(context: WorkflowContext): Promise<void> {
|
|
const { actions, events, data, logger } = context;
|
|
|
|
// ==== Workflow State (using data manager) ====
|
|
// Request information (persists across the entire workflow)
|
|
if (!data.get('requestInfo')) {
|
|
const { triggerEvent } = context.input;
|
|
data.set('requestInfo', {
|
|
id: triggerEvent.payload.requestId,
|
|
amount: triggerEvent.payload.amount,
|
|
requestor: triggerEvent.user_id,
|
|
submittedAt: triggerEvent.timestamp
|
|
});
|
|
}
|
|
|
|
// Approval tracking (updated throughout the workflow)
|
|
if (!data.get('approvals')) {
|
|
data.set('approvals', []);
|
|
}
|
|
|
|
// ==== Local Variables (temporary for this execution) ====
|
|
// Get current request info for local use
|
|
const requestInfo = data.get('requestInfo');
|
|
|
|
// Local processing - determine approvers based on amount
|
|
const requiredApprovers = [];
|
|
if (requestInfo.amount <= 1000) {
|
|
requiredApprovers.push('team_lead');
|
|
} else if (requestInfo.amount <= 10000) {
|
|
requiredApprovers.push('team_lead', 'manager');
|
|
} else {
|
|
requiredApprovers.push('team_lead', 'manager', 'director');
|
|
}
|
|
|
|
// Store the required approvers list in workflow state
|
|
data.set('requiredApprovers', requiredApprovers);
|
|
|
|
// Use local variables for processing current approval level
|
|
const approvals = data.get('approvals');
|
|
const currentApprovalLevel = requiredApprovers[approvals.length];
|
|
|
|
if (!currentApprovalLevel) {
|
|
// All approvals received
|
|
context.setState('completed');
|
|
return;
|
|
}
|
|
|
|
// Create approval task
|
|
const { taskId } = await actions.createHumanTask({
|
|
taskType: 'approval',
|
|
title: `${currentApprovalLevel.toUpperCase()} Approval Required`,
|
|
description: `Please review request #${requestInfo.id} for $${requestInfo.amount}`,
|
|
assignTo: { roles: [currentApprovalLevel] }
|
|
});
|
|
|
|
// Store task ID in workflow state
|
|
data.set('currentTaskId', taskId);
|
|
context.setState('awaiting_approval');
|
|
|
|
// In a real implementation, the workflow would pause here and resume when the task is completed
|
|
// When resumed with a task.completed event:
|
|
const approvalEvent = context.input.triggerEvent;
|
|
|
|
// Update workflow state with the approval result
|
|
if (approvalEvent.payload.approved) {
|
|
const newApprovals = [...data.get('approvals')];
|
|
newApprovals.push({
|
|
level: currentApprovalLevel,
|
|
approverId: approvalEvent.user_id,
|
|
timestamp: approvalEvent.timestamp,
|
|
comments: approvalEvent.payload.comments
|
|
});
|
|
data.set('approvals', newApprovals);
|
|
|
|
// Continue to next approval level or completion
|
|
if (newApprovals.length === requiredApprovers.length) {
|
|
context.setState('approved');
|
|
} else {
|
|
context.setState('processing_next_level');
|
|
}
|
|
} else {
|
|
data.set('rejectionReason', approvalEvent.payload.comments);
|
|
data.set('rejectedBy', {
|
|
level: currentApprovalLevel,
|
|
approverId: approvalEvent.user_id
|
|
});
|
|
context.setState('rejected');
|
|
}
|
|
}
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
The event sourcing pattern used by the workflow system provides powerful capabilities for workflow persistence, auditing, and recovery. Using the workflow data manager effectively ensures your workflows can reliably maintain their state across events, while judicious use of local variables keeps your code clean and efficient.
|
|
|
|
By following these guidelines, you'll create workflows that are both robust and maintainable, with clear separation between persistent workflow state and temporary computation variables. |