← Writing

Seventeen Cloud Functions codebases is not an architecture

Planning the consolidation of ~17 independently-deployed gen-1 Firebase Functions into one backend: the cold-start case, what stays behind, what you lose with per-function deploys, and the middleware debt it finally forces you to pay.

On this page

A Flutter/Firebase product I run engineering for has, at last count, roughly 17 separate Cloud Functions codebases — each independently deployed, each with its own dependencies, each born the same way: a feature needed a backend endpoint, and the path of least resistance was another folder with another index.js. No one decision was wrong. The sum is a backend that exists as an archipelago.

This is a decision post, not a victory lap — the consolidation is planned, not shipped. But the planning surfaced trade-offs that I think generalize to anyone staring at their own functions sprawl.

Why move at all: cold starts

The forcing function is latency. Gen-1 Cloud Functions cold-start, and a backend made of 17 small, separately-warmed functions cold-starts a lot — each function is its own instance pool, so low-traffic endpoints are nearly always cold. Users feel it as the app’s random slowness: one tap fast, the next tap seconds.

Two consolidation shapes fix this:

  • A single gen-2 service (one HTTP app — Hono or Express — behind one function) with minimum instances, so there is always a warm instance and one shared pool absorbs all HTTP traffic.
  • Cloudflare Workers, where cold start effectively stops being a concept, and which the rest of my stack already leans on — see my infrastructure post.

Either way, the architectural move is the same: HTTP endpoints stop being seventeen deployables and become one app with a router.

What stays behind

Not everything should move. Trigger-based functions stay put. Firestore triggers, Auth triggers, storage triggers — these exist because they sit inside Firebase’s event system, and re-platforming them means rebuilding event delivery for no gain. The consolidation target is the HTTP surface: the request/response endpoints the app calls. Splitting along that line keeps the migration honest — event plumbing stays where events live; request handling goes where requests are served best.

The thing you actually lose

Seventeen codebases have one genuine virtue nobody designed on purpose: per-function deploy control. Ship a fix to one endpoint and you cannot break any other, because you didn’t deploy any other. Blast radius is a folder.

Consolidation trades that away — one app means one deploy touching every route — and pretending otherwise is how consolidations get rolled back. The mitigations are real but must be chosen deliberately: a router structured so each module stays independently testable; deploys gated by a test suite that covers routes you didn’t change; and gradual cutover, endpoint by endpoint, with the old function left serving until its replacement has taken traffic. What you’re really buying back is confidence-per-deploy, and it has to come from tests and staged rollout now that it no longer comes from isolation.

The honest confession: the middleware debt

Here’s the part that makes consolidation more than a performance project. Seventeen codebases means seventeen places where auth, logging, and input validation were each implemented — or, honestly, not implemented. Grown-by-accretion function folders accumulate endpoints that lack consistent auth middleware and structured logging, because every new folder started from zero and shipped under deadline.

A single app has a single middleware stack. Consolidation is the one moment where putting shared authentication, request logging, and validation in front of every endpoint is cheaper than not doing it — the route table becomes a checklist, and an endpoint that shouldn’t be public has nowhere left to hide. That, as much as cold starts, is the real payoff: the migration forces an inventory that the archipelago made impossible.

The decision framework

If you’re weighing the same move, the questions that mattered:

  1. Where does latency hurt? If it’s user-facing HTTP, consolidate that surface. If your pain is elsewhere, min-instances on the hot functions may be the whole fix.
  2. What’s trigger-bound? Leave it. Migrate the HTTP surface only.
  3. What replaces per-function blast radius? Name the mechanism (tests, staged cutover, module boundaries) before the first endpoint moves.
  4. What debt does the move let you pay? Auth, logging, validation — write the endpoint inventory first; it doubles as the migration checklist.
  5. One platform or two? If the rest of your infrastructure already lives somewhere (mine leans Cloudflare), gravity matters more than benchmark deltas.

Seventeen folders was never a decision anyone made. The consolidation is the decision — which is exactly why it deserves a plan instead of an eighteenth folder.

← All writing Book a call →
Book a call → WhatsApp