Dual deployment — one binary, serverless and container entrypoints
Dual deployment — one binary, serverless and container entrypoints
Ship the same artifact to AWS Lambda and to long-running containers (bank EKS, standalone Docker) without a port. The "port" is a deployment flag, not a rewrite.
Problem it solves
- Serverless gives scale-to-zero and per-invocation billing for SaaS; containers give banks the control, auditability, and data residency they require for regulated workloads. Maintaining two codebases guarantees drift.
Shape
- Business logic in host-agnostic handlers:
(ctx, typed-request) → (typed-response, error)— no Lambda event types, no framework assumptions in core. - Two thin entrypoints:
lambda.Start(httpadapter.NewV2(router).ProxyWithContext)vshttp.ListenAndServe(":8080", router). For event triggers (queues), one core function, two triggers. - Async work goes through queues (SQS/EventBridge), never background goroutines — Lambda freezes between invocations; a worker consumes the same queue in container mode.
- Config via env vars only; dependencies behind interfaces (storage, payment, KYC vendors) with env-selected implementations.
Rules
- Handlers are one-shot and idempotent: no correctness may depend on in-memory state or the previous invocation.
- Caches are TTL-bounded and lazy; connection pools tolerate staleness and rebuild on demand.
defercleanup stays inside handler scope; nothing escapes the invocation.- One Go binary per service; compile once, run in both modes (
MODEenv switch or two mains). - Every HTTP service ships a warm-reuse test: invoke the handler N times in one process and assert no state leakage, no duplicate side effects (see
factory/flow-builder/internal/httpapi/httpapi_test.go).
Anti-patterns
- Background goroutines for correctness (frozen in Lambda, leaky in containers).
- In-memory sessions or per-process caches assumed warm.
- Framework-specific types (API Gateway events, Express req/res) leaking into core.
- Treating Lambda's process reuse as a fresh start — warm environments persist across invocations.
Sources
- factory/AGENTS.md (merged PR #13)
- factory/flow-builder/internal/httpapi/httpapi_test.go (warm-reuse harness)
- factory/flow-builder/internal/httpapi/httpapi.go (plain http.Handler, gateway-adapter-ready)
- repayments/backend-go/cmd/server vs cmd/jobs (split entrypoints)