GMI · TECHNOLOGY OBSERVATORY // ALL SYSTEMS NOMINAL
ENGINEERED BY LEOPARD DATA

Modular Monolith with Worker Processes

Why GMI deliberately runs 310K+ lines as one codebase, one database, and one API deployment — and still gets the fault isolation and independent scaling people adopt microservices for

THE ACTUAL SHAPE
rendering diagram…
flowchart TB
    subgraph Clients[Client applications]
      WEB[Client Web - Blazor WASM]
      ADMIN[Admin Web]
      MAUI[MAUI mobile app]
      PUB[Public static pages<br/>SEO + shared reports shim]
    end
    subgraph Mono[ONE deployable API - Azure Functions]
      API[336 endpoints<br/>auth + rate-limit middleware]
    end
    subgraph Lib[ONE shared codebase - 35 projects]
      SVC[Services layer]
      REPO[Repositories - 48 interfaces]
    end
    subgraph VM[Worker processes - report VM, systemd]
      W1[Report engine]
      W2[Email / SMS senders]
      W3[Billing engine]
      W4[SEO + social card builder]
      W5[11 more workers]
    end
    DB[(ONE MySQL database<br/>69 tables + job queues)]
    Clients --> API
    API --> SVC --> REPO --> DB
    W1 --> SVC
    W2 --> SVC
    W3 --> SVC
    W4 --> SVC
    W5 --> SVC
    DB -. job rows claimed by polling .-> VM
One solution, one MySQL database, one API deployment — with compute-heavy work isolated into worker processes on a dedicated VM, coordinated through database job queues rather than service-to-service calls.

What GMI Is — Named Precisely

GMI is a modular monolith with worker processes. All 35 projects build from one solution against one shared domain model. Every one of the 336 API endpoints deploys as a single application. Every table lives in one MySQL database. The 15 background console applications — report engine, email/SMS senders, billing engine, SEO page builder, and the rest — are separate processes, but they are not microservices: they run the same shared libraries against the same database, and they coordinate through database job tables, never through service-to-service HTTP. That last part is a standing architectural rule, not an accident.

Modularity lives at the code level — repository interfaces, service seams, dependency injection throughout — where it costs nothing at runtime. Distribution exists only where a process boundary buys something concrete.

THE TRADE
rendering diagram…
flowchart LR
    subgraph MS[Microservices buy...]
      A1[Independent team deploys]
      A2[Per-service tech choices]
    end
    subgraph COST[...at the price of]
      B1[Network failure modes<br/>where function calls were]
      B2[Distributed transactions +<br/>eventual consistency]
      B3[Deploy orchestration +<br/>observability sprawl]
      B4[Per-service infrastructure cost]
    end
    subgraph GMI[GMI's position]
      C1[One engineering seat:<br/>the benefits are worthless here]
      C2[Modular seams in CODE<br/>not in the network]
      C3[Worker processes deliver<br/>isolation + scaling anyway]
    end
    MS --> COST
    COST --> GMI
Microservices trade in-process simplicity for organizational independence. With one engineering seat, that trade buys nothing and costs everything — the industry's recent monolith revival says the quiet part out loud.

Why Not Microservices

Microservices solve an organizational problem: many teams shipping independently without stepping on each other. The price is a distributed-systems tax — network calls that fail where function calls couldn't, distributed transactions, eventual consistency, deploy orchestration, per-service infrastructure, and observability sprawl. Paying that tax makes sense at fifty teams. It makes no sense at one.

The industry has been saying this out loud lately: Amazon Prime Video published a 90% cost reduction from collapsing a microservices workflow back into a monolith, and the "majestic monolith" / modular-monolith pattern (37signals, Shopify) has moved from contrarian to mainstream. GMI never had to migrate back — it was built this way on purpose, with the seams in the code ready if the organization ever grows into needing real service boundaries.

Microservices Benefits, Harvested Without the Tax

The two things teams actually want when they reach for microservices are fault isolation and independent scaling. GMI gets both from plain operating-system processes:

  • Fault isolation: a crash mid-report-generation on the VM cannot take the API down — they are separate processes on separate compute. Each worker is a systemd service with automatic restart.
  • Independent scaling: request traffic scales on the Functions plan; compute-heavy batch work (multi-threaded report generation, ML forecasting, 2,500-page SEO builds, social-card rendering) scales on the report VM. Neither's load profile disturbs the other.
  • Async decoupling: the database doubles as the job queue. A refresh request is a row; a worker polls, claims, processes, and stamps completion. Durable, transactional, observable with a SELECT — no message broker to operate.

And the monolith's own superpowers stay intact: one deploy pipeline, transactions that just work, refactoring across the whole domain in one commit, and a single debugger session that can see everything.

When This Would Change

Architecture is a bet on constraints, and the constraints are named: this shape is right while one small team owns the whole domain and one MySQL instance carries the load. The pressure points that would justify extracting a real service are (1) multiple teams needing independent deploy cadence, (2) a component needing a radically different runtime or scaling model, or (3) the database becoming a contention bottleneck that read replicas and caching can't absorb. Because modularity already lives at the interface seams, any such extraction starts from clean lines — the modular monolith is the architecture that keeps its options open while spending nothing to do so.