HIGH-LEVEL DESIGN
VietGara — Multi-Tenant SaaS Platform for Auto Garage Management & Maintenance
Version 1.7 — October 9, 2026 · Cloud: Google Cloud Platform (GCP) · Backend: Go
1. Introduction
The High-Level Design (HLD) describes the overall design of the VietGara platform at the module/service level: design principles, the list of business modules and their responsibilities, the main business flows spanning multiple modules, and a consolidated technology table. This document bridges the SAD (C4 Model — structure) and the LLD (detailed design of individual services).
Technology decisions (options, pros/cons, criteria) are analyzed in detail in the separate Architecture Decision Records (ADR) document — the HLD only states the outcomes and applies them to the concrete design.
2. Design Principles
- Modules are split by business bounded context (Domain-Driven Design), not by database table (ADR-001).
- The data isolation unit (tenant) is garage_id, not user_id — one owner can manage multiple independent garages (ADR-005).
- Data ownership per module: each module owns its tables and never queries another module’s tables directly — it calls that module’s use-case interface. In the modular monolith all modules share one database (ADR-008); a module split into a service takes its tables to its own database.
- Synchronous communication (gRPC internally, REST/JSON for clients through grpc-gateway, ADR-010) for user-facing flows awaiting a response; asynchronous communication (Kafka + Outbox Pattern, ADR-004) for cross-module business operations.
- Authorization is evaluated per request from the database (garage membership, staff groups, role permissions), never from token claims, so revocation is immediate (ADR-007).
- Offline-first design for the Technician App — accepting sync latency in favor of an uninterrupted in-workshop experience.
3. List of Business Modules
| # | Module | Main Responsibility | Key Data Owned |
|---|---|---|---|
| 1 | Identity & Access | Users, e-mail/password and Google sign-in, e-mail OTP verification and password reset, sessions, platform admins, interface language | users, platform_admins; sessions and OTPs in Redis |
| 2 | Tenant/Garage Management | Garages (1 user – many garages) and their configuration, staff, invitations, owner-defined roles, staff groups; the garage access check (RBAC) used by every module | garages, garage_staff, staff_invitations, roles, role_permissions, staff_groups, staff_group_members |
| 3 | License Management | Plans with limits (garages, staff, transactions/day), one subscription per owner, entitlements and enforcement; plan changes and renewals by bank transfer, approved by a platform admin, with account credit for unused days (Release 5.2) | plans, subscriptions, plan_payment_requests, credit_ledger, platform_payment_settings, entitlements |
| 4 | Customer & Vehicle | Garage-owned customer profiles of vehicle owners (no user required; optional link to a user, Release 5.1), customer tax profiles, vehicles (identity fields locked while live documents use them; corrections need customer.correct) | customers, customer_tax_profiles, customer_user_links, vehicles |
| 5 | Quote & Pricing | Quotes; customer approval either via a public link or recorded manually by staff, per the garage’s/quote’s chosen mode; converts an approved quote into a draft repair order | quotes, quote_lines |
| 6 | Repair Order | Repair orders (Kanban board, status derived from per-line progress), technician assignment | repair_orders, repair_order_lines |
| 7 | Inventory | How many of a part there are and what happened to them: stock in/out/on-hand always in the part’s base unit; posts the repair order’s stock issue document (created with the order, reserves its parts, posted when the order closes) as a repair_order.closed consumer; goods receipt documents, average cost, write-off, returns and stock count (Phase 7); reservation through the draft stock issue (Release 7.0); low-stock alerts (Release 10.1). Uses Catalogue for the part, its units and categories | stock_transactions, stock_balance, goods_receipts, stock_issues, part_suppliers; from Phase 7 also stock_counts, part_barcodes |
| 8 | Billing & Settlement | Settlement from a closed repair order, single payer (the customer), recorded payments with a numbered payment voucher, voids; serves FR-REC-01’s receivables view off the same listing | settlements, settlement_payments |
| 9 | Receivables | Users receivable (reminders, reconciliation) and supplier payables — Phase 13 (Release 13.0); Release 1.4’s FR-REC-01 is served by Billing & Settlement directly, no tables of its own yet | none until Release 13.0 (then receivables, payment_records) |
| 10 | Insurance (Phase 17) | The garage’s insurers directory; claim tracking around a repair order: deadlines, assessment, insurer-agreed quote, document checklist, status, settlement split; optional submission adapters | insurers, insurance_claims, claim_events, claim_documents |
| 11 | Payment Gateway Adapter | VNPay/Momo/ZaloPay integration | payment_transactions |
| 12 | E-Invoice Service | E-invoice issuance | invoices, invoice_status_log |
| 13 | Document/Print Service | PDF generation (quote, repair order, settlement, payment voucher) from a print model per document type and a versioned template registry (A4/A5 Phase 10, thermal Phase 14, designer Phase 14); Excel export (stock movement, revenue, receivables) | print_templates and print_template_versions from Release 14.2; built-in templates are code |
| 14 | Notification Service | E-mail (registration confirmation, renewal reminders, FR-NOT-04); Push is Phase 11, more e-mail notifications (FR-NOT-05) and low-stock alerts Phase 10, booking reminders Phase 12, SMS and Zalo ZNS Phase 16. No RPCs of its own, event-driven only | notification_log |
| 15 | Reporting & Analytics | Revenue by period for a single garage (FR-RPT-01), read fresh from Billing & Settlement; the platform admin dashboards are Phase 12, technician performance and profit per repair order Phase 13, the multi-garage dashboard Phase 15 | none in Release 1.5; a consolidated read model (CQRS) arrives when those later phases need one |
| 16 | Customer BFF | Consolidated API for the Customer App | stateless |
| 17 | Technician BFF | Consolidated API for the Technician App, offline sync support | sync_checkpoints |
| 18 | Catalogue | What a part is, and the reference data around it, used by Inventory, Quote and Repair Order and using none of them: the part itself (SKU, name, brand, category, base unit, selling price, active flag), the garage’s units of measure, a part’s packaging units and the conversion to its base unit, the part category tree (three levels), part images, and from Phase 7 the job catalogue with suggested parts. The delete-or-deactivate rule is shared platform code (FR-CAT-07) | parts (the part), units, part_units, part_categories, part_images, jobs, job_parts |
| 19 | Booking (Phase 12) | Booking settings, slot availability computed on read, bookings and their lifecycle, public booking page, reminders; conversion to a quote or repair order | booking_settings, blocked_periods, bookings |
| 20 | Files | The file registry: ownership, kind, size, checksum, public or signed access over the local and GCS storage backends | files |
| 21 | Supplier | The garage’s directory of parts suppliers (name, tax code, bank details, contact), separate from customers and insurers; supplier link and portal in Phase 17 | suppliers, supplier_user_links (Phase 17) |
3.1. Catalogue and Inventory: who owns what
The split follows one question. Catalogue answers “what is this part?” — the part (parts), its units and packaging, categories, brand, selling price and images, and later the jobs that suggest parts. Inventory answers “how many are there, and what happened to them?” — the balance, the movement ledger and, from Phase 7, receipts, issues, counts and cost. The dependency goes one way:
flowchart LR INV[Inventory] -- "part, unit, conversion" --> CAT[Catalogue] QT[Quote & Pricing] -- "part, unit, price prefill" --> CAT RO[Repair Order] -- "part, unit" --> CAT
- Catalogue knows nothing about quantities. It does not read stock, quotes or repair orders; when it must know whether a part is in use (to refuse a base-unit change or to choose delete or deactivate) it asks a port wired in
cmd/server. - Every quantity is converted to the base unit once, by Catalogue, when it is saved; Inventory, Quote and Repair Order store the base amount plus the entered unit and amount (Database Design 5.9).
- A quote prefills its price from the part in Catalogue, so Quote has no dependency on Inventory.
- The stock-on-hand screen needs both halves. Inventory serves it: it asks Catalogue for the parts that match the search, category or brand and adds its balances, or starts from its own balances for “low stock only” and sort by quantity, and then fetches those parts. Neither module stores the other’s data.
- Both are modules of the one modular-monolith process in Phase 1 (ADR-001); they split into services only with the rest in Phase 15, along the same line. The contracts are
vietgara.catalogue.v1(parts, units, categories, images) andvietgara.inventory.v1(stock, receipts, movements). - Business view of the procedures that use both: Business Procedures P10 to P13.
4. Main Business Flows
4.1. Quote → Repair → Settlement → Invoice → Payment Flow
sequenceDiagram actor SA as Service Advisor (Owner Web) participant Q as Quote & Pricing participant R as Repair Order participant I as Inventory participant B as Billing & Settlement participant E as E-Invoice participant P as Payment Adapter SA->>Q: create quote Q-->>SA: quote created SA->>Q: customer approves Q->>R: convert to repair order R->>R: assign technicians, update line progress SA->>R: close order (every line completed) R-)I: repair_order.closed (Kafka) → deduct stock for part lines SA->>B: create settlement (direct request, FR-BIL-01 — not event-triggered) B->>B: record payments, issue payment vouchers B-)E: settlement.created (Kafka) E->>E: issue invoice P->>P: customer pays, gateway webhook confirms P-)SA: payment.confirmed (Kafka → Receivables)
Every step that writes financial data (settlement, invoice, payment) uses the Outbox Pattern to guarantee no event is lost if a service crashes mid-way — details in Event Design.
4.2. Owner Registration and First Garage (1 user – many garages)
sequenceDiagram actor U as Owner participant I as Identity participant T as Tenant/Garage participant L as License participant N as Notification participant M as Mail (Mailjet) U->>I: register (e-mail, password) I->>M: verification OTP (2 minutes) U->>I: verify e-mail with OTP I-)T: user.email_verified I-)L: user.email_verified I-)N: user.email_verified T->>T: create default roles L->>L: create 14-day trial subscription N->>M: registration-confirmation e-mail (Release 1.5) U->>T: create garage #1 T->>L: check garage limit (plan) T->>T: default staff groups for the garage T-)T: garage.created (outbox → Kafka) U->>T: create garage #2 (same user, same subscription) T->>L: check garage limit → PLAN_LIMIT_REACHED on a 1-garage plan
Google sign-up skips the OTP step: the account is created verified and the same user.email_verified event follows.
4.3. Staff Invitation
sequenceDiagram actor O as Owner / Manager participant T as Tenant/Garage participant L as License participant M as Mail actor S as Invitee (existing user) O->>T: look up by e-mail or phone T-->>O: candidate (member / invited / none) O->>T: invite with staff groups T->>L: check staff limit (members + pending invitations) T->>M: invitation e-mail (invitee's language) S->>T: accept (or decline) from /invitations/:id T->>T: create membership and group assignments
5. Consolidated Technology Table
Detailed analysis of the options and the reasoning behind each decision below is presented in the Architecture Decision Records (ADR) document.
| Layer | Chosen Technology | Related ADR |
|---|---|---|
| Cloud Provider | Google Cloud Platform (GCP), asia-southeast1 region | ADR-002 |
| Backend Language | Go 1.26 | ADR-003 |
| Message Broker | Apache Kafka (managed on GCP; KRaft locally), franz-go client | ADR-004 |
| Multi-Tenancy | PostgreSQL Row-Level Security, tenant = garage_id | ADR-005 |
| Internal Communication | gRPC / Protocol Buffers | ADR-006 |
| Client API | REST/JSON via grpc-gateway, OpenAPI generated from the protos | ADR-010 |
| Identity Provider | In-house Identity module (e-mail/password + e-mail OTP, Google OAuth) | ADR-007 |
| Transactional e-mail | Mailjet (Mailpit locally) | ADR-011 |
| Primary Database | Cloud SQL for PostgreSQL | ADR-008 |
| Container Orchestration | GKE Autopilot | ADR-009 |
| Cache | Memorystore (Redis) | — |
| Object Storage | Cloud Storage | — |
| API Gateway | Kong on GKE | — |
| Owner Web (garage owners and staff) | React + TypeScript, TanStack Query, react-i18next (Vietnamese/English), UI from @vietgara/web-ui (Tailwind v4, Radix UI, Lucide, Motion; light and dark themes) | — |
| Admin Web (platform admins and admin staff) | Same stack as the Owner Web, separate repository and deployment (vietgara-admin-web) | — |
| Web UI kit | vietgara-web-ui: design tokens, Tailwind theme and React components, source-only, pinned by tag in both web apps | — |
| Mobile Apps (owner, admin, customer, technician) | Flutter (shared iOS/Android codebase) | — |
| CI/CD | GitHub Actions (CI for backend, owner web, admin web, web UI kit, owner app, admin app, proto, e2e) + ArgoCD (GitOps delivery) | — |
| IaC | Terraform (Release 9.2) | — |
| Observability | Structured logs (slog) now; OpenTelemetry tracing from Release 1.5; Prometheus metrics, central logging and alerting from Release 9.1 | — |
6. Phased Rollout Strategy
| Phase | Architectural Scope |
|---|---|
| Phase 1 — MVP | Modular Monolith (a single Go binary, packages organized by bounded context) for modules 1–9; Owner Web; a single Cloud SQL database (one schema, tables owned per module); Kafka for domain events from Release 1.1 |
| Phase 2 — Mobile App Foundations | Owner and Admin Apps on the existing REST API; Customer App and Technician App foundations via BFFs |
| Phase 3 — Terminology | account becomes user in Identity, database, API and events |
| Phase 4 — Quality Foundation | Test data repository, seeder and purge, blackbox and web E2E suites; no runtime change |
| Phase 5 — Customer & Garage Foundations | Files module; lifecycle rule; payment vouchers in Billing; insurance code removed from Billing and Customer; partners becomes the Supplier module’s suppliers; customer links and tax profiles |
| Phase 6 — Catalogue & Security | Catalogue module (units, categories, images); MFA in Identity |
| Phase 7 — Inventory & Job Catalogue | Goods receipt documents, average cost, stock count and adjustments, barcodes and import in Inventory |
| Phase 8 — Security & Support Access | Kong rate limiting and security headers; platform roles, staff groups and support grants in Identity and Tenant |
| Phase 9 — Production Readiness | Metrics, alerting, central logging; Terraform for GCP; load-tested at about 50 garages |
| Phase 10 — Quoting & Printing | Print model and template registry in Document/Print; Notification Service e-mail expansion and stock alerts |
| Phase 11 — Mobile Core | Push gateway and device tokens; WebSocket gateway only if needed; offline sync and photos for the technician app (Repair Order sync API); customer app quote approval and live progress |
| Phase 12 — Scheduling | Booking module with a public booking page; a platform-admin read path for Platform Analytics (aggregate counts only) |
| Phase 13 — Money & Insight | Receivables module with its own state; payment-out vouchers, purchase orders and supplier payables; profit report |
| Phase 14 — Advanced Printing & Templates | Thermal/ESC/POS rendering; mobile LAN printing and the print agent (a client-side component pairing over an outbound WebSocket); HTML-to-PDF renderer and the template designer |
| Phase 15 — Scale | Split the Repair Order service and all remaining modules into independent services based on real load; tenant silos; Reporting moves to a dedicated CQRS read model; transfers between garages |
| Phase 16 — Payments & E-invoicing | Payment Adapter and E-Invoice as independent services behind adapters (their consumers already use Kafka); SMS/Zalo channels; License renewal charged through the Payment Adapter |
| Phase 17 — Insurance & Partner Expansion | Insurance module (insurers directory, claim tracking, then submission adapters); MISA/BKAV e-invoice adapters; supplier link and portal |