Architecture العربية

arc42 §8 — Cross-Cutting Concepts

Status: Draft v0.1 · Date: 2026-07-20 Purpose: Patterns that apply across modules. Decided once here rather than inconsistently in twelve places.


8.1 Bilingual Content (Arabic / English)#

OQ-12 · QAS-LOC-01, QAS-PERF-02 · CON-12

Qwizin is a Saudi platform with an Arabic BRD. Arabic is not a localisation afterthought — it is the primary language of the market.

Storage: split by content type, not by field length#

Content Mechanism Why
Short attributes — listing names, category labels, tags, short descriptions JSON column ({"ar": "...", "en": "..."}) One row, no joins, simple writes. The majority of translatable content.
Long-form — training modules, templates, assessment questions Separate translation table These need independent lifecycles: Arabic may be published while English is in review, each with its own author, review state, and version history

The distinction is not field length — it is whether the translation has an independent lifecycle. A JSON blob cannot express "the Arabic version is approved and the English one isn't", and retrofitting that later means migrating your most valuable content.

Arabic normalisation — mandatory, not optional#

Users type أ / إ / آ / ا interchangeably, ى and ي interchangeably, and almost never type diacritics. Without folding, search recall collapses silently and totally — a user searches in Arabic, gets nothing, and concludes the directory is empty.

Normalisation pipeline (applied identically at index time and to queries):

strip tatweel (ـ) → strip diacritics/harakat → fold أإآٱ → ا
→ fold ة → ه → fold ى → ي → normalise Arabic-Indic digits ٠-٩ → 0-9

Own this in the application layer. It is roughly 150 lines of well-tested string handling, it delivers the higher-impact half of Arabic search quality on any engine, and it makes the engine choice reversible rather than a one-way door (R-11). This is the single most portable investment available.

Additional Arabic concerns#

Concern Handling
Broken plurals (كتاب → كتب, بيت → بيوت) No light stemmer handles these — they are common for product and category nouns. Plan a synonym/lemma dictionary regardless of engine; it is the highest-leverage relevance investment.
Collation Default collation gives codepoint order, not Arabic alphabetical order. Specify an Arabic collation for any user-visible sorted list.
Directional control characters (RLM/LRM) Strip on input — they break exact matching invisibly
Mixed LTR/RTL ("iPhone 15 برو") Grapheme-aware truncation for previews; never split a cluster or strand a direction mark
Slugs Arabic slugs need transliteration or percent-encoding — decide explicitly

API response shape#

Resolve server-side by default; return the resolved string plus metadata.

{ "id": 42, "name": "مطعم الرياض", "locale": "ar", "locale_fallback": false }
Rule Rationale
Use an explicit ?locale= parameter as the cache key, not Accept-Language Accept-Language is high-cardinality in the wild (ar, ar-SA, ar-SA,ar;q=0.9,en;q=0.8…) and shreds CDN hit rates. Two supported values = two cache entries.
Accept-Language normalised to the supported set, used only as a default
Return all locales on detail and admin endpoints (?locales=all) Editors are literally editing translations; detail views benefit from instant switching
Always return locale_fallback Silent fallback makes missing translations invisible to the content team. Content gaps are a product problem, not a rendering one.
Return a direction hint on locale metadata So each client doesn't hardcode a locale→direction map

8.2 Error Handling & API Contract#

Error response shape#

Consistent across every endpoint. Mobile clients must be able to act on errors programmatically.

{
  "error": {
    "code": "account.not_approved",
    "message": "Your account is under review.",
    "message_ar": "حسابك قيد المراجعة.",
    "details": { "state": "under_review", "submitted_at": "..." },
    "correlation_id": "01J..."
  }
}
Element Purpose
Machine-readable code Clients branch on this, never on message text
Localised message Displayable directly
details Structured context — e.g. the account state, so the app renders the right onboarding screen
correlation_id The user can quote it to support, and it links directly to logs (QAS-OPS-01)

Status code discipline#

Code Use
400 Malformed request
401 Missing/invalid authentication
403 Authenticated, not permitted — and the resource's existence is not a secret
404 Not found or cross-tenant probe — a 403 confirms existence and enables enumeration
409 State conflict (e.g. booking a taken slot)
422 Validation failure with field-level detail
429 Rate limited, with Retry-After

Failure philosophy#

Principle Meaning
Fail closed on authorization and tenancy Ambiguity denies
Fail open on non-critical enrichment Search unavailable → degraded DB listing, not an error page (QAS-AVL-04)
Never lose committed work An assessment result survives a PDF failure; a booking survives a notification failure
Errors are events Authorization denials are the attack signal — log them (§8.5)

8.3 Idempotency & Concurrency#

Idempotency#

Surface Mechanism
Client-initiated payment mutations Client-supplied idempotency key, stored with the result
All inbound webhooks Deduplicate on the provider's event ID — providers will retry
Queued jobs Handlers idempotent; SIGKILL after grace expiry will happen
Assessment submission Second submission for a finalised attempt is rejected, not re-graded

Duplicate processing of a payment capture is a double charge. This is not a robustness nicety.

Concurrency#

Contention point Mechanism
Booking slot Pessimistic lock for the reservation window — double-booking is user-visible and unrecoverable
Subscription renewal Distributed lock per subscription
ZATCA invoice sequence (if OQ-11 applies) Single serialisation point — hash chaining is strictly ordered and cannot be produced concurrently by multiple pods (R-02)
Certificate issuance Transactionally bound to a graded attempt; unique constraint prevents duplicates
Counters / aggregates Atomic increments, never read-modify-write

8.4 Time, Money & Identifiers#

Time#

Rule Rationale
Store UTC, always
Present in Asia/Riyadh by default KSA-first market
Business-hour logic uses Sun–Thu The Saudi working week — a Mon–Fri assumption is wrong here
Never trust a client clock Assessment time limits enforced against the server-recorded start
Booking slots carry an explicit timezone Required before GCC expansion (CON-12)
Hijri dates where user-facing Consider for certificates and official documents

Money#

Rule Rationale
Integer minor units (halalas) — never floats Floating-point money is a correctness bug waiting for a rounding case
Currency is explicit on every amount CON-12 — GCC expansion means SAR is not forever the only currency
A Money value object in the shared kernel Prevents mixing currencies by accident
Store the as-charged amount, not just a reference to a plan Plan prices change; historical records must not
VAT handled explicitly Not folded into an opaque total (OQ-11)

Identifiers#

Type Use
ULID/UUID for anything externally referenced Listings, bookings, documents, certificates — removes trivial enumeration
Sequential integers internally where useful
Never sequential for certificates Sequential IDs let anyone enumerate the entire certificate corpus
Typed ID value objects (UserId, CompanyId) Prevents passing a company ID where a user ID belongs — a real source of cross-tenant bugs

8.5 Observability#

QAS-OPS-01 — a single on-call engineer must diagnose a 02:00 incident without tribal knowledge.

flowchart LR
    REQ["Request"] -->|"generate<br/>correlation_id"| API["API"]
    API -->|"propagate"| JOB["Queue job"]
    API -->|"propagate"| SIDE["Sidecar"]
    API -->|"propagate"| EXT["External call"]
    API & JOB & SIDE --> LOGS["Structured logs<br/><i>correlation_id on every line</i>"]
    API & JOB & SIDE --> TRACE["Traces"]
    API & JOB & SIDE --> METRIC["Metrics"]
    LOGS & TRACE & METRIC --> ALERT["Alerts on<br/><b>user-facing SLOs</b>"]
Signal Approach
Correlation ID Generated at ingress, propagated through every async hop, present on every log line and returned in error responses
Traces Sampled; payment and video flows at 100 %
Metrics RED for API; queue depth + oldest-job age for workers; session count for media
Logs Structured JSON to stdout, shipped off-cluster, PII redacted at the logging layer
Alerts On user-facing SLOs — not CPU graphs

Business metrics are first-class, not an afterthought: the four SRC-MOM §13 KPIs require event capture designed in from the start. Reconstructing "templates downloaded" from access logs a year later is expensive and inaccurate.


8.6 Configuration & Feature Management#

Configuration hierarchy#

Layer Contains Changes require
Build-time Framework config, cached Deploy
Environment Endpoints, credentials (from secret store) Restart
Runtime / database Business configuration Nothing — admin UI

What must be database configuration, not code#

CON-07 and CON-08 are explicit constraints, and both are trivially satisfied if designed correctly from the start:

  • Subscription plans and pricing (CON-07)
  • Assessment passing scores (CON-08)
  • Featured listing packages
  • Required document sets per account type (pending OQ-02)
  • Notification templates
  • Consultant response SLA, booking expiry windows (pending OQ-05)

Changing a passing score must never require a release. Anything a business stakeholder will plausibly want to adjust belongs in the admin panel — hardcoding it converts a five-minute business decision into a deployment.

Feature flags#

For CONF-01-dependent training features, and for the gradual rollout of high-risk changes (RLS enforcement, search engine switch). Keep the flag set small and remove flags once decided — a permanent flag is untested branching.


8.7 Testing Strategy#

Ordered by defect-prevention value for this system, not by convention.

Layer Focus Non-negotiable
Adversarial cross-tenant suite Every endpoint, both directions QAS-SEC-01 — the control that survives turnover
Architecture tests Module boundaries, User isolation, tenant traits, no env() outside config R-08
Contract tests No correctness fields in assessment delivery responses QAS-INT-01
Golden-image tests Arabic PDF rendering over a fixture set of names QAS-LOC-01
Integration State machines: booking, approval, subscription High value
Unit Domain logic, grading, money arithmetic Standard
Load Peak concurrency, burst certificate generation Before launch

The four starred layers are unusual and specific to this system's risks. A conventional test pyramid would miss all four, and each guards a failure mode that is silent in production.


8.8 Data Retention & Deletion#

QAS-CMP-01 · ⚠️ Retention periods require legal confirmation (R-03)

Data Retention driver Conflict
Financial records, invoices Statutory (likely 7+ years) Overrides erasure requests
Audit ledger Compliance evidence ⚠️ Direct tension with PDPL minimisation — must be documented and defended, not improvised
Issued certificates Third-party verification value Revoke rather than delete? → OQ-07
Assessment attempts Certificate provenance Anonymise rather than delete
Recorded consultations (if OQ-08) Dispute window only Aggressive deletion is the correct default
Session logs, telemetry Operational Short, automated
Marketing/analytics events Consent-dependent

The enumeration problem#

The hard part of a data-subject request is not deletion — it is finding everything. Once personal data has spread into a search index, a reporting projection, a log pipeline, and backups, "locate all data about this person" is unanswerable without a personal-data inventory maintained as a first-class artifact.

Build the inventory while the system is small. Retrofitting it means auditing every store after the fact.


8.9 Concept Summary#

Concept Decided Where
Bilingual storage Split by lifecycle, not length §8.1
Arabic normalisation Application-owned pipeline §8.1
Error contract Machine-readable code + correlation ID §8.2
Cross-tenant probes 404, never 403 §8.2
Idempotency Client keys + webhook dedup §8.3
Booking concurrency Pessimistic lock §8.3
Money Integer minor units + explicit currency §8.4
Time UTC stored, Riyadh presented, Sun–Thu week §8.4
External IDs ULID — never sequential for certificates §8.4
Observability Correlation ID through every async hop §8.5
Business config Database, not code (CON-07, CON-08) §8.6
Testing Four system-specific layers above the pyramid §8.7
Retention Inventory-first; audit/erasure conflict documented §8.8
Tech Lead · Amir Haroun Draft v0.1 · research & design only