CRUMB a card from devarno-cloud

Stateless BFF Deployment Topology

so1 intermediate 4 min read

ELI5

Each BFF box is interchangeable — no box remembers the last visitor. So a load balancer can shuffle traffic between any number of boxes, and adding more boxes is the entire scaling plan.

Technical Deep Dive

Topology

---
title: "so1 Deployment"
---
flowchart TD
user(("<b>Operator</b>")):::person
rover["<b>so1-rover</b><br/>Next.js 16 — Vercel/Node"]:::system
subgraph api ["**so1-control-plane-api**"]
lb["<b>Load Balancer</b>"]:::system
b1["<b>BFF instance 1</b><br/>Hono container"]:::system
b2["<b>BFF instance N</b><br/>Hono container"]:::system
end
secrets["<b>Secrets Manager</b><br/>KMS / Vault"]:::ext
integrations["<b>GitHub / n8n / MCP</b>"]:::ext
user -- "HTTPS" --> rover
rover -- "HTTPS, same-origin" --> lb
lb -- "HTTP" --> b1
lb -- "HTTP" --> b2
b1 --> secrets
b2 --> secrets
b1 --> integrations
b2 --> integrations
classDef person fill:#1c1c24,stroke:#e85d3e,color:#f0ece6
classDef system fill:#1c1c24,stroke:#d4a574,color:#f0ece6
classDef ext fill:#141419,stroke:#8b7e74,color:#f0ece6,stroke-dasharray: 4 3
classDef db fill:#1c1c24,stroke:#d4a574,color:#f0ece6
classDef container fill:#1c1c24,stroke:#d4a574,color:#f0ece6

Environment Matrix (ADR-002)

EnvFrontendBFFNotes
local devnpm run dev (rover, port 3000)npm run dev (api, port 3001)BFF_INTERNAL_URL=http://localhost:3001
stagingcontainercontainer (Cloud Run / ECS)env-specific secrets injected
productioncontainersame container imageonly env vars differ

The same Docker image is used across staging and production; Dockerfile lives in so1-control-plane-api.

Statelessness Rule

ADR-002 §6: “BFF is stateless; no in-memory caches or sessions.” Implications:

  • Any state-bearing concern (idempotency store from so1-009, log persistence from so1-008, cache) must externalise to Redis / DB / S3.
  • Adding sticky sessions to the load balancer is a smell — fix the offending state instead.
  • Horizontal scaling is N→N+1 at any time without warm-up.

Same-Origin Constraint

The frontend and BFF share an origin (e.g., console.example.com with the BFF mounted at /api/). Per ADR-002 this enables secure cookies and avoids CORS — see so1-002 for the proxy that makes this concrete.

Trade-off

flowchart LR
cheap[stateless = cheap horizontal scale]
ext[every cache/store is external]
hop[extra hop browser→BFF→upstream]
cheap --> ext
cheap --> hop

ADR-002 names the ~10–50ms per-request overhead from the extra hop as the accepted cost.

Key Terms

  • Stateless → instance has no per-user memory between requests.
  • Same-origin → identical scheme/host/port for frontend and BFF.
  • Image parity → staging and production run the same artefact, differing only in injected configuration.

Q&A

Q: Could the BFF cache adapter responses in memory for 60s? A: That violates the stateless rule and produces inconsistent reads across instances. Use a shared cache (Redis) or rely on upstream caching headers.

Q: Why is BFF_INTERNAL_URL server-only and not NEXT_PUBLIC_…? A: Hiding the BFF hostname from client JS keeps the same-origin proxy contract intact (so1-002).

Q: Where is the production secrets manager defined? A: ADR-001 §“Secrets Manager Strategy (Deferred)” — the choice (KMS / Vault / Google Secret Manager) is deferred to TASKSET 5.

Examples

Doubling traffic: add more BFF replicas behind the load balancer; no migration, no warm-up, no session affinity. Idempotency lookups (so1-009) and log persistence (so1-008) hit shared external stores, so any replica can serve any request.

neighbors on the map