← Overview  Research appendix

Development & architecture diagrams · Rev 0.1 · 2026-08-28

MVGO Architecture

The build blueprint behind the playbook: system context, containers and modules, the event-sourced order lifecycle, the two sequences that define the product, where every rufiyaa moves, how it deploys, and how the first year of development is sequenced. Companion to 04-technical-architecture.md in the docs package.

Section 1 · who talks to what

System context

Five kinds of people touch the platform, and six external systems make it work. The one unusual actor is the Ministry of Transport: Maldivian law makes its read-only live dashboard a launch requirement, so it is drawn as a first-class consumer, not an afterthought.

Rider consumer app Driver / courier driver app Merchant merchant app + portal Ops team console Ministry of Transport regulatory dashboard MVGO platform rides · food · mart · express payments · trust · authority feed Go services · event-sourced core book · order · pay jobs · GPS · cash-out menus · settlement dispatch override · refunds live trips · km · driver histories BML gateway 3-DS cards (MPGS) Favara · PayMV via partner bank / PSP SMS OTP Dhiraagu · Ooredoo Push FCM · APNs OpenStreetMap MV extract + own fixes Boat ticketing RTL · Odiapp · Dhathuru auth · capture Favara Request · payouts login OTP status pushes map data (pull) search · book · e-ticket
Fig 1 — System context. Everything the platform must integrate on day one sits on the right in solid boxes; the dashed boat-ticketing edge is the Phase-3 boats seam. The coral edge is the legally mandated Ministry data feed — read-only, out of the platform, never into it.

Section 2 · what runs

Containers & modules

One Go modular monolith carries every domain module — Grab's patterns at a six-engineer scale. The module boundaries are drawn so any one of them can be split into its own service later without rewriting its neighbours.

Consumer app Flutter · iOS + Android Driver app Flutter · GPS streaming Merchant app + portal Flutter + web Ops · Ministry portals web · read-models API gateway — REST/GraphQL · auth · rate limits · push fan-out TLS · pinned Go modular monolith — one deployable, module boundaries = future services identity profiles catalog pricing (fare tables) orders ★ dispatch payments ★ comms (masked) trust · fraud authority analytics ★ event-sourced OSRM road ETA · MV extract ETA queries PostgreSQL + PostGIS state · events · ledger Redis GEO live drivers · offers NATS JetStream order events · projections Object storage docs · proof photos SQL · journal GEO r/w emit · consume uploads
Fig 2 — Containers. Clients never touch a datastore; the gateway is the single front door. The starred modules (orders, payments) are event-sourced from day one — every state change is an immutable event on JetStream that the ops console, Ministry portal and analytics replay as projections.

Module dependencies

orders state machines catalog pricing dispatch payments authority · analytics comms · trust menus · availability quote (fare table) ASSIGN needs a match hold · capture · split events only (async) events only
Fig 3 — Dependency rule. Solid edges are synchronous calls inside a request; dashed edges are async event consumption. Authority, analytics, comms and trust never sit on the booking hot path — if they are down, rides still book.

Section 3 · one machine for every vertical

The order lifecycle

Rides, food, mart, express and (later) boat tickets all run the same event-sourced state machine with per-vertical configuration. The one deliberate deviation from the Grab default: food money is only captured after the merchant confirms — the structural fix for the cancellation trap their users hate most.

DRAFT QUOTED REQUESTED CONFIRMED ASSIGNED IN_PROGRESS COMPLETE SETTLED price book merchant / centre OK match pickup drop T+1 FOOD: capture payment HERE, not before CANCELLED FAILED free — nothing captured free until driver en route ≥2 min refund + support Every transition = immutable event → JetStream → consumer status screens, driver job cards, ops map, Ministry feed, analytics — all projections of one log.
Fig 4 — One lifecycle, five verticals. RIDE skips the merchant leg (a taxi-centre auto-confirm), EXPRESS adds a jetty hand-off inside IN_PROGRESS, BOAT delegates CONFIRMED to the operator's API. Cancellation is free everywhere left of ASSIGNED — a product commitment enforced by the state machine, not by support staff.

Section 4 · the two flows that define the product

Sequences

Ride dispatch — request to driver in under 20 seconds

Rider app orders pricing dispatch Redis + OSRM Driver app 1 quote (from, to) 2 zone-pair lookup 3 upfront fare — MVR 70, fixed by law 4 book (payment method) 5 find driver 6 GEO nearby → road ETA rank 7 offer — full fare shown · 12 s window 8 accept 9 driver card · plate · honest p90 ETA 10 GPS stream → live map (server push, battery-sane) 11 complete trip 12 receipt · rate · coins — settle T+1 no offer beyond 12-min pickup — queue honestly
Fig 5 — Dispatch. Steps 6–8 are the marketplace: candidates come from Redis GEO filtered by zone adjacency, ranked by OSRM road ETA (Malé's one-way maze makes straight-line distance lie), then offered exclusively for 12 seconds each. The far-pickup cap is enforced at step 6, not apologised for at step 9.

Food payment — the anti-cancellation-trap timing

Consumer orders payments · ledger Merchant app Rider 1 checkout MVR 145 2 HOLD (card auth / Favara request) 3 new order — loud alert 4 CONFIRM (prep 15 min) 5 CAPTURE — money moves only now no confirm in 10 min → auto-cancel · hold released · nothing charged 6 assign rider when food nearly ready 7 tracked delivery · honest ETA band, auto-voucher past 15 min 8 delivered (photo if unattended) 9 split: merchant · platform · rider
Fig 6 — Food payment timing. The teal edges are the contract: a kitchen's confirmation is the event that takes the customer's money. Grab charges first and makes cancellation the customer's problem; this ordering makes a closed restaurant or a sold-out dish structurally free for the customer.

Section 5 · where every rufiyaa goes

Ledger money flow

All money movement is double-entry journal entries tied to order events — the same design the MMA will audit when the wallet licence is filed. One worked example, the MVR 145 order with a MVR 10 delivery fee:

Consumer pays MVR 155.00 Gateway suspense capture on CONFIRMED card / Favara Merchant payable MVR 123.25 (85% of basket) Platform revenue MVR 21.75 commission (15%) Rider payable MVR 10.00 delivery fee journal split on DELIVERED payouts via Favara — merchant T+1 · rider instant cash orders: rider float account debited at delivery, reconciled daily — same journal, different leg
Fig 7 — Double-entry from day one. Every arrow is a journal entry referencing the order event that caused it; account balances are projections. Refunds reverse journals — they never "adjust" a balance. Amounts in integer laari internally.

Section 6 · where it runs

Deployment

Single region (Mumbai — ~35 ms from Malé), managed data stores, one Kubernetes cluster. The Ministry portal gets its own ingress with an IP allowlist so regulatory access never shares a front door with the public apps.

Mobile + web consumer · driver · merchant Ministry portal IP-allowlisted users AWS ap-south-1 (Mumbai) — single region + warm standby posture Load balancer TLS · WAF Reg ingress allowlist · audit log Kubernetes cluster app ×3monolith pods workersprojections · payouts OSRMrouting NATS JS3-node gatewayAPI + push RDS Postgres primary → standby (sync) Redis (managed) GEO · offers · sessions Object storage photos · docs · exports Observability OTel · Grafana · booking-funnel alarms Secrets · KMS · IaC (Terraform) no shell-configured prod Offsite backups nightly · encrypted restore-tested monthly WAL + snapshots External egress: BML MPGS · Favara partner API · SMS · FCM/APNs all through NAT with per-provider circuit breakers
Fig 8 — Deployment. Everything stateful is managed (RDS, Redis, object storage); the cluster only runs replaceable pods. The coral path is the regulator's: separate ingress, allowlist, full audit log — provable isolation.

Section 7 · how it gets built

Delivery pipeline & roadmap

CI/CD — two lanes, one gate

Backend lane PR checkslint · unit · contract stagingmigrations gated · seeded e2e suitebooking sims prod (canary)manual approve · auto-rollback Mobile lane Flutter build3 apps · both stores internal trackTestFlight · Play internal beta cohortpilot drivers + staff phased release10% → 100% apps test against staging API API is versioned; old app versions keep working two releases back — phones in Malé update slowly.
Fig 9 — Pipeline. One human gate (prod approve) and one hard technical gate (schema migrations run and verified on staging first). Everything else is automatic, so a six-person team ships daily without a release manager.

Roadmap — four workstreams, four milestones

M0 · wk 1–6 M1 · wk 7–16 M2 · wk 17–24 M3 · wk 25+ Backend skeleton · orders · GEO · fares dispatch · payments v1 food vertical · promos express · mart · wallet core Mobile shell · design system · OTP rider + driver field pilot merchant app · food UX wallet UX · Dhivehi audit Integrations OSRM · SMS · push BML 3-DS · Favara Request settlement automation PSP wallet · boat APIs Reg & ops company · centre MoUs Ministry dashboard demo merchant onboarding ×150 MMA licence filing Ministry approval — launch gate first live ride food live wallet licence — P2 gate Diamonds are gates, not dates: coral gates block the next phase (regulatory), teal ones are launch moments. Slack lives inside phases, never inside gates.
Fig 10 — Delivery roadmap. The critical path runs through the coral diamonds: Ministry approval gates every ride, the MMA licence gates the wallet. Both are worked from week 1 — the engineering is sequenced so it is never the blocker.