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¶
- Go (Golang)
- Rust
- Java/Kotlin (JVM)
- C#/.NET
- TypeScript/Node.js (incl. Bun)
- 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 != nilrepetitivo) - 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.
Links¶
- analysis/language-choice — Detailed analysis con scoring
- adrs/adr-002-postgresql-as-state-store — pgx driver matters
- adrs/adr-006-single-threaded-per-partition — Goroutine fit
- analysis/failure-mode-analysis — Resilience requirements
- analysis/implementation-roadmap-concrete — Velocity assumptions