Source document · API/docs/ARCHITECTURE.md
API Architecture
Runtime topology
The Go API is one modular process with PostgreSQL as its durable system of record. In production, a reverse proxy terminates HTTPS and forwards API traffic to the process. The API calls MailAfrica for transactional account emails, S3-compatible object storage for media, and LiveKit for scoped meeting tokens. Firebase push delivery is optional. An in-process outbox worker publishes durable order notifications.
The API sends credentials and session tokens only over HTTPS in production. Upload and read media bytes travel directly between the client and object storage through short-lived signed URLs. Audio/video media flows through LiveKit; the Go API authorizes room access but does not relay media.
One deployable application
The backend begins as a modular monolith. Auth, orders, payments, chat, and the other product areas are modules in one Go application. They are not independently deployed microservices.
This shape keeps local development and operations simple and lets order creation and its notification event commit in one PostgreSQL transaction.
Dependency direction
HTTP/WebSocket handlers
↓
Application module interfaces
↓
Domain rules and use cases
↓
Repository/provider interfaces
↓
PostgreSQL, Stripe, S3, APNs, FCM, email adaptersHandlers decode requests, obtain the authenticated actor, call one use case, and encode the standard response. They do not contain SQL or workflow rules.
Domain code does not import HTTP frameworks, PostgreSQL drivers, Stripe clients, storage SDKs, or push providers. Adapters implement interfaces owned by the module that uses them.
Module ownership
| Module | Owns |
|---|---|
| Auth | Credentials, OTPs, sessions, refresh rotation, revocation, external identity verification |
| Users | Account profile, locale, status, roles, account deletion orchestration |
| Catalog | Service categories and platform-defined services |
| Providers | Provider registration, approval, public profile, portfolio, availability |
| Listings | Business listings, menus, publishing subscription, featured placement |
| Orders | Order creation, price snapshots, order transitions, cancellation, completion |
| Payments | Payment attempts, manual confirmation, Stripe intents/webhooks |
| Sales | Sales records, manual sales, financial adjustments |
| Chat | Conversations, messages, unread/read state, realtime delivery |
| Media | Upload authorization, metadata, visibility, processing lifecycle |
| Notifications | Devices, preferences, push/email delivery |
| Reporting | Cross-module read models and accountant/admin reports |
| Outbox | Durable asynchronous event delivery and retry |
Order completion
Order completion is owned by the Orders application use case and occurs in one transaction:
- Authorize the provider/admin actor.
- Lock or version-check the order.
- Validate the transition and payment policy.
- Mark the order completed.
- Create exactly one completed-order snapshot.
- Create exactly one primary sale.
- Insert notification/reporting events into the outbox.
- Commit.
The API process runs an outbox worker that delivers order-created events as durable in-app notifications to both participants. Delivery is at-least-once, notification IDs make retries idempotent, temporary errors use bounded exponential backoff, and events that reach the attempt limit are marked dead-lettered. A notification provider outage does not undo the order creation.
Payments and orders
A Payment records a payment attempt or manual payment claim. It does not replace an Order and does not own order status. Manual “I have paid” moves the Payment to payment_submitted. An administrator confirms listing subscription payments; the owning provider or an administrator confirms order payments. Verified provider webhooks are not currently wired into the production request flow. Orders applies its own policy to the related order.
Data and media
PostgreSQL is the transactional system of record. Object storage holds media and documents. Domain records reference Media Asset IDs; raw storage keys remain internal. Private downloads use short-lived authorization. Large media bytes are not routed through Go application memory when direct signed transfer is available.
When DATABASE_URL is configured, production composition selects PostgreSQL
repositories for auth, catalog, orders, payments, notifications, provider
dashboard, chat, media metadata, community, lifestyle, reporting, and admin.
The media adapter issues S3 Signature V4 URLs; the production startup check
requires HTTPS storage credentials.
Asynchronous work
The transaction that changes domain state writes an outbox event in the same commit. A worker claims events with SKIP LOCKED, retries temporary failures with bounded backoff, and marks exhausted events for operator review.
Evolution
Extract a module into a separate deployment only when measured scaling, availability, security isolation, or team ownership creates a real need. Preserve its application interface and event contract during extraction.
Order creation persists the optional RFC3339 schedule_at value alongside the
order so scheduled bookings survive reloads and appear in order reads.