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
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 .-> VMWhat 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.
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 --> GMIWhy 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.