| name | Temporal Workflow Guidelines |
| description | Comprehensive guide for developing Temporal.io workflows and activities in the A4C-AppSuite.
Covers Workflow-First architecture, deterministic workflow design, event-driven activities,
saga compensation patterns, CQRS integration, and testing strategies for durable workflow
orchestration in healthcare compliance contexts (HIPAA audit trails).
|
| version | 1.0.0 |
| category | temporal |
| tags | ["temporal","workflows","activities","event-driven","saga","cqrs","orchestration"] |
Temporal Workflow Guidelines
This skill provides comprehensive guidance for developing Temporal.io workflows and activities following the Workflow-First architecture pattern used in A4C-AppSuite.
Quick Start
Creating a New Workflow
Creating a New Activity
Common Imports
import { proxyActivities, sleep, uuid4, patched } from '@temporalio/workflow'
import type * as activities from '../../activities/organization'
import { Context } from '@temporalio/activity'
import { ApplicationFailure } from '@temporalio/common'
import { createClient } from '@supabase/supabase-js'
import { TestWorkflowEnvironment } from '@temporalio/testing'
import { Worker } from '@temporalio/worker'
Topics Overview
Workflow Patterns
Learn deterministic workflow design, versioning with patched(), saga compensation, and durable state management. Workflows orchestrate processes without side effects.
📖 resources/workflow-patterns.md
Activity Best Practices
Master idempotent activity design, retry policies, error handling, and timeout configuration. Activities perform all side effects and integrate with external systems.
📖 resources/activity-best-practices.md
Event Emission
Understand domain event patterns, CQRS integration, event schema design, and PostgreSQL event store usage. Every state change becomes an immutable audit trail.
📖 resources/event-emission.md
Testing Workflows
Explore workflow replay testing, activity mocking, local development setup, and integration testing strategies against the dev Temporal cluster.
📖 resources/testing-workflows.md
Navigation Table
| Resource | Focus | Key Concepts |
|---|
| workflow-patterns.md | Workflow design | Determinism, versioning, saga, child workflows |
| activity-best-practices.md | Activity implementation | Idempotency, retries, timeouts, error handling |
| event-emission.md | Event-driven architecture | Domain events, CQRS, audit trails, metadata |
| testing-workflows.md | Testing strategies | Replay tests, mocks, local setup, debugging |
Core Principles
1. Workflow-First Architecture
Workflows orchestrate, activities execute.
Workflows define the what and when of business processes. Activities handle the how (side effects).
export async function BootstrapOrganization(params: BootstrapParams) {
const orgId = await createOrganizationActivity(params)
await configureDNSActivity({ orgId, subdomain: params.subdomain })
await sendInvitationsActivity({ orgId, emails: params.invitations })
return { orgId, subdomain: params.subdomain }
}
2. Determinism is Non-Negotiable
Workflows must be deterministic - same input always produces same execution history.
Use Temporal APIs for workflow operations:
import { uuid4, sleep } from '@temporalio/workflow'
const workflowId = uuid4()
await sleep('5 minutes')
const workflowId = Math.random().toString()
await new Promise(resolve => setTimeout(resolve, 300000))
No side effects in workflows:
export async function BadWorkflow(params) {
const response = await fetch('https://api.example.com')
await supabase.from('orgs').insert({ name: params.name })
console.log('Started at', new Date())
}
export async function GoodWorkflow(params) {
const result = await fetchDataActivity()
await createOrgActivity({ name: params.name })
}
3. Event-Driven Activities
Every activity that changes state MUST emit a domain event.
This creates an immutable audit trail for HIPAA compliance and enables CQRS projections.
export async function createOrganizationActivity(params: CreateOrgParams) {
const { data: org, error } = await supabase
.from('organizations')
.insert({ name: params.name, subdomain: params.subdomain })
.select()
.single()
if (error) throw new Error(`Failed to create org: ${error.message}`)
const workflowInfo = Context.current().info
await supabase.from('domain_events').insert({
event_type: 'OrganizationCreated',
aggregate_type: 'Organization',
aggregate_id: org.id,
event_data: { name: org.name, subdomain: org.subdomain },
metadata: {
workflow_id: workflowInfo.workflowId,
workflow_run_id: workflowInfo.,
: workflowInfo.
}
})
org.
}
4. Saga Compensation Pattern
Workflows must handle partial failures by rolling back completed steps in reverse order.
export async function ProvisionTenantWorkflow(params: ProvisionParams) {
let orgId: string | undefined
let dnsConfigured = false
try {
orgId = await createOrganizationActivity(params)
await configureDNSActivity({ orgId, subdomain: params.subdomain })
dnsConfigured = true
await provisionDatabaseActivity({ orgId })
return { orgId, success: true }
} catch (error) {
if (dnsConfigured && orgId) {
await deleteDNSRecordActivity({ subdomain: params.subdomain })
}
if (orgId) {
await deleteOrganizationActivity({ orgId })
}
throw error
}
}
5. CQRS with Event Sourcing
Events are the source of truth. Projections (read models) are derived from the event stream.
Flow:
- Activity performs action → Emits event to
domain_events
- PostgreSQL trigger processes event → Updates projection table
- Frontend queries projection → Gets denormalized read model
await supabase.from('domain_events').insert({
event_type: 'UserInvitationSent',
aggregate_type: 'Invitation',
aggregate_id: invitation.id,
event_data: { email: invitation.email, role: invitation.role }
})
const { data: invitations } = await supabase
.from('invitations_projection')
.select('*')
.eq('org_id', orgId)
6. Idempotent Activity Design
Activities must be safe to retry without duplicating effects. Check if operation already completed before executing.
See: resources/activity-best-practices.md for complete idempotency patterns.
7. Configurable Retry Policies
Different activities need different retry strategies. External APIs get aggressive retries, validations fail fast.
See: resources/activity-best-practices.md for retry policy patterns.
8. Workflow Versioning
Use patched() to safely update workflows without breaking in-flight executions.
See: resources/workflow-patterns.md for complete versioning guide.
Complete Workflow Template
import { proxyActivities } from '@temporalio/workflow'
import type * as activities from '../../activities/organization'
const {
step1Activity,
step2Activity,
compensateStep1Activity,
compensateStep2Activity
} = proxyActivities<typeof activities>({
startToCloseTimeout: '5 minutes',
retry: {
initialInterval: '1s',
backoffCoefficient: 2,
maximumInterval: '30s',
maximumAttempts: 3
}
})
export interface MyWorkflowParams {
orgId: string
config: Record<string, unknown>
}
export interface MyWorkflowResult {
success: boolean
resourceId: string
}
export async function MyWorkflow(
params: MyWorkflowParams
): Promise<MyWorkflowResult> {
step1Completed =
step2Completed =
:
{
resourceId = ({ : params. })
step1Completed =
({ resourceId, : params. })
step2Completed =
{ : , resourceId }
} (error) {
(step2Completed) {
({ resourceId })
}
(step1Completed) {
({ resourceId })
}
error
}
}
Complete Activity Template
import { Context } from '@temporalio/activity'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
export interface MyActivityParams {
orgId: string
config: Record<string, unknown>
}
export async function myActivity(
params: MyActivityParams
): Promise<string> {
const { data: resource, error } = await supabase
.from('resources')
.insert({
org_id: params.orgId,
config: params.config
})
.select()
.single()
if (error) {
throw new Error(`Failed to create resource: `)
}
workflowInfo = .().
{ : eventError } = supabase
.()
.({
: ,
: ,
: resource.,
: {
: params.,
: params.
},
: {
: workflowInfo.,
: workflowInfo.,
: workflowInfo.,
: workflowInfo.
}
})
(eventError) {
()
}
.()
resource.
}
Anti-Pattern Example
What NOT to Do: Side Effects in Workflow
export async function BadWorkflow(params) {
const timestamp = Date.now()
const response = await fetch('https://api.example.com/data')
const data = await response.json()
await supabase.from('logs').insert({
message: 'Workflow started',
timestamp: new Date().toISOString()
})
return data
}
export async function GoodWorkflow(params) {
const data = await fetchDataActivity()
await logWorkflowStartActivity({ workflowId: params.id })
return data
}
Why this matters: Temporal replays workflows from event history to reconstruct state after crashes. Non-deterministic code or side effects will produce different results on replay, breaking workflow execution.
Quick Reference
kubectl port-forward -n temporal svc/temporal-frontend 7233:7233
export TEMPORAL_ADDRESS=localhost:7233
npm run dev
kubectl port-forward -n temporal svc/temporal-web 8080:8080
npm test
See temporal/CLAUDE.md for complete environment variables and commands.
When to Use This Skill
Activate this skill when:
- Creating or modifying Temporal workflows
- Implementing activities that emit domain events
- Adding saga compensation logic
- Debugging workflow execution issues
- Writing workflow or activity tests
- Working in
temporal/src/workflows/ or temporal/src/activities/
- Implementing event-driven CQRS patterns
This skill complements temporal/CLAUDE.md with detailed implementation patterns and examples.