216 lines
9.2 KiB
Markdown
216 lines
9.2 KiB
Markdown
---
|
|
title: System Architecture
|
|
tags: [architecture, system, overview]
|
|
created: 2026-05-23
|
|
---
|
|
|
|
# System Architecture
|
|
|
|
End-to-end architecture for the **Amn** escrow marketplace platform — a two-repo system (frontend + backend) that brokers crypto-escrowed transactions between buyers and sellers.
|
|
|
|
> [!info]
|
|
> Read [[Backend Architecture]] and [[Frontend Architecture]] for component-level detail. Read [[Infrastructure]] for deployment topology.
|
|
|
|
---
|
|
|
|
## 1. High-level topology
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Browser[Browser / PWA]
|
|
CDN[Static CDN]
|
|
Nginx[Nginx Reverse Proxy<br/>:80/:443]
|
|
FE[Next.js Frontend<br/>standalone server<br/>:8083]
|
|
BE[Express Backend<br/>+ Socket.IO<br/>:5001]
|
|
Mongo[(MongoDB 8<br/>primary runtime store)]
|
|
PG[(PostgreSQL 18<br/>migration target / quote table)]
|
|
Redis[(Redis 8)]
|
|
RN[Request Network<br/>Pay-in + webhooks]
|
|
CFWorker[Durable webhook ingress<br/>roadmap]
|
|
SMTP[SMTP<br/>Nodemailer]
|
|
OAI[OpenAI API]
|
|
BC[Blockchain RPC<br/>Alchemy / WalletConnect]
|
|
|
|
Browser -->|HTTPS + WSS| CDN
|
|
Browser -->|HTTPS| Nginx
|
|
Nginx --> FE
|
|
Nginx --> BE
|
|
FE -->|REST /api/*| BE
|
|
FE -.->|Socket.IO| BE
|
|
BE --> Mongo
|
|
BE -.->|PG_URL + migration/quote paths| PG
|
|
BE --> Redis
|
|
BE -->|Pay-in intent / status| RN
|
|
RN -.->|Signed webhook| CFWorker
|
|
CFWorker -.->|Forward / replay| BE
|
|
BE --> SMTP
|
|
BE --> OAI
|
|
FE -->|Wallet Connect| BC
|
|
BE -->|Tx verify| BC
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Request lifecycle (typical authenticated REST call)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
actor U as User
|
|
participant FE as Frontend (Next.js)
|
|
participant AX as Axios Client
|
|
participant BE as Backend (Express)
|
|
participant MW as Middleware Chain
|
|
participant SVC as Service Layer
|
|
participant DB as MongoDB
|
|
participant SK as Socket.IO
|
|
|
|
U->>FE: Interact with UI
|
|
FE->>AX: useMutation(...).mutate(payload)
|
|
AX->>AX: Attach Authorization: Bearer <jwt>
|
|
AX->>BE: POST /api/marketplace/requests
|
|
BE->>MW: helmet, cors, body-parser
|
|
MW->>MW: authMiddleware → verify JWT
|
|
MW->>MW: validation middleware
|
|
MW->>SVC: controller invokes service
|
|
SVC->>DB: Mongoose create / update
|
|
DB-->>SVC: document
|
|
SVC->>SK: emitToRoom("user-{id}", "request:created", payload)
|
|
SVC-->>BE: response payload
|
|
BE->>AX: 200 { success, data }
|
|
AX->>FE: React Query cache update
|
|
FE-->>U: UI re-render
|
|
```
|
|
|
|
> [!note] Postgres status on `integrate-main-into-development`
|
|
> Backend `2.6.79` includes Drizzle schemas, migrations, repository implementations, backfill/verify tooling, and conditional oracle quote persistence to Postgres. It is not a full runtime cutover: ordinary services still call Mongoose models directly and MongoDB remains the primary store. See [[Postgres Runtime Cutover Status]] for the current boundary.
|
|
|
|
Concurrent realtime path:
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant FE as Frontend
|
|
participant BE as Backend
|
|
FE->>BE: socket.connect() w/ auth token
|
|
BE-->>FE: socket connected
|
|
FE->>BE: join-user-room <userId>
|
|
BE-->>FE: joined user-{userId}
|
|
Note over BE,FE: long-lived connection
|
|
BE-->>FE: emit "notification:received"
|
|
FE->>FE: update notification badge
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Deployment topology
|
|
|
|
Production runs as a single Docker Compose stack (`backend/docker-compose.production.yml`) behind an internal Nginx that proxies to both the frontend and backend containers. Watchtower watches the container registry (`git.manko.yoga/manawenuz/escrow-backend`) and auto-pulls new images tagged `latest`.
|
|
|
|
| Layer | Service | Image | Port | Purpose |
|
|
|---|---|---|---|---|
|
|
| Edge | Nginx | `nginx:alpine` | 8083 | Reverse proxy + static assets |
|
|
| App | Frontend | `nickapp-frontend:latest` | 8083 (internal) | Next.js standalone |
|
|
| App | Backend | `nickapp-backend:latest` | 5001 (internal) | Express + Socket.IO |
|
|
| Data | MongoDB | `mongo:8.0` | 27017 (internal) | Primary store |
|
|
| Data | PostgreSQL | `postgres:18` / `postgres:18-alpine` | 5432 (internal) | Migration target; required for PG backfill/verify and oracle `payment_quotes` when enabled |
|
|
| Data | Redis | `redis:8-alpine` | 6379 (internal) | Cache + sessions + rate-limit counters |
|
|
|
|
External SSL termination, DNS, and CDN are assumed to live in front of Nginx (CloudFlare / nginx-proxy / similar).
|
|
|
|
> [!note]
|
|
> Dev uses `backend/docker-compose.dev.yml` (no Nginx, no frontend container — developer runs FE locally with `yarn dev`). See [[Docker Setup]] for full compose breakdown.
|
|
|
|
---
|
|
|
|
## 4. Network ports
|
|
|
|
| Port | Process | Visibility | Notes |
|
|
|---|---|---|---|
|
|
| 8083 | Frontend (Docker) | Public via Nginx | `next start` standalone |
|
|
| 3000 | Frontend (local dev) | localhost | `yarn dev` — hot reload |
|
|
| 5001 | Backend (Express + Socket.IO) | Public via Nginx `/api` & `/socket.io` | One process, two protocols |
|
|
| 27017 | MongoDB | Internal | Mapped to host only in dev |
|
|
| 6379 | Redis | Internal | Mapped to host only in dev |
|
|
| 222 | Gitea SSH | Public | Used for `git clone` |
|
|
|
|
---
|
|
|
|
## 5. Data flow patterns
|
|
|
|
### 5.1 Read path
|
|
|
|
REST `GET` requests are cache-able. React Query on the frontend de-duplicates concurrent requests, retries on failure (exponential backoff), and treats data as stale after `staleTime` (default 60s, varies per query). Backend reads use Mongoose lean queries where possible for performance.
|
|
|
|
### 5.2 Write path
|
|
|
|
Mutations follow optimistic-then-confirm:
|
|
1. Frontend `useMutation` issues the request, often with `onMutate` updating cache optimistically.
|
|
2. Backend validates → writes to MongoDB → emits affected socket rooms → returns canonical response.
|
|
3. Frontend invalidates related query keys; React Query refetches.
|
|
4. Other connected clients receive the socket event and refetch on their side.
|
|
|
|
### 5.3 Webhook path (inbound)
|
|
|
|
External services POST payment callbacks to provider-specific webhook routes. The current primary path is Request Network at `/api/payment/request-network/webhook`; the target architecture puts a durable ingress worker in front of the backend so raw delivery evidence can be replayed after outages. The backend remains the trust oracle: it verifies signatures, deduplicates deliveries, applies Transaction Safety Provider checks, updates ledger/payment state, and emits Socket.IO events to both buyer and seller rooms.
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant RN as Request Network
|
|
participant WK as Durable ingress worker
|
|
participant BE as Backend
|
|
participant DB as MongoDB
|
|
participant Buyer
|
|
participant Seller
|
|
RN->>WK: POST signed webhook<br/>delivery id + raw body
|
|
WK->>WK: Store immutable delivery evidence
|
|
WK->>BE: Forward / replay webhook
|
|
BE->>BE: Verify RN signature + idempotency
|
|
BE->>BE: Transaction Safety Provider checks tx hash, recipient, token, amount, confirmations
|
|
BE->>DB: Append ledger entry + Payment escrowState="funded"
|
|
BE->>DB: PurchaseRequest.updateOne(..., {status:"payment"})
|
|
BE-->>Buyer: socket emit "payment:status-updated"
|
|
BE-->>Seller: socket emit "request:funded"
|
|
BE-->>WK: 200 OK
|
|
```
|
|
|
|
See [[PRD - Request Network In-House Checkout]] and [[Request Network Integration Constraints]] for the full Request Network sequence.
|
|
|
|
---
|
|
|
|
## 6. Scaling considerations
|
|
|
|
| Concern | Current state | Scale-out path |
|
|
|---|---|---|
|
|
| Backend stateless? | Yes — JWT-only auth, no in-memory session | Run N replicas behind LB; use Redis pub/sub adapter for Socket.IO |
|
|
| MongoDB | Single-node | Replica set → sharding by `buyerId` |
|
|
| PostgreSQL | Dev/staging service for migration work | Managed Postgres or hardened self-hosted PG with backups/PITR before cutover |
|
|
| Redis | Single-node | Cluster mode; separate cache vs session DBs |
|
|
| Socket.IO | Single process | `@socket.io/redis-adapter` for multi-node fan-out |
|
|
| File uploads | Local `uploads/` mount | S3 / R2; multer-s3 adapter |
|
|
| Logs | Container stdout | Loki / ELK |
|
|
| Errors | Sentry (`@sentry/nextjs` on FE) | Add Sentry backend SDK |
|
|
|
|
> [!warning]
|
|
> Before horizontal-scaling backend, switch Socket.IO to the Redis adapter — otherwise users on different nodes will not receive each other's events.
|
|
|
|
---
|
|
|
|
## 7. Cross-cutting concerns
|
|
|
|
- **Auth** — Bearer JWT on REST, same token also passed via Socket.IO `auth` payload on connect. See [[Security Architecture]] & [[Authentication Flow]].
|
|
- **i18n** — Frontend supports en/fa/ar/fr/cn/vi via `i18next`. RTL handled by `stylis-plugin-rtl` per direction. See [[Internationalization & RTL]].
|
|
- **Realtime** — All notification/chat/payment state changes broadcast over Socket.IO. See [[Real-time Layer]].
|
|
- **Errors** — Standardized envelope `{ success: false, error: { code, message, details } }` via `backend/src/shared/utils/response-handler.ts`. Sentry on frontend.
|
|
- **Idempotency** — Payment webhooks idempotent by `providerPaymentId` + status. Pay-out uses a client-supplied `reference` string.
|
|
|
|
---
|
|
|
|
## 8. Related documents
|
|
|
|
- [[Backend Architecture]] — module-level walk-through of the Express app
|
|
- [[Frontend Architecture]] — Next.js App Router & section organization
|
|
- [[Infrastructure]] — Docker, compose, registry, Watchtower
|
|
- [[Real-time Layer]] — Socket.IO setup, rooms, events
|
|
- [[Security Architecture]] — auth, hashing, rate-limit, webhook HMAC
|
|
- [[Tech Stack]] — exact versions & purpose of every dependency
|
|
- [[Escrow Flow]] — current Request Network pay-in, ledger, and custody release flow
|