Desafio 05: Workflow Engine — O Cérebro que Orquestra Fintechs
🇧🇷 Orquestração de Processos Financeiros
🇬🇧 Financial Process Orchestration
Um Workflow Engine é o cérebro orquestrador de uma fintech. Coordena processos complexos com múltiplos sistemas, aprovações, retries, timeouts e compensações. Sem ele, você tem "código espaguete" sem visibilidade.
Switch: TypeScript vs Go
Por que precisamos?
| Caso de Uso | Fluxo |
|---|---|
| Onboarding | KYC → AML → Score → Conta |
| Crédito | Aplicação → Análise → Aprovação → Liberação |
| Pagamentos | PIX, TED, DOC com múltiplos passos |
| Chargebacks | Disputa → Análise → Decisão → Reembolso |
O Problema sem Engine
typescript
// ❌ Spaghetti anti-pattern
async function onboardCustomer(data: CustomerData) {
const kyc = await kycService.verify(data);
if (!kyc.approved) throw new Error('KYC failed');
try {
const aml = await amlService.check(data.document);
if (aml.isBlacklisted) throw new Error('AML failed');
} catch (error) {
// E agora? Como compensar o KYC? Como retentar?
}
const score = await creditBureau.query(data.document);
const account = await ledger.createAccount(data);
// Se crashar aqui? Como retomar?
}Problemas: sem persistência, sem retry, sem compensação, sem visibilidade.
Conceitos Fundamentais
| Conceito | Descrição |
|---|---|
| Workflow (DAG) | Grafo acíclico dirigido de tarefas |
| Task (Activity) | Unidade de trabalho, idempotente |
| Execution (Run) | Instância específica com estado persistido |
| Compensation (Saga) | Rollback em caso de falha |
| Retry Policy | Backoff exponencial configurável |
Fluxo: Onboarding de Cliente
State Machine
Workflow Entity
typescript
export enum WorkflowStatus {
PENDING = 'PENDING', RUNNING = 'RUNNING', WAITING = 'WAITING',
COMPLETED = 'COMPLETED', FAILED = 'FAILED', CANCELLED = 'CANCELLED',
COMPENSATING = 'COMPENSATING', COMPENSATED = 'COMPENSATED', PAUSED = 'PAUSED',
}
export interface RetryPolicy {
maxAttempts: number;
initialIntervalMs: number;
backoffCoefficient: number;
maxIntervalMs: number;
nonRetryableErrors?: string[];
}
export interface TaskDefinition {
name: string;
type: string;
dependsOn?: string[];
timeout?: number;
retryPolicy?: RetryPolicy;
compensation?: string;
parallel?: boolean;
}
export class Workflow extends Entity<string> {
public getReadyTasks(definition: WorkflowDefinition): TaskDefinition[] {
const ready: TaskDefinition[] = [];
for (const taskDef of definition.tasks) {
const execution = this.props.tasks.get(taskDef.name);
if (!execution || execution.status !== TaskStatus.PENDING) continue;
const deps = taskDef.dependsOn || [];
const allDepsCompleted = deps.every(depName => {
const dep = this.props.tasks.get(depName);
return dep && (dep.status === TaskStatus.COMPLETED || dep.status === TaskStatus.SKIPPED);
});
if (allDepsCompleted) ready.push(taskDef);
}
return ready;
}
public startCompensation(): string[] {
this.props.status = WorkflowStatus.COMPENSATING;
const toCompensate: string[] = [];
for (const [name, execution] of Array.from(this.props.tasks.entries()).reverse()) {
if (execution.status === TaskStatus.COMPLETED) {
toCompensate.push(name);
execution.status = TaskStatus.COMPENSATING;
}
}
return toCompensate;
}
}Workflow Engine — Orquestrador
typescript
export class WorkflowEngine {
public async startWorkflow(input: StartWorkflowInput): Promise<StartWorkflowOutput> {
const definition = await this.definitionRepo.findByNameAndVersion(input.definitionName, 'latest');
const workflow = Workflow.create(definition, input.input);
await this.workflowRepo.save(workflow);
await this.eventPublisher.publish('workflow.started', { workflowId: workflow.id });
return { workflowId: workflow.id, status: workflow.status };
}
public async start(): Promise<void> {
this.running = true;
while (this.running) {
await this.processPendingWorkflows();
await this.processRetryableTasks();
await this.processTimedOutTasks();
await this.sleep(1000);
}
}
private async executeTask(workflow: Workflow, taskDef: TaskDefinition, definition: WorkflowDefinition) {
workflow.startTask(taskDef.name);
const executor = this.taskRegistry.get(taskDef.type);
const result = await this.withTimeout(executor.execute(taskInput, context), timeout);
if (result.success) {
workflow.completeTask(taskDef.name, result.output || {});
} else {
await this.handleTaskFailure(workflow, taskDef, definition, result.error);
}
}
private async handleTaskFailure(workflow: Workflow, taskDef: TaskDefinition, definition: WorkflowDefinition, error: string) {
const retryPolicy = taskDef.retryPolicy || { maxAttempts: 3, initialIntervalMs: 1000, backoffCoefficient: 2.0, maxIntervalMs: 60000 };
const shouldRetry = !retryPolicy.nonRetryableErrors?.some(e => error.includes(e)) && taskExec.attempt < retryPolicy.maxAttempts;
if (shouldRetry) {
const interval = Math.min(retryPolicy.initialIntervalMs * Math.pow(retryPolicy.backoffCoefficient, taskExec.attempt - 1), retryPolicy.maxIntervalMs);
workflow.failTask(taskDef.name, error, true, new Date(Date.now() + interval));
} else {
workflow.failTask(taskDef.name, error, false);
if (definition.onFailure === 'COMPENSATE') await this.compensateWorkflow(workflow, definition);
}
}
private async compensateWorkflow(workflow: Workflow, definition: WorkflowDefinition) {
const toCompensate = workflow.startCompensation();
for (const taskName of toCompensate) {
const taskDef = definition.tasks.find(t => t.name === taskName);
if (!taskDef?.compensation) continue;
const executor = this.taskRegistry.get(taskDef.compensation);
await executor.execute({ originalInput: taskExec.input, originalOutput: taskExec.output }, context);
}
}
}Definição YAML
yaml
name: customer-onboarding
version: "1.0.0"
onFailure: COMPENSATE
tasks:
- name: kyc_verification
type: kyc_check
input: { document: ${input.document}, name: ${input.name} }
timeout: 30000
retryPolicy: { maxAttempts: 3, backoffCoefficient: 2.0 }
- name: aml_check
type: aml_check
dependsOn: [kyc_verification]
input: { document: ${input.document} }
- name: credit_score
type: credit_bureau_query
dependsOn: [kyc_verification]
parallel: true
- name: fraud_check
type: fraud_detection
dependsOn: [kyc_verification]
parallel: true
- name: account_creation
type: account_create
dependsOn: [aml_check, credit_score, fraud_check]
conditional: "tasks.credit_score.score > 600 && tasks.fraud_check.risk === 'LOW'"
compensation: close_account_compensationComparação: TypeScript vs Go
| Aspecto | TypeScript | Go |
|---|---|---|
| Temporal SDK | Excelente | Nativo |
| Throughput | ~12K workflows/s | ~75K workflows/s |
| Memory | ~2GB (100K workflows) | ~450MB |
| Goroutines | Worker threads | Goroutines (2KB stack) |
| ECOSSistema | Temporal, BullMQ | Temporal, Cadence |
Casos Reais
- Nubank (Temporal + Clojure) — 80M+ clientes, millions/day
- Stone (Temporal + Go) — 5M+ maquininhas, P99 < 100ms
- Mercado Pago (Go + Custom) — Fork do Cadence, 100K+ workflows/s
- Itaú (Camunda + Java) — BPMN 2.0, compliance
Como testar
bash
# TypeScript
pnpm --filter @banking/workflow-engine dev
# Go
cd packages/backend/workflow-engine-go
go run .
# Iniciar workflow
curl -X POST http://localhost:3011/api/v1/workflows \
-H "Content-Type: application/json" \
-d '{"definitionName":"customer-onboarding","input":{"document":"12345678901","name":"João Silva"}}'
# Consultar status
curl http://localhost:3011/api/v1/workflows/{id}Lições aprendidas
- Sem engine = código espaguete — Sem visibilidade, sem retry, sem compensação
- Estado durável — Workflows sobrevivem a crashes
- Retry com backoff — Evita thundering herd
- Saga pattern — Compensação em ordem reversa (LIFO)
- Tasks idempotentes — Podem ser reexecutadas com segurança
- Temporal.io — Padrão de mercado (Cadence → Uber → Open Source)
- Go é natural pra engines — Temporal, Cadence, Kubernetes são Go
- Parallel execution — Tasks independentes rodam em goroutines
- WebSockets — Updates em tempo real pro cliente
- Versionamento — Workflows precisam de SemVer e compatibilidade