Architecture العربية

arc42 §3 — Context, Scope & Container View

Status: Draft v0.1 · Date: 2026-07-20 Traces to: 00-requirements-baseline.md, 01-quality-attribute-scenarios.md


3.1 Business Context (C4 Level 1 — System Context)#

graph TB
    subgraph Actors["Human Actors"]
        IND["👤 Individual<br/><i>ACT-IND</i><br/>Browses, books, learns"]
        CO["🏢 Company Admin<br/><i>ACT-CO</i><br/>Manages employees, buys services"]
        EMP["👷 Employee<br/><i>ACT-EMP</i><br/>Learns, takes assessments"]
        CON["🎓 Consultant<br/><i>ACT-CON</i><br/>Sets availability, consults"]
        SP["🚚 Service Provider<br/><i>ACT-SP</i><br/>Maintains listing, subscribes"]
        ADM["🛡️ Platform Admin<br/><i>ACT-ADM</i><br/>Approves, moderates, reports"]
    end

    QW["<b>Qwizin Platform</b><br/><br/>Operational enablement platform for<br/>KSA restaurant &amp; hospitality sector.<br/>Consultancy · Templates · Training ·<br/>Assessment · Certification · Marketplace"]

    subgraph External["External Systems"]
        PAY["💳 Payment Gateway<br/><i>UNDECIDED — OQ-01</i><br/>mada, cards, holds, refunds, payouts"]
        VID["🎥 Video Provider<br/><i>Behind port — SEAM-02</i><br/>Real-time consultation media"]
        GOOG["🔑 Google Identity<br/>Sign-In (OIDC)"]
        SMS["📱 SMS Provider<br/>OTP + reminders (KSA)"]
        MAIL["✉️ Email Provider<br/>Transactional mail"]
        PUSH["🔔 Push Service<br/>APNs / FCM"]
        ZATCA["🧾 ZATCA / FATOORA<br/><i>Applicability — OQ-11</i><br/>E-invoicing clearance"]
    end

    IND & CO & EMP & CON & SP --> QW
    ADM --> QW

    QW -->|"Charge, refund, pay out"| PAY
    QW -->|"Create room, issue token,<br/>receive events"| VID
    QW -->|"Verify ID token"| GOOG
    QW -->|"Send OTP / reminder"| SMS
    QW -->|"Send transactional mail"| MAIL
    QW -->|"Deliver push"| PUSH
    QW -.->|"Submit / clear invoice"| ZATCA

    style QW fill:#1168bd,stroke:#0b4884,color:#fff
    style PAY fill:#c94a4a,stroke:#8b2f2f,color:#fff
    style ZATCA fill:#c94a4a,stroke:#8b2f2f,color:#fff
    style VID fill:#d98c1f,stroke:#96610f,color:#fff

Legend: red = undecided or unconfirmed dependency that blocks design; orange = deliberately abstracted behind a port; dashed = applicability unconfirmed.

External dependency register#

Dependency Status Blocking Notes
Payment Gateway Undecided OQ-01, OQ-10 Must support mada + auth-hold/capture + refund + marketplace payout. Payout capability may trigger SAMA licensing — a legal question, not a technical one.
Video Provider ⚠️ Deliberately deferred SEAM-02 Abstracted behind a port by design. Residency constraint (CON-13) may disqualify most managed CPaaS.
Google Identity ✅ Confirmed SRC-MOM FR-5. Must verify ID token server-side; never trust client assertions.
SMS Provider ⚠️ "If implemented" SRC-BRD §13 dep 13 hedges. OTP is a hard requirement if phone registration ships.
Email Provider ✅ Confirmed SRC-BRD §13 dep 12
Push Service ⚠️ Implied Not named in sources, but mobile-first + appointment reminders implies it
ZATCA / FATOORA Unconfirmed OQ-11 If applicable, a substantial unplanned workstream. Under active research.

Architectural observation. Three of the seven external dependencies are unresolved, and two of those (payments, e-invoicing) are regulatory rather than technical. This is the largest single source of schedule risk in the programme, and it is not visible in any source document. The mitigation is structural: every unresolved dependency sits behind a port so that the decision can be made late without redesign.


3.2 Technical Context#

Interface Protocol Direction Notes
Mobile app → API HTTPS / REST-JSON, TLS 1.3 Inbound Versioned (/api/v1). Bearer token. Primary client (CON-16).
Web app → API HTTPS / REST-JSON Inbound Deferred (mobile-first, web later) — but API contract designed to serve it
Admin portal → API HTTPS Inbound Separate surface, separate authz, MFA-required (QAS-SEC-04)
API → Payment gateway HTTPS + webhooks Bi-directional Webhooks must be signature-verified and idempotent (QAS-AVL-03)
API → Video provider HTTPS + webhooks Bi-directional Room lifecycle out, participant events in
Client → Video media WebRTC (SRTP/DTLS, ICE) Direct Media does not traverse the API tier. Critical topology fact — see §3.3.
API → Object storage S3-compatible HTTPS Outbound Documents, templates, certificates
Client → Object storage HTTPS, signed URL Direct Short-TTL signed URLs (QAS-SEC-02); bypasses API for bulk transfer

3.3 Container View (C4 Level 2)#

Per CON-14, this is a modular monolith with extracted sidecars — not microservices. The extraction criterion applied is deliberately narrow: a component is separated only when it has a genuinely different scaling profile, runtime, or failure domain. Everything else stays in the monolith.

graph TB
    subgraph Clients["Client Tier"]
        MOB["📱 Mobile App<br/><i>iOS / Android</i><br/>Primary client"]
        WEB["🌐 Web App<br/><i>Phase 1.5</i>"]
        ADMUI["🖥️ Admin Portal<br/><i>Server-rendered</i>"]
    end

    subgraph Edge["Edge — KSA Region"]
        CDN["CDN<br/>Static assets, template files"]
        WAF["WAF + DDoS"]
        ING["Ingress Controller<br/>TLS termination, routing,<br/>rate limiting"]
    end

    subgraph K8S["Kubernetes Cluster — In-Kingdom"]
        subgraph AppTier["Application Tier — stateless, HPA on RPS/latency"]
            API["<b>Qwizin API</b><br/><i>Laravel 13 · PHP 8.4</i><br/>Modular monolith<br/>MOD-IAM · ONB · CONS · TPL ·<br/>LRN · ASMT · CERT · MKT · PAY · ADM"]
        end

        subgraph WorkerTier["Worker Tier — HPA on queue depth"]
            WRK["<b>Queue Workers</b><br/><i>Same image, different entrypoint</i><br/>Notifications, webhooks,<br/>search indexing, reconciliation"]
            SCHED["<b>Scheduler</b><br/><i>Singleton</i><br/>Reminders, expiry, subscription<br/>renewal, predictive video scaling"]
        end

        subgraph Sidecars["Extracted Sidecars — distinct runtime or scaling profile"]
            PDF["<b>Document Renderer</b><br/><i>Headless browser runtime</i><br/>Certificates, custom docs<br/><b>Why separate:</b> heavy memory,<br/>different runtime, burst scaling"]
            SCAN["<b>Malware Scanner</b><br/><i>ClamAV</i><br/><b>Why separate:</b> large signature<br/>DB, isolation of untrusted input"]
            SRCH["<b>Search Engine</b><br/><i>Arabic-capable — TBD</i><br/><b>Why separate:</b> stateful,<br/>own memory/disk profile"]
        end

        subgraph VideoTier["Video Tier — conditional, see ADR-0005"]
            SFU["<b>Media Servers</b><br/><i>Only if self-hosted</i><br/>hostNetwork · session-based scaling"]
            TURN["<b>TURN/STUN</b><br/><i>Only if self-hosted</i>"]
        end
    end

    subgraph Data["Data Tier — In-Kingdom"]
        DB[("<b>PostgreSQL</b><br/>Primary + read replica<br/>Tenant-scoped")]
        REDIS[("<b>Redis</b><br/>Cache · queues · locks<br/>· rate limiting")]
        OBJ[("<b>Object Storage</b><br/>S3-compatible<br/>Legal docs · templates ·<br/>certificates<br/><i>Encrypted at rest</i>")]
    end

    subgraph Obs["Observability"]
        OTEL["OpenTelemetry Collector"]
        LOGS["Logs · Metrics · Traces<br/>Correlation-ID threaded"]
    end

    MOB & WEB --> CDN
    MOB & WEB & ADMUI --> WAF --> ING
    ING --> API
    ING -.->|"Deferred: BFF only if<br/>client divergence demands it"| API

    API --> DB
    API --> REDIS
    API --> OBJ
    API --> SRCH
    WRK --> DB & REDIS & OBJ & SRCH
    SCHED --> REDIS
    WRK --> PDF
    WRK --> SCAN
    API -.->|"signed URL"| OBJ
    MOB -.->|"direct download<br/>via signed URL"| OBJ

    API -->|"port"| VIDEXT["Video Provider<br/><i>managed or self-hosted</i>"]
    MOB -.->|"WebRTC media<br/><b>bypasses API</b>"| SFU
    SFU --- TURN

    API & WRK & PDF --> OTEL --> LOGS

    style API fill:#1168bd,stroke:#0b4884,color:#fff
    style PDF fill:#2d8659,stroke:#1c5638,color:#fff
    style SCAN fill:#2d8659,stroke:#1c5638,color:#fff
    style SRCH fill:#2d8659,stroke:#1c5638,color:#fff
    style SFU fill:#d98c1f,stroke:#96610f,color:#fff
    style TURN fill:#d98c1f,stroke:#96610f,color:#fff

Container register#

Container Runtime Scaling signal Justification for its boundary
Qwizin API Laravel 13 / PHP 8.4 RPS + p95 latency The monolith. All business modules. Stateless → horizontally scalable (QAS-SCL-01).
Queue Workers Same image Queue depth, not CPU Same code, different entrypoint — deliberately not a separate service. Async work must not share the request tier's scaling signal (QAS-SCL-03).
Scheduler Same image Singleton Must not run in multiple replicas. Leader election or a single-replica deployment.
Document Renderer Headless browser Job queue depth Extracted — a browser runtime has a fundamentally different memory profile (hundreds of MB per render) and would distort API pod sizing. Also the only component needing Arabic font/shaping stack (QAS-LOC-01).
Malware Scanner ClamAV Upload rate Extracted — large signature database, periodic updates, and deliberate isolation: it is the component that touches untrusted input first (QAS-SEC-03).
Search Engine TBD (Arabic-capable) Index size / QPS Extracted — stateful, own persistence and memory profile. Must be self-hostable in-Kingdom (CON-13 likely disqualifies managed search SaaS).
Media / TURN Conditional Session count, not CPU Present only under the self-hosted option. hostNetwork requirement makes these architecturally unlike everything else in the cluster — see ADR-0005.

Why not microservices — summary#

Recorded here; full analysis in ADR-0002.

  • No independent scaling need across business modules. Booking, templates, and learning have near-identical, modest load profiles. The components that genuinely differ (PDF rendering, malware scanning, search, media) have been extracted — which is the actual engineering benefit microservices offer, obtained without the distributed-systems cost.
  • No team-topology justification. Microservice boundaries pay off when they map to independent teams. There is one delivery vendor.
  • Distributed transactions would be a net loss. The booking↔payment↔video flow (CONF-02) already needs careful state management. Splitting it across services converts a database transaction into a saga for no benefit.
  • CON-01 and QAS-OPS-01. Microservices multiply the operational surface a small team must run — directly opposing the cost constraint and the operability scenario.
  • The seams are preserved anyway. QAS-MOD-03 mandates CI-enforced boundaries, so Phase-2 extraction stays cheap. The option is retained without paying for it now.

3.4 Deliberately Deferred Containers#

Recording what was considered and rejected for now, with the trigger that would change the decision:

Deferred Rationale Reconsider when
BFF layer Only one client at launch. A BFF with a single consumer is an extra hop and an extra deployable for no gain. Web and mobile clients' data needs visibly diverge, or a third client type appears
API Gateway (beyond ingress) Ingress + in-app rate limiting covers current needs. A dedicated gateway is justified by many services, not one. Modules are extracted into real services (Phase 2)
Message broker (Kafka/RabbitMQ) Redis-backed queues are sufficient at this scale. The event bus abstraction exists from day one so the broker can be swapped in without touching publishers or subscribers (QAS-MOD-03). Cross-service events, event replay, or ordered streams are needed
Separate read model / CQRS Reporting needs (SRC-MOM §13 KPIs) are satisfiable from read replicas. Analytical queries begin degrading transactional performance
Service mesh Adds significant operational complexity against QAS-OPS-01. Network policies cover the security need. Many services with complex mTLS and traffic-shaping needs

Each row is an option deliberately kept open rather than an omission. The pattern throughout: build the seam, defer the component.


3.5 Open Items Affecting This View#

  • OQ-01 / OQ-10 — payment gateway and licensing; determines whether payments stay in-process or must be isolated further for compliance scope
  • OQ-08 — consultation recording; adds storage, retention, and a recording-egress path not shown above
  • OQ-11 — ZATCA applicability; would add an invoicing integration container or module
  • ADR-0005 (pending research) — self-hosted vs managed video determines whether the Video Tier exists in-cluster at all
Tech Lead · Amir Haroun Draft v0.1 · research & design only