Saltar a contenido

ADR-026: Go como lenguaje de implementación

  • Status: Accepted
  • Date: 2026-05-14
  • Tags: core, language, technology-stack

Context and Problem Statement

El MVP necesita una elección de lenguaje. Prioridades del usuario: (1) resiliencia, (2) eficiencia. ¿Qué lenguaje balancea mejor estas prioridades con consideraciones prácticas?

Decision Drivers

  • Resiliencia: memory safety, predictable behavior, mature distributed systems ecosystem
  • Eficiencia: memory footprint, startup time, GC behavior, throughput
  • Talent pool hireable
  • Library ecosystem para nuestro problem domain
  • Validación industrial (¿qué eligieron sistemas similares?)
  • Development velocity para MVP timeline (26 weeks)

Considered Options

  1. Go (Golang)
  2. Rust
  3. Java/Kotlin (JVM)
  4. C#/.NET
  5. TypeScript/Node.js (incl. Bun)
  6. Elixir/Erlang (BEAM)

Análisis exhaustivo en analysis/language-choice.

Decision Outcome

Chosen option: Go 1.22+ para: - Engine (core, performance-critical) - CLI tools - Go worker SDK

Secondary: TypeScript para frontend (Tasklist, Inspector) + worker SDK.

Tertiary: Python + Java SDKs (ecosystem coverage). REST API spec available for any language.

Positive Consequences

  • Mature ecosystem para sistemas distribuidos (validado por Temporal, K8s, etcd, Consul, Vault, CockroachDB, Nomad)
  • pgx driver Postgres production-grade
  • Memory safe (GC eliminates use-after-free)
  • Predictable GC (sub-ms pauses)
  • Fast startup (< 1s) — crítico per FMEA F1
  • Small memory footprint (~50-100MB baseline) — lower infra cost
  • Single static binary — simple deployment
  • Strong typing con type inference
  • Explicit error handling forces conscious decisions
  • Mature OpenTelemetry SDK
  • Goroutines + channels match actor model (per ADR-006)
  • Hireable talent pool 2026
  • Backwards compatibility guarantee (Go 1 promise)
  • Faster development velocity than Rust/Java

Negative Consequences

  • Verbose error handling (if err != nil repetitivo)
  • Generics still maturing (Go 1.18+, some 2022 libs less idiomatic)
  • No real exceptions (panic = unrecoverable)
  • GC exists (vs Rust no-GC) — rarely problematic but real
  • Some loss of compile-time guarantees vs Rust
  • No BPMN parsing library in Go ecosystem — build minimal parser

Industry validation

Direct competitors / similar systems all chose Go in 2018+ era:

Project Language Domain
Temporal Go Workflow engine (Uber/Cadence successor)
Cadence Go Workflow engine (Uber, Temporal predecessor)
CockroachDB Go Distributed SQL
Kubernetes Go Orchestration
etcd Go Distributed kv (consensus)
Consul Go Service discovery
Vault Go Secrets management
Nomad Go Job scheduler
Argo Workflows Go K8s workflows

Pattern: serious distributed systems in 2018+ overwhelmingly chose Go.

Pros and Cons of the Options

Go (Score 92/100)

Pros: - Resilience: 9/10 (mature ecosystem, predictable, memory safe) - Efficiency: 8/10 (fast startup, low memory, sub-ms GC) - Practical: 9/10 (hireable, fast dev, excellent tooling) - Industry validated for this exact problem domain - pgx is best-in-class Postgres driver

Cons: - Verbose error handling - GC exists (rarely problematic) - No BPMN library (build minimal parser)

Rust (Score 87/100)

Pros: - Resilience: 10/10 (compile-time guarantees, no GC) - Efficiency: 10/10 (C++-level performance, smallest memory) - Memory safety enforced by compiler

Cons: - Practical: 5/10 — steep learning curve, slower dev velocity - Smaller talent pool, 30-50% premium for Rust devs - Async ecosystem fragmenting - 30-50% slower MVP delivery - Library maturity less than Go for our domain

When to choose: team has 3+ Rust experts + willing to accept slower velocity.

Java/Kotlin (Score 76/100)

Pros: - Mature ecosystem (decades) - Camunda uses Java (proven at scale) - Spring ecosystem - Easy hiring

Cons: - JVM memory overhead (~200-500MB baseline) - Slow startup (10-30s) — bad for FMEA F1 recovery - 2-3x infrastructure cost (more RAM) - 20-30% higher ops cost ongoing (JVM tuning) - Defeats operational simplicity differentiator vs Camunda

When to choose: team migrating from Camunda 8, wants minimal language risk.

TypeScript/Node.js (Score 71/100)

Pros: - Fast development - Type safety opt-in - Same language frontend + backend - Massive ecosystem

Cons: - Long-running Node.js processes have memory issues - V8 GC less predictable than Go for this workload - Type safety not runtime-enforced - Workflow engine community not on TS

When to choose: NEVER for engine core. PERFECT for frontend + worker SDK.

Elixir/Erlang (Score 79/100)

Pros: - BEAM designed for resilience (99.9999% in telecom) - Actor model native (matches engine pattern) - Hot code reload - Process isolation

Cons: - Smaller talent pool (HARD to hire 2026) - Postgres ecosystem less mature than Go - Dynamic typing (typespecs not enforced) - Bus factor risk - Workflow engine community small

When to choose: team has 3+ Erlang/Elixir experts already.

C#/.NET (Score 78/100)

Pros: - .NET 8+ much improved - AOT compilation - Mature ecosystem - Good for Windows shops

Cons: - Niche fit for distributed systems / workflow domain - Smaller open-source ecosystem for our use case - Microsoft stewardship (less concern now, but real)

When to choose: shops already on .NET stack heavily.

Concrete tech stack

Engine (Go):
  • Web: Chi (lightweight router) or Gin
  • DB: pgx/v5
  • SQL gen: sqlc (compile-time SQL to typed Go)
  • Migrations: golang-migrate
  • Expressions: cel-go (per ADR-022)
  • OTel: go.opentelemetry.io/otel
  • Tests: testify + standard library

Frontend (TypeScript):
  • Framework: React + Vite
  • UI: Tailwind + Radix UI
  • State: TanStack Query
  • Forms: react-jsonschema-form
  • BPMN viewer: bpmn-js

Worker SDKs:
  • Go: same as engine
  • TypeScript: @mvp/sdk-ts
  • Python: mvp-sdk (Phase 1)
  • Java: mvp-sdk-java (Phase 2, for Camunda migrants)
  • Other: REST + OpenAPI-generated

CLI:
  • Go (cobra framework)
  • Shares types with engine

Build/CI:
  • Make + Docker
  • GitHub Actions

Total active languages: 2 (Go + TypeScript). Manageable.

Implementation notes

Verbose error handling — mitigation

Pattern adopted:

if err := db.Exec(ctx, sql, args...); err != nil {
    return fmt.Errorf("create instance failed: %w", err)
}

Verbose but explicit. golangci-lint catches unhandled errors.

Generics — when to use

  • Collections (slices of typed records)
  • Result-style helpers (Optional, Result types)
  • AVOID in early code if making more complex

Panic strategy

panic only for unrecoverable bugs. Recover at goroutine boundaries:

defer func() {
    if r := recover(); r != nil {
        log.Error("panic", "stack", debug.Stack())
        metrics.Inc("engine.panics")
    }
}()

Cost analysis summary

For 1 year of development (Phase 0 + Phase 1 HA):

Stack Year 1 cost Time to MVP Talent risk
Go ~$454K 26 weeks Low
Rust $604-704K 32-42 weeks High
Java $490K + ops 28-30 weeks Low
Elixir $554-704K 28-40 weeks Very high

Go wins on combined factors.

Migration path (if wrong)

If Go proves wrong choice later (unlikely but possible):

  • To Rust: 6-12 month rewrite of engine, keep SDKs
  • To Java: 4-8 month rewrite, but kills operational differentiator
  • Both painful but doable since architecture is decoupled (Postgres state, REST API stable)

Risk is low because Go has been validated at scale by competitors.