Skip to content

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 UsoFluxo
OnboardingKYC → AML → Score → Conta
CréditoAplicação → Análise → Aprovação → Liberação
PagamentosPIX, TED, DOC com múltiplos passos
ChargebacksDisputa → 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

ConceitoDescriçã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 PolicyBackoff 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_compensation

Comparação: TypeScript vs Go

AspectoTypeScriptGo
Temporal SDKExcelenteNativo
Throughput~12K workflows/s~75K workflows/s
Memory~2GB (100K workflows)~450MB
GoroutinesWorker threadsGoroutines (2KB stack)
ECOSSistemaTemporal, BullMQTemporal, 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

  1. Sem engine = código espaguete — Sem visibilidade, sem retry, sem compensação
  2. Estado durável — Workflows sobrevivem a crashes
  3. Retry com backoff — Evita thundering herd
  4. Saga pattern — Compensação em ordem reversa (LIFO)
  5. Tasks idempotentes — Podem ser reexecutadas com segurança
  6. Temporal.io — Padrão de mercado (Cadence → Uber → Open Source)
  7. Go é natural pra engines — Temporal, Cadence, Kubernetes são Go
  8. Parallel execution — Tasks independentes rodam em goroutines
  9. WebSockets — Updates em tempo real pro cliente
  10. Versionamento — Workflows precisam de SemVer e compatibilidade