Saltar a contenido

Developer Experience

Developer experience del MVP. Target: from git clone to first running workflow en < 30 minutos. Local dev: Docker Compose one-command. SDK: hello-world worker < 10 LOC. Documentation hierarchy: 5-min quickstart → 30-min tutorial → reference. Examples library: 20+ realistic workflows. Testing: unit/integration/e2e patterns. Debugging tools: process inspector + trace viewer + replay tool. CLI completo. Onboarding metrics tracked: time-to-first-workflow, time-to-production.

DX target

Goal: a developer who has never used the MVP should be able to:

Milestone Target time
Local environment running 5 minutes
First workflow deployed 15 minutes
First worker complete a job 30 minutes
Understand the model 2 hours (tutorial)
Deploy something real 1 day
In production confidently 1 week

These are aggressive but achievable with good DX investment.

Local development setup

One-command quickstart

git clone https://github.com/your-org/mvp-workflow-engine
cd mvp-workflow-engine
make quickstart

make quickstart should: 1. Check prerequisites (Docker, Docker Compose) 2. Pull/build images 3. Start: Postgres, engine, frontend 4. Run migrations 5. Seed test tenant + user 6. Open browser to local Inspector

Total time: < 5 minutes for first-time user with prerequisites.

Docker Compose stack

# docker-compose.yml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: workflow
    volumes:
      - pg-data:/var/lib/postgresql/data
    ports: ["5432:5432"]
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 5s

  engine:
    build: ./engine
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DATABASE_URL: postgresql://postgres:dev@postgres/workflow
      AUTH_MODE: dev  # bypass OIDC in dev
    ports: ["8080:8080"]

  webapp:
    build: ./webapp
    depends_on: [engine]
    ports: ["3000:3000"]
    environment:
      API_BASE: http://engine:8080

  # Optional: keycloak for full OIDC testing
  keycloak:
    image: quay.io/keycloak/keycloak:24
    profiles: [full]
    ports: ["8090:8080"]
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    command: start-dev

volumes:
  pg-data:

make quickstart runs docker compose up. make quickstart-full includes Keycloak for OIDC testing.

Hot reload

Engine code changes trigger rebuild + restart:

dev:
    docker compose watch

watch mode (Docker Compose v2.22+) rebuilds on file changes. Sub-30s feedback loop.

First workflow tutorial

Step 1: Hello World BPMN

Create hello.bpmn:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
                  xmlns:zeebe="http://camunda.org/schema/zeebe/1.0"
                  targetNamespace="http://example.com/bpmn">
  <bpmn:process id="hello-world" isExecutable="true">
    <bpmn:startEvent id="start" />
    <bpmn:sequenceFlow sourceRef="start" targetRef="say-hello" />

    <bpmn:serviceTask id="say-hello" name="Say Hello">
      <bpmn:extensionElements>
        <zeebe:taskDefinition type="say-hello" />
      </bpmn:extensionElements>
    </bpmn:serviceTask>

    <bpmn:sequenceFlow sourceRef="say-hello" targetRef="end" />
    <bpmn:endEvent id="end" />
  </bpmn:process>
</bpmn:definitions>

Step 2: Deploy

mvp-cli deploy hello.bpmn
# Output: Deployed process: hello-world (key: 1234567890)

Step 3: Worker

// worker.ts
import { MVPClient, JobWorker } from '@mvp/sdk';

const client = new MVPClient({
  endpoint: 'http://localhost:8080',
  apiKey: 'dev-key'
});

const worker = new JobWorker({
  client,
  jobType: 'say-hello',
  handler: async (job) => {
    console.log('Hello, world!');
    return { greeting: 'Hello!' };
  }
});

worker.start();
console.log('Worker started');
npm install @mvp/sdk
npx tsx worker.ts
# Output: Worker started

Step 4: Create instance

mvp-cli create-instance hello-world
# Output: Created instance: 9876543210

# Worker logs immediately:
# Hello, world!

# Verify
mvp-cli instances get 9876543210
# Output: { state: 'COMPLETED', ... }

Total time: ~15 minutes for a developer following the tutorial.

SDK ergonomics

Goal: most common operations < 5 LOC.

Worker examples

// Basic worker (already shown)
new JobWorker({ client, jobType: 'foo', handler: async (job) => {} }).start();

// Worker with input/output typing (Zod)
const InputSchema = z.object({
  customerId: z.string(),
  amount: z.number()
});

new TypedJobWorker({
  client,
  jobType: 'charge-card',
  schema: InputSchema,
  handler: async (job, input) => {
    // input.customerId is typed string
    // input.amount is typed number
    const result = await stripe.charge(input);
    return { transactionId: result.id };
  }
}).start();

// Worker with idempotency
new JobWorker({
  client,
  jobType: 'send-email',
  idempotencyStore: new RedisStore('redis://...'),
  handler: async (job) => {
    // Automatically dedup based on job key
    await emailProvider.send(job.variables);
  }
}).start();

// Worker with retries config
new JobWorker({
  client,
  jobType: 'call-flaky-api',
  retries: {
    max: 5,
    backoff: 'exponential',
    initialDelayMs: 1000
  },
  handler: async (job) => {
    // Engine retries handles retries automatically
  }
}).start();

Client examples

// Create instance
const pi = await client.processInstances.create({
  bpmnProcessId: 'hello-world',
  variables: { name: 'Alice' }
});

// Wait for completion (sync mode)
const result = await client.processInstances.createAndWait({
  bpmnProcessId: 'hello-world',
  variables: { name: 'Alice' },
  timeoutMs: 30000
});

// Search
const instances = await client.processInstances.search({
  state: 'ACTIVE',
  bpmnProcessId: 'hello-world'
});

// Get variables
const vars = await client.processInstances.getVariables(pi.key);

// Cancel
await client.processInstances.cancel(pi.key, { reason: 'duplicate order' });

// Publish message (correlate)
await client.messages.correlate({
  name: 'payment-received',
  correlationKey: 'order-123',
  variables: { amount: 100 }
});

Code completion + type safety from TypeScript types generated from OpenAPI.

Documentation hierarchy

Layer 1: README (1 minute)

# MVP Workflow Engine

A simplified workflow engine inspired by Camunda 8, built on PostgreSQL.

## Quick start

\`\`\`bash
make quickstart
open http://localhost:3000
\`\`\`

[Full tutorial](./docs/tutorial.md)

Layer 2: Quickstart (5 minutes)

Pre-built docker compose + simple worker example.

Layer 3: Tutorial (30 minutes)

Step-by-step: 1. Deploy a process 2. Write a worker 3. Create instances 4. Handle user tasks 5. Use forms 6. Handle errors

Layer 4: How-to guides (per task)

  • How to: integrate with Slack
  • How to: send emails
  • How to: handle long-running tasks
  • How to: implement saga pattern
  • How to: deploy to production
  • How to: monitor workflows

Layer 5: Reference

  • BPMN supported elements
  • API reference (auto-generated)
  • SDK reference (auto-generated)
  • CLI reference

Layer 6: Concepts

  • Architecture overview
  • BPMN execution model
  • Why event sourcing
  • Tradeoffs

Layer 7: Operations

  • Deployment guide
  • Monitoring setup
  • Troubleshooting
  • Performance tuning
  • Migration guides

Examples library

20+ realistic workflows showing patterns:

examples/
├── 01-hello-world/
├── 02-user-task-approval/
├── 03-parallel-gateway/
├── 04-exclusive-gateway/
├── 05-timer-events/
├── 06-error-handling/
├── 07-multi-instance/
├── 08-subprocess/
├── 09-call-activity/
├── 10-message-correlation/
├── 11-signal-broadcast/
├── 12-form-handling/
├── 13-saga-pattern/
├── 14-long-polling/
├── 15-batch-processing/
├── 16-scheduled-jobs/
├── 17-webhook-integration/
├── 18-database-transaction/
├── 19-machine-learning-pipeline/
└── 20-complex-approval-workflow/

Each example has: - README explaining the use case - process.bpmn BPMN file - worker.ts worker code - tests/ integration tests - run.sh to execute

Testing patterns

Unit tests for workers

import { mockJob, mockClient } from '@mvp/sdk/testing';
import { handler } from './worker';

test('handles successful payment', async () => {
  const job = mockJob({
    variables: { amount: 100, customerId: 'cust-1' }
  });

  const result = await handler(job);

  expect(result.transactionId).toBeDefined();
});

Integration tests (with engine)

import { TestEngine } from '@mvp/sdk/testing';

test('order-approval flow', async () => {
  const engine = await TestEngine.start();  // in-memory engine

  await engine.deploy('order-approval.bpmn');

  const pi = await engine.createInstance('order-approval', {
    orderId: 'order-1',
    amount: 100
  });

  // Complete service task
  const job = await engine.activateJob('check-credit');
  await engine.completeJob(job.key, { approved: true });

  // Complete user task
  const task = await engine.findUserTask('manager-approval');
  await engine.completeTask(task.key, { decision: 'approve' });

  // Verify final state
  const finalState = await engine.getInstance(pi.key);
  expect(finalState.state).toBe('COMPLETED');
});

TestEngine is in-memory implementation. Fast (< 100ms per test).

Property-based tests

import { fc } from 'fast-check';

test('any valid path completes the process', () => {
  fc.assert(
    fc.property(
      fc.bpmnModel(),
      fc.executionPath(),
      async (model, path) => {
        const engine = await TestEngine.start();
        await engine.deploy(model);
        const pi = await engine.createInstance(model.processId);

        for (const step of path.steps) {
          await engine.execute(step, pi.key);
        }

        const final = await engine.getInstance(pi.key);
        expect(final.state).toBe('COMPLETED');
      }
    )
  );
});

Per concepts/property-based-testing.

E2E tests

import { TestCluster } from '@mvp/sdk/testing';

test('full cluster e2e', async () => {
  const cluster = await TestCluster.start({
    engineCount: 1,
    workerCount: 2,
    realDatabase: true  // uses test Postgres
  });

  // ... full workflow execution

  await cluster.stop();
});

Slower (~5-10s per test) but realistic.

Debugging tools

Process Inspector (built-in)

For local development: - Browse all instances - See current element - View variables (live) - See history - Cancel/retry from UI

Trace viewer (via APM)

OpenTelemetry traces show: - API → engine → worker → completion - Latency per step - Error sources

Replay tool

Recreate failure deterministically:

mvp-cli replay --process-instance=12345 --from-event=1

# Replays the entire process execution event by event
# Outputs state at each step
# Identifies divergence from expected

Useful for debugging "why did this fail in prod?"

Log inspection

mvp-cli logs --process-instance=12345 --since=1h

# Returns all log entries related to this instance
# Includes worker logs (correlated by trace_id)

CLI completo

# Process management
mvp-cli deploy <file.bpmn>
mvp-cli instances list [--state=ACTIVE] [--process=foo]
mvp-cli instances get <key>
mvp-cli instances create <process-id> [--variables=...]
mvp-cli instances cancel <key>
mvp-cli instances variables <key>

# Job management
mvp-cli jobs list [--type=foo] [--state=ACTIVATED]
mvp-cli jobs get <key>

# Incidents
mvp-cli incidents list [--state=ACTIVE]
mvp-cli incidents resolve <key>

# User tasks
mvp-cli tasks list [--assignee=me]
mvp-cli tasks claim <key>
mvp-cli tasks complete <key> --variables=...

# Identity
mvp-cli users list
mvp-cli users grant <user-id> <role> --tenant=<id>
mvp-cli api-keys create --name=worker --role=worker

# Admin
mvp-cli tenants list
mvp-cli tenants create <name>
mvp-cli audit search --action=...

# Debug
mvp-cli logs --process-instance=<key>
mvp-cli replay --process-instance=<key>
mvp-cli health
mvp-cli metrics

# Migration (from Camunda 8)
mvp-cli bpmn convert --from=camunda <file>
mvp-cli migrate from-camunda --camunda-url=... --target=...

All operations possible via CLI. Power users productive immediately.

Error messages

Bad error message:

Error: Validation failed

Good error message:

Error: Validation failed for process instance creation:
  - bpmnProcessId: Process 'hello-world' has no version 5 (latest is 3)
  - variables.amount: Expected number, got string "100"

Hint: Use --version=latest or specify existing version (1, 2, or 3)
Documentation: https://docs.mvp.dev/troubleshooting#PROCESS_VERSION_NOT_FOUND

Each error: - What went wrong - Why (which constraint failed) - How to fix - Link to docs

IDE integration

VS Code extension (Phase 2 nice-to-have)

  • Syntax highlighting for BPMN
  • Inline preview of process
  • Auto-complete CEL expressions
  • Jump from BPMN element to worker code
  • Snippets for common patterns

IntelliJ plugin

For Java developers migrating from Camunda.

Onboarding metrics

Track these to validate DX:

Metric Target
Time-to-first-workflow (median) < 30 min
Time-to-production (median) < 1 week
Documentation page views per user 5-10
Help/support tickets per user < 1 first month
Net Promoter Score from devs > 50
Tutorial completion rate > 70%

Survey new developers at 1 week + 1 month.

Anti-patterns to avoid

Don't require kubernetes for local dev

Many devs don't have K8s locally. Docker Compose is universal.

Don't make auth complex in dev

AUTH_MODE: dev bypasses OIDC for local. Production uses full OIDC.

Don't bury errors in stack traces

Surface user-friendly errors. Stack trace available with --verbose.

Don't require API keys in tutorial

Dev mode uses simple bearer token. Production introduces OIDC + API keys.

Don't have multiple ways to do everything

One blessed path. Document alternatives clearly.

Documentation maintenance

Documentation rot is real. Strategies:

  • Code examples in docs are tested (linkcheck + executable)
  • API reference auto-generated from OpenAPI
  • Version-pin docs match release version
  • Stale docs flagged via timestamps
  • Community contributions welcome

Marketing the developer story

Public-facing value props:

  1. 15-minute setup vs Camunda's hours
  2. One database vs Camunda's RocksDB + Elasticsearch
  3. Real-time queries vs Camunda's eventually consistent
  4. 80% cheaper at scale
  5. Open source vs Camunda's commercial license
  6. No JVM required vs Camunda's Java-only

DX is product. Investment here pays off in adoption.