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 & 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-01andQAS-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-03mandates 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 scopeOQ-08— consultation recording; adds storage, retention, and a recording-egress path not shown aboveOQ-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