Skip to main content

SDLC 02: System Design and Architectural Decisions

Revision history: Updated June 2026 — 19-microservice architecture; Go/Python services; Meilisearch; BullMQ; expanded RBAC; fraud detection; loyalty/coupons; ZRA invoicing; WhatsApp; delivery tracking; dual-key gateway security.


1. High-Level Architecture

[End Users / Browsers / Mobile Apps]
|

Cloudflare (DNS · WAF · CDN · SSL)
|
┌────┴────────────────────────────────────┐
│ │
▼ ▼
Next.js Frontend (Vercel) Nginx Reverse Proxy (EC2)
│ │
│ API calls (/api/v1/*) │
└──────────────────────────────────┘

┌──────────┴──────────┐
│ pakashop-gateway │ Port 8000
│ (API Gateway) │
└──────────┬──────────┘

┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Core Services │ │ Supporting │ │ Data Layer │
│ (Node.js) │ │ Services │ │ │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ pakashop- │ │ pakashop-search │ │ PostgreSQL │
│ backend 3080 │ │ (Go) 3005 │ │ (Prisma ORM) │
│ pakashop- │ │ pakashop- │ │ Meilisearch │
│ config 3085 │ │ analytics │ │ (Go) 3007 │ │ Redis │
│ pakashop- │ │ pakashop- │ │ (sessions, │
│ notifications │ │ moderation │ │ cache, │
│ 3090 │ │ (Python) 3110 │ │ pub/sub, │
│ pakashop- │ │ pakashop- │ │ queues) │
│ tracking 3120 │ │ recommendations│ │ Cloudinary │
│ pakashop- │ │ (Python) 3100 │ │ (files) │
│ scheduler 3004│ │ │ │ │
│ pakashop-fraud │ │ │ │ │
│ 3006 │ │ │ │ │
│ pakashop-coupon │ │ │ │ │
│ 3008 │ │ │ │ │
│ pakashop-loyalty│ │ │ │ │
│ 3010 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ whatsapp 3009 │ │ │ │ │
│ pakashop-reports│ │ │ │ │
│ 3011 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ reconciliation│ │ │ │ │
│ 3012 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ invoicing 3013│ │ │ │ │
│ pakashop- │ │ │ │ │
│ pricing 3014 │ │ │ │ │
│ pakashop- │ │ │ │ │
│ settlement 3016│ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└──────────────────────────┼──────────────────────────┘

┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
PawaPay Flutterwave Middleware.io
(MoMo primary) (Card + MoMo failover) (APM)

2. Microservices Architecture

The Pakashop platform comprises 19 services, all hosted on AWS EC2 and managed by systemd. Services communicate via the API Gateway using a dual-key security model.

2.1 Service Registry

ServicePortLanguageResponsibility
pakashop-gateway8000Node.jsReverse proxy, routing, dual-key auth, rate limiting, WebSocket upgrades
pakashop-backend3080Node.jsCore business logic: auth, orders, products, shops, payments, users
pakashop-config3085Node.jsCentralised feature flags and runtime configuration (Redis-cached)
pakashop-notifications3090Node.jsEmail (Resend/SMTP), in-app notifications, SSE real-time stream
pakashop-tracking3120Node.jsReal-time delivery tracking: WebSocket server, Redis Pub/Sub, GPS ingestion
pakashop-moderation3110PythonSightengine AI content moderation pipeline for uploaded images
pakashop-recommendations3100PythonCollaborative filtering engine (Jaccard + k-NN) on Redis
pakashop-scheduler3004Node.jsBackground job infrastructure (BullMQ): periodic cleanup, retries
pakashop-search3005GoFull-text product search via Meilisearch: typo-tolerant, faceted
pakashop-fraud3006Node.jsRules-based fraud detection: velocity checks, anomalies, self-dealing
pakashop-analytics3007GoBusiness intelligence: vendor/admin dashboards, aggregation
pakashop-coupon3008Node.jsPromotions & discount engine: creation, validation, redemption
pakashop-loyalty3010Node.jsPoints-based loyalty & rewards: earn, redeem, expire
pakashop-whatsapp3009Node.jsWhatsApp Business Cloud API: order alerts, delivery PINs, cart recovery
pakashop-reports3011Node.jsPDF/CSV/Excel report generation
pakashop-reconciliation3012Node.jsAutomated financial reconciliation: internal vs provider settlements
pakashop-invoicing3013Node.jsZRA Smart Invoice VSDC integration: configurable on/off, mock mode
pakashop-pricing3014Node.jsDynamic pricing engine: commissions, discounts, markups
pakashop-settlement3016Node.jsAutomated batch vendor & agent payouts

2.2 Inter-Service Communication

All external traffic enters through the API Gateway (pakashop-gateway on port 8000). Internal service-to-service communication uses the gateway with an internal API key.

Dual-Key Security Model:

KeyHeaderPurposeWho Uses It
External client keyx-pakashop-keyIdentifies external clients (frontend, mobile apps)Frontend, third-party integrations
Internal service keyx-internal-keyAuthenticates inter-service requestsAll microservices calling each other
External Client → Gateway (x-pakashop-key) → Backend Service
Internal Service → Gateway (x-internal-key) → Another Service

The gateway validates keys, applies rate limiting, and routes to the appropriate upstream service based on path prefixes.


3. Key Architectural Decisions

3.1 Hosting: AWS EC2 with systemd

  • Decision: All 19 services deployed on AWS EC2 instances, managed by systemd unit files.
  • Rationale: EC2 provides persistent WebSocket connections, long-running background jobs (BullMQ, polling, settlement), and direct Redis access — requirements not met by serverless edge runtimes.
  • Frontend remains on Vercel for CDN and preview deployment benefits.

3.2 Polyglot Services

  • Decision: Use the best language for each service domain.
  • Node.js (16 services): Primary language for I/O-bound services (API, notifications, payments, scheduling).
  • Go (2 services): CPU-bound services requiring high throughput (search, analytics).
  • Python (2 services): ML/AI pipelines (moderation via Sightengine, recommendations via collaborative filtering).

3.3 Database: Prisma ORM + PostgreSQL

  • Decision: Prisma as the sole ORM for PostgreSQL across all Node.js services.
  • Rationale: Type-safe migrations, strong typing, and seamless Node.js integration.
  • Connection pooling: PgBouncer or Prisma Accelerate recommended for high-concurrency endpoints.
  • Go services use pgx (native PostgreSQL driver) directly.
  • Python services use asyncpg or psycopg2.

3.4 Search: Meilisearch

  • Decision: Dedicated Meilisearch instance (Go service, port 3005) for product search.
  • Rationale: Typo-tolerant, faceted search with sub-100ms response times; superior to PostgreSQL full-text search for marketplace scale.
  • Sync: Product create/update/delete events trigger Meilisearch index updates via the search service.

3.5 Job Queue: BullMQ on Redis

  • Decision: BullMQ (Redis-backed) for background jobs across all Node.js services.
  • Use cases: Email sending, report generation, settlement batching, reconciliation, ZRA invoice transmission, inventory alerts.
  • Scheduler service (port 3004) manages cron-style recurring jobs.

3.6 Caching: Redis

  • Decision: Redis deployed on EC2 (same VPC as services).
  • Use cases: User sessions, cart data, rate-limiting buckets, temporary payment status, product listing cache, pub/sub for tracking, BullMQ job queues.
  • See ../caching-strategy.md for key naming and TTL conventions.

3.7 Payment Architecture: PawaPay + Flutterwave

  • Decision: PawaPay as the primary mobile money provider; Flutterwave for card payments and mobile money failover.
  • Rationale: PawaPay is purpose-built for Zambian MNOs (MTN, Airtel, Zamtel); Flutterwave provides SAQ-A-compliant card processing.
  • PCI-DSS: Card data never touches Pakashop servers; all card entry handled by Flutterwave's hosted payment page.
  • Settlement: Delayed settlement model — funds held by licensed providers, released after PIN-verified delivery.
  • See ../payment-architecture.md for the full implementation.

3.8 Content Moderation: Sightengine

  • Decision: All image uploads run through the moderation microservice (Python, port 3110) which calls Sightengine.
  • Rationale: Automated NSFW/violence detection before images appear on the marketplace.
  • See ../content-moderation.md.

3.9 Fraud Detection: Rules Engine

  • Decision: Dedicated fraud microservice (Node.js, port 3006) for real-time transaction monitoring.
  • Rationale: Velocity checks, amount anomalies, self-dealing detection, and risk scoring require isolated processing to avoid impacting checkout latency.
  • See ../fraud-detection.md.

3.10 Observability: Middleware.io

  • Decision: OpenTelemetry-based APM via Middleware.io across all 19 services.
  • Rationale: Unified traces, logs, and metrics from a single dashboard without managing a self-hosted Grafana stack.
  • Logging: pino structured logging with correlation IDs; shipped to Middleware.io and journald.

3.11 Email Infrastructure Split

  • Resend: Transactional emails (OTPs, order confirmations, password reset). High deliverability, analytics.
  • Zoho Mail: Human-to-human customer support correspondence.

3.12 File Uploads: Cloudinary

  • All product images, shop logos, KYC documents, and delivery signature photos upload to Cloudinary with signed authentication.
  • After upload, the URL is stored in PostgreSQL; Sightengine moderation runs asynchronously.

3.13 DNS and Security: Cloudflare

  • Full DNS management, WAF rules (OWASP top 10), and CDN via Cloudflare.
  • GoDaddy acts only as domain registrar with NS records pointing to Cloudflare.

3.14 WhatsApp Integration

  • Decision: Dedicated whatsapp microservice (Node.js, port 3009) for WhatsApp Business Cloud API.
  • Use cases: Order alerts, delivery PIN notifications, cart recovery messages.
  • Rationale: Isolates WhatsApp API complexity and rate limits from core backend.

4. Data Flow Examples

4.1 Mobile Money Checkout

1. Customer selects MTN Money on /checkout
2. Frontend → POST /api/v1/orders → Order created (PENDING)
3. Frontend → POST /api/v1/payments/initiate
4. PaymentOrchestrator → PawaPayAdapter.initiateDeposit()
5. PawaPay sends USSD push to customer's phone
6. Backend returns { type: 'ussd', transactionId, message }
7. Frontend shows MobileMoneyOverlay, polls GET /api/v1/payments/status/:orderId every 5 s
8. PawaPay webhook → POST /api/v1/payments/webhook/pawapay
9. WebhookHandler verifies HMAC → marks payment CAPTURED → Order = PAID/CONFIRMED
10. Poll detects PAID → redirect to /orders/:id

4.2 Card Checkout

1. Customer selects Visa/Mastercard on /checkout
2. Frontend → POST /api/v1/orders → Order created (PENDING)
3. Frontend → POST /api/v1/payments/initiate
4. FlutterwaveAdapter generates hosted payment link
5. Frontend redirects to Flutterwave's hosted page
6. Customer enters card details on Flutterwave (raw card data never reaches Pakashop)
7. Flutterwave redirects to /payment/callback?orderId=...
8. Flutterwave also POSTs to /api/v1/payments/webhook/flutterwave
9. Callback page polls status → shows success

4.3 Product Image Upload with Moderation

1. Merchant uploads image → Express API requests Cloudinary signed signature
2. Frontend uploads directly to Cloudinary using signature
3. Cloudinary URL stored in PostgreSQL via Prisma
4. Background job → moderation service (Python) → Sightengine API
5. If violation: image flagged, product hidden until manual review
6. Admin reviews in dashboard → approve/reject/escalate

4.4 Delivery Tracking

1. Agent picks up order → status changes to OUT_FOR_DELIVERY
2. Agent app posts GPS location every 5s → POST /api/v1/tracking/location
3. Tracking service applies Kalman filter → smooths GPS jitter
4. Redis PUBLISH order:{orderId}:location
5. WebSocket subscribers (buyer, vendor, admin) receive live location
6. Geofence check: within 400m of destination → ARRIVING event → buyer push notification
7. Agent arrives → customer provides 6-digit PIN → agent confirms delivery
8. Digital signature captured → SHA-256 hash stored for tamper evidence
9. Order status → DELIVERED → settlementStatus → RELEASABLE

4.5 Fraud Detection Flow

1. Payment initiation request received
2. Fraud service (port 3006) evaluates in parallel:
- Velocity: >5 payments/hour from same IP?
- Amount: >3x average order value?
- Self-dealing: buyer and seller same account?
- Risk score: composite score 0-100
3. If score > threshold: payment blocked, admin review queue entry created
4. Admin reviews in fraud dashboard → approve/release or reject
5. If approved: payment proceeds; if rejected: order cancelled

4.6 ZRA Smart Invoice Flow

1. Order confirmed (PAID) → invoicing service (port 3013) triggered
2. If ZRA integration enabled (feature flag) and vendor has valid TPIN:
a. Generate Smart Invoice payload per ZRA VSDC spec
b. Transmit to ZRA VSDC API
c. Log transmission (success/failure) with retry logic
3. If mock mode: simulate transmission, log mock receipt
4. Admin can view transmission status in dashboard
5. Failed transmissions auto-retry (3 attempts, exponential backoff)

5. Environment Configuration

VariablePurposeScope
DATABASE_URLPostgreSQL connection stringAll
REDIS_URLRedis connection URLAll
MEILISEARCH_URLMeilisearch hostsearch, backend
RESEND_API_KEYTransactional emailnotifications
CLOUDINARY_CLOUD_NAMECloudinary configbackend
CLOUDINARY_API_SECRETSigned upload authbackend
PAWAPAY_API_KEYPawaPay deposits & payoutsbackend, settlement
PAWAPAY_WEBHOOK_SECRETHMAC signature verificationbackend
FLUTTERWAVE_SECRET_KEYFlutterwave API authbackend
FLUTTERWAVE_SECRET_HASHWebhook verif-hashbackend
MIDDLEWARE_API_KEYMiddleware.io APMAll
SIGHTENGINE_API_USERContent moderationmoderation
SIGHTENGINE_API_SECRETContent moderationmoderation
JWT_SECRETSession token signingbackend, gateway
PAYMENT_PROVIDERMOCK | AUTO | PAWAPAYbackend
INTERNAL_API_KEYInter-service authenticationAll
PAKASHOP_CLIENT_KEYExternal client authenticationgateway
TOTP_ENCRYPTION_KEYTOTP secret encryptionbackend
TWILIO_ACCOUNT_SIDSMS/WhatsApp MFAbackend, whatsapp
TWILIO_AUTH_TOKENSMS/WhatsApp MFAbackend, whatsapp
WHATSAPP_BUSINESS_IDWhatsApp Business APIwhatsapp
ZRA_VSDC_API_KEYZRA Smart Invoiceinvoicing
ZRA_MOCK_MODEZRA mock transmissioninvoicing
FEATURE_FLAGS_REDIS_KEYFeature flags cache keyconfig

For internal use only. Do not distribute outside Pakashop engineering.