Skip to Content
Architecture

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 adapters

Handlers 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

ModuleOwns
AuthCredentials, OTPs, sessions, refresh rotation, revocation, external identity verification
UsersAccount profile, locale, status, roles, account deletion orchestration
CatalogService categories and platform-defined services
ProvidersProvider registration, approval, public profile, portfolio, availability
ListingsBusiness listings, menus, publishing subscription, featured placement
OrdersOrder creation, price snapshots, order transitions, cancellation, completion
PaymentsPayment attempts, manual confirmation, Stripe intents/webhooks
SalesSales records, manual sales, financial adjustments
ChatConversations, messages, unread/read state, realtime delivery
MediaUpload authorization, metadata, visibility, processing lifecycle
NotificationsDevices, preferences, push/email delivery
ReportingCross-module read models and accountant/admin reports
OutboxDurable asynchronous event delivery and retry

Order completion

Order completion is owned by the Orders application use case and occurs in one transaction:

  1. Authorize the provider/admin actor.
  2. Lock or version-check the order.
  3. Validate the transition and payment policy.
  4. Mark the order completed.
  5. Create exactly one completed-order snapshot.
  6. Create exactly one primary sale.
  7. Insert notification/reporting events into the outbox.
  8. 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.