Skip to main content

Adapter Swap Enables Zero-Cost MVP Modes

Executive Summary

Golden-press had a hard dependency on NATS JetStream — publisher.NewPublisher() failure was fatal at startup (line 114 of main.go). This meant the service could not deploy to Railway without a NATS cluster, which is $50+/month infrastructure for zero MVP value (nothing consumes the events yet). The fix: make NATS_URL empty by default, skip publisher init when empty, pass nil publisher to handler. The handler already handled publish errors gracefully (log + continue). Total change: ~15 lines.

Pattern: Infrastructure Dependencies as Optional Adapters

This extends the ADAPTER-SWAP-PATTERN doctrine already documented in choco-hq. The key insight is that optional infrastructure is not the same as degraded functionality:
DependencyMVP ModeProduction ModeUser Impact
NATS JetStreamDisabled (no publisher)Connected (events published)None — events are for downstream consumers, not users
RedisDisabled (in-memory rate limiting)Connected (shared rate limiting)Minimal — rate limits per-instance instead of global
the-ledger (gRPC)Disabled (no Merkle proofs)Connected (cryptographic verification)Audit trail unavailable, signatures still work
OTel collectorDisabled (structured logs only)Connected (traces + metrics)None — observability is for operators, not users
The pattern: if removing a dependency doesn’t change user-visible behaviour, it should be optional at startup.

Implementation Pattern

// BEFORE: Fatal dependency
pub, err := publisher.NewPublisher(cfg.NATSURL)
if err != nil {
    logger.Fatal("Failed to connect to NATS", zap.Error(err))  // Service won't start
}

// AFTER: Optional dependency
var pub *publisher.Publisher
if cfg.NATSURL != "" {
    pub, err = publisher.NewPublisher(cfg.NATSURL)
    if err != nil {
        logger.Warn("NATS unavailable — event publishing disabled", zap.Error(err))
    }
}
// Handler nil-checks pub before calling pub.Publish()

Key Learning

Fatal startup dependencies should match user-visible dependencies. PostgreSQL is fatal because every endpoint reads/writes data. NATS is optional because event consumers don’t exist yet. The test: “If I remove this dependency, does any HTTP endpoint return a different response?” If no, it’s optional.
Session: choco-hq-mvp-gateway-implementation-2026-04-07 Owner: Dev4rno Archive: devarno-cloud/atlas/learnings/2026-04-07-adapter-swap-enables-zero-cost-mvp-modes.md