Files
nick-doc/01 - Architecture/System Architecture.md
2026-05-23 20:35:34 +03:30

7.7 KiB

title, tags, created
title tags created
System Architecture
architecture
system
overview
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

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)]
    Redis[(Redis 8)]
    SHK[SHKeeper<br/>Crypto Gateway]
    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 --> Redis
    BE -->|Pay-in / Pay-out| SHK
    SHK -.->|Webhook HMAC| BE
    BE --> SMTP
    BE --> OAI
    FE -->|Wallet Connect| BC
    BE -->|Tx verify| BC

2. Request lifecycle (typical authenticated REST call)

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

Concurrent realtime path:

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 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 (SHKeeper) POST to /api/payment/shkeeper/webhook. The backend verifies HMAC signature, updates the Payment document, advances any linked PurchaseRequest/SellerOffer state, and emits Socket.IO events to both buyer and seller rooms.

sequenceDiagram
    participant SHK as SHKeeper
    participant BE as Backend
    participant DB as MongoDB
    participant Buyer
    participant Seller
    SHK->>BE: POST /api/payment/shkeeper/webhook<br/>X-Signature: HMAC-SHA256
    BE->>BE: verifySignature(body, header, SHKEEPER_WEBHOOK_SECRET)
    BE->>DB: Payment.updateOne({providerPaymentId}, {status:"completed"})
    BE->>DB: PurchaseRequest.updateOne(..., {status:"funded"})
    BE-->>Buyer: socket emit "payment:status-updated"
    BE-->>Seller: socket emit "request:funded"
    BE-->>SHK: 200 OK

See Payment Flow - SHKeeper for the full 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
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.