Saltar a contenido

ADR-016: SDK outbound minimal

  • Status: Accepted
  • Date: 2026-05-14
  • Tags: integration, sdk, workers

Context and Problem Statement

Workers necesitan SDK para conectarse al engine, activate jobs, complete/fail. Camunda tiene SDK rico con OutboundConnectorContext.bindVariables(Class<T>) que hace 3 cosas (deserialize + validate + replace secrets). ¿Build SDK con esa abstracción o más minimal?

Decision Drivers

  • bindVariables magic puede confundir (qué validation? qué secret provider?)
  • Simple > abstract para SDKs
  • Users prefer explicit > implicit
  • Polyglot SDK requires consistency

Considered Options

  1. Minimal SDK: client + worker base, sin magic
  2. Camunda-style SDK: bindVariables, secret providers, validators
  3. Multi-tier SDK: minimal base + optional helpers
  4. No SDK (users construyen sobre REST API directo)

Decision Outcome

Chosen option: Minimal SDK + optional helpers (option 3) porque: - Base mínima es portable across languages - Helpers opcionales para Java/TS/Python - Users pueden opt-in o usar REST directo - No magic, no surprises

Positive Consequences

  • Easy to port a múltiples languages
  • Explicit > implicit
  • Onboarding rápido
  • Debugging straightforward

Negative Consequences

  • Boilerplate más visible
  • Users implementan secret resolution themselves
  • Sin element templates auto-generated

API design

Core SDK (minimal)

import { WorkflowClient, JobWorker } from '@mvp/sdk';

const client = new WorkflowClient({
  endpoint: 'https://workflow.example.com',
  apiKey: process.env.WORKFLOW_API_KEY
});

// Client operations
await client.deployments.create('process.bpmn');
const pi = await client.processInstances.create({
  bpmnProcessId: 'order-approval',
  variables: { orderId: '123' }
});

// Worker
const worker = new JobWorker({
  client,
  jobType: 'check-credit',
  handler: async (job) => {
    const { customerId } = job.variables;
    const result = await checkCredit(customerId);
    return { creditScore: result.score };
  },
  concurrency: 10,
  timeout: 30000
});

await worker.start();

That's it. No magic.

Optional helpers

import { typedVariables } from '@mvp/sdk/helpers';

// Type-safe variables con Zod
const InputSchema = z.object({
  customerId: z.string().uuid(),
  amount: z.number().positive()
});

const worker = new JobWorker({
  client,
  jobType: 'check-credit',
  handler: async (job) => {
    const input = typedVariables(job, InputSchema);
    // input.customerId tiene type string
    // throws ValidationError si inválido

    return { score: await check(input) };
  }
});

Optional layer — users que NO quieren Zod pueden skip.

Idempotency helper

import { withIdempotency } from '@mvp/sdk/helpers';

const worker = new JobWorker({
  client,
  jobType: 'send-email',
  handler: withIdempotency({
    store: new RedisStore({ url: 'redis://...' }),
    ttl: 86400
  }, async (job) => {
    await sendEmail(job.variables);
  })
});

Recall ADR-007 — workers MUST be idempotent. Helper facilita.

Secret resolution

NO en core SDK. Users resuelven manualmente:

const worker = new JobWorker({
  client,
  jobType: 'call-api',
  handler: async (job) => {
    const apiKey = process.env.EXTERNAL_API_KEY;  // env var
    // OR use Vault/AWS SM SDK directly:
    // const apiKey = await vault.read('secret/data/external-api');

    return await callAPI(apiKey, job.variables);
  }
});

Cada user usa su secret backend. SDK no asume.

Polyglot strategy

Language SDK status Comments
TypeScript/JS Primary Most popular MVP target
Python Tier 1 Major language
Go Tier 1 Cloud-native
Java Tier 2 Camunda migrants
Rust Tier 3 Niche
Other Use REST API OpenAPI spec available

OpenAPI spec del engine permite SDK generation automática para cualquier language.

NO incluir en SDK

  • Element template generator (Web Modeler thing)
  • Secret providers framework
  • Inbound connector base classes (ADR-015 — skipped)
  • Distributed tracing auto-instrumentation (via OTel)
  • Custom serialization (JSON only)