Skip to content

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

#ModuleMain ResponsibilityKey Data Owned
1Identity & AccessUsers, e-mail/password and Google sign-in, e-mail OTP verification and password reset, sessions, platform admins, interface languageusers, platform_admins; sessions and OTPs in Redis
2Tenant/Garage ManagementGarages (1 user – many garages) and their configuration, staff, invitations, owner-defined roles, staff groups; the garage access check (RBAC) used by every modulegarages, garage_staff, staff_invitations, roles, role_permissions, staff_groups, staff_group_members
3License ManagementPlans 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
4Customer & VehicleGarage-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
5Quote & PricingQuotes; 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 orderquotes, quote_lines
6Repair OrderRepair orders (Kanban board, status derived from per-line progress), technician assignmentrepair_orders, repair_order_lines
7InventoryHow 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 categoriesstock_transactions, stock_balance, goods_receipts, stock_issues, part_suppliers; from Phase 7 also stock_counts, part_barcodes
8Billing & SettlementSettlement 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 listingsettlements, settlement_payments
9ReceivablesUsers 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 yetnone until Release 13.0 (then receivables, payment_records)
10Insurance (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 adaptersinsurers, insurance_claims, claim_events, claim_documents
11Payment Gateway AdapterVNPay/Momo/ZaloPay integrationpayment_transactions
12E-Invoice ServiceE-invoice issuanceinvoices, invoice_status_log
13Document/Print ServicePDF 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
14Notification ServiceE-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 onlynotification_log
15Reporting & AnalyticsRevenue 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 15none in Release 1.5; a consolidated read model (CQRS) arrives when those later phases need one
16Customer BFFConsolidated API for the Customer Appstateless
17Technician BFFConsolidated API for the Technician App, offline sync supportsync_checkpoints
18CatalogueWhat 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
19Booking (Phase 12)Booking settings, slot availability computed on read, bookings and their lifecycle, public booking page, reminders; conversion to a quote or repair orderbooking_settings, blocked_periods, bookings
20FilesThe file registry: ownership, kind, size, checksum, public or signed access over the local and GCS storage backendsfiles
21SupplierThe garage’s directory of parts suppliers (name, tax code, bank details, contact), separate from customers and insurers; supplier link and portal in Phase 17suppliers, 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) and vietgara.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.

LayerChosen TechnologyRelated ADR
Cloud ProviderGoogle Cloud Platform (GCP), asia-southeast1 regionADR-002
Backend LanguageGo 1.26ADR-003
Message BrokerApache Kafka (managed on GCP; KRaft locally), franz-go clientADR-004
Multi-TenancyPostgreSQL Row-Level Security, tenant = garage_idADR-005
Internal CommunicationgRPC / Protocol BuffersADR-006
Client APIREST/JSON via grpc-gateway, OpenAPI generated from the protosADR-010
Identity ProviderIn-house Identity module (e-mail/password + e-mail OTP, Google OAuth)ADR-007
Transactional e-mailMailjet (Mailpit locally)ADR-011
Primary DatabaseCloud SQL for PostgreSQLADR-008
Container OrchestrationGKE AutopilotADR-009
CacheMemorystore (Redis)—
Object StorageCloud Storage—
API GatewayKong 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 kitvietgara-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/CDGitHub Actions (CI for backend, owner web, admin web, web UI kit, owner app, admin app, proto, e2e) + ArgoCD (GitOps delivery)—
IaCTerraform (Release 9.2)—
ObservabilityStructured logs (slog) now; OpenTelemetry tracing from Release 1.5; Prometheus metrics, central logging and alerting from Release 9.1—

6. Phased Rollout Strategy

PhaseArchitectural Scope
Phase 1 — MVPModular 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 FoundationsOwner and Admin Apps on the existing REST API; Customer App and Technician App foundations via BFFs
Phase 3 — Terminologyaccount becomes user in Identity, database, API and events
Phase 4 — Quality FoundationTest data repository, seeder and purge, blackbox and web E2E suites; no runtime change
Phase 5 — Customer & Garage FoundationsFiles 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 & SecurityCatalogue module (units, categories, images); MFA in Identity
Phase 7 — Inventory & Job CatalogueGoods receipt documents, average cost, stock count and adjustments, barcodes and import in Inventory
Phase 8 — Security & Support AccessKong rate limiting and security headers; platform roles, staff groups and support grants in Identity and Tenant
Phase 9 — Production ReadinessMetrics, alerting, central logging; Terraform for GCP; load-tested at about 50 garages
Phase 10 — Quoting & PrintingPrint model and template registry in Document/Print; Notification Service e-mail expansion and stock alerts
Phase 11 — Mobile CorePush 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 — SchedulingBooking module with a public booking page; a platform-admin read path for Platform Analytics (aggregate counts only)
Phase 13 — Money & InsightReceivables module with its own state; payment-out vouchers, purchase orders and supplier payables; profit report
Phase 14 — Advanced Printing & TemplatesThermal/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 — ScaleSplit 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-invoicingPayment 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 ExpansionInsurance module (insurers directory, claim tracking, then submission adapters); MISA/BKAV e-invoice adapters; supplier link and portal