Tech Stack & Architecture Decision Records¶
The Stack¶
| Layer | Technology | Version | Role |
|---|---|---|---|
| API | Laravel (PHP) | 11.x (PHP 8.3) | Modular-monolith REST API, auth, business logic |
| Database | MySQL | 8.0 (InnoDB, utf8mb4) | System of record |
| Cache / queues | Redis | 7.x | Cache, sessions, rate limits, Horizon queues |
| Queue dashboard | Laravel Horizon | 5.x | Worker supervision, metrics, retries |
| Search | Meilisearch | 1.x | Vendor/service/package search with typo tolerance and Arabic support, via Laravel Scout |
| Web | Next.js (React) | 15.x (App Router) | Customer web app + SEO pages; next-intl for ar/en with RTL |
| Mobile | Flutter (Dart) | 3.x | iOS + Android customer & vendor apps |
| Object storage | S3-compatible | — | Media, licenses, attachments; CDN in front |
| Push | Firebase Cloud Messaging | HTTP v1 API | Mobile + web push |
| Mailgun or Amazon SES | — | Transactional email (OTP, receipts, digests) | |
| Payments | MyFatoorah / Tap Payments | REST v2 | KNET, Visa/MC, Apple Pay, Google Pay — behind PaymentProviderInterface |
| Auth tokens | Laravel Sanctum | 4.x | Bearer tokens for SPA + mobile |
| Containers / CI | Docker + GitHub Actions | — | Build, test, deploy (blue/green) |
Supporting packages (indicative): laravel/scout (Meilisearch driver), spatie/laravel-permission (RBAC), spatie/laravel-medialibrary or a thin in-house Media module, nwidart/laravel-modules (module layout), deptrac (boundary enforcement in CI), pestphp/pest (tests).
ADR-001: Modular Monolith over Microservices¶
Status: Accepted — 2026
Context. Yam3at launches in Kuwait with a small engineering team (2–4 backend engineers at MVP). The product spans 12 domains, but launch traffic is modest (thousands of users, not millions). We need fast iteration across domains that are still changing shape — the RFQ/quotation/booking flow will be redesigned repeatedly based on real vendor behavior.
Decision. Build a single deployable Laravel application organized into 12 modules matching the product domains, with enforced boundaries (no cross-module model imports; communication via service interfaces and domain events; each module owns its tables).
Alternatives considered.
- Microservices from day one. Rejected: multiplies operational surface (deploys, observability, inter-service auth, distributed transactions) for a team that cannot staff it. Cross-domain features (accept quotation → create booking → take payment → notify) would become distributed sagas on day one.
- Unstructured monolith ("just a Laravel app"). Rejected: cheapest today, but every Laravel project that skips boundaries ends up with
Bookingreadingpaymentstables directly, and extraction later becomes a rewrite.
Consequences.
- (+) One deploy, one database, real ACID transactions across domains where needed (quote acceptance + booking creation).
- (+) Extraction path preserved: search, notifications, messaging, payments are the pre-planned first extractions (see overview).
- (−) Boundary discipline requires CI enforcement (deptrac) and code review vigilance; the framework will not stop violations by itself.
- (−) One bad module (e.g., a slow query in search fallback) can degrade the whole app; mitigated with per-route rate limits and queue isolation.
ADR-002: Laravel (PHP 8.3) over Node.js¶
Status: Accepted — 2026
Context. The API needs: relational data with heavy invariants (subscriptions, quotas, bookings, payments), queues, scheduled jobs, RBAC, file handling, localization, and admin tooling — built quickly and hired-for locally. Node.js (NestJS) and Laravel were the finalists.
Decision. Laravel 11 on PHP 8.3.
Alternatives considered.
- Node.js + NestJS + Prisma. Strong typing and shared language with the web frontend. Rejected as primary: we'd assemble queues (BullMQ), auth, RBAC, admin, billing scaffolding piecemeal; Laravel ships Sanctum, Horizon, Scout, notifications, scheduling, and policies as first-party, battle-tested pieces. NestJS's module system enforces boundaries better out of the box — we compensate with deptrac.
- Django. Comparable batteries; weaker regional hiring pool for our market and weaker ecosystem fit with MyFatoorah/Tap SDKs and local agency talent.
Consequences.
- (+) Fastest path to MVP: OTP flows, subscription billing jobs, media handling, and Meilisearch sync are mostly configuration, not construction. Strong PHP/Laravel talent pool in the GCC and nearby markets.
- (+) Horizon gives queue observability for free — critical because our architecture is queue-heavy.
- (−) Real-time messaging is not PHP's sweet spot. MVP messaging is HTTP polling / FCM-nudged fetch; if we need true websockets at scale, Laravel Reverb first, then the Messaging module is extraction candidate #3 (possibly to a Node/Go runtime).
- (−) No end-to-end type sharing with TypeScript clients; mitigated by generating an OpenAPI spec from the API and generating client types from it.
ADR-003: MySQL 8 over PostgreSQL¶
Status: Accepted — 2026
Context. Both engines handle our OLTP shape (moderate write volume, heavy relational integrity, JSON columns for flexible metadata). The choice is about operations, hosting, and team familiarity more than features.
Decision. MySQL 8.0, InnoDB, utf8mb4_0900_ai_ci (with explicit collation care on Arabic text search columns — full-text search is delegated to Meilisearch anyway).
Alternatives considered.
- PostgreSQL 16. Technically excellent — richer types, transactional DDL, partial indexes. Rejected on fit, honestly not on merit: regional managed-hosting options, existing team expertise, and local agency/DevOps familiarity skew heavily MySQL. None of our workloads (no PostGIS-grade geo, no heavy JSONB querying — search is Meilisearch's job) require Postgres-only features. Geo distance filtering uses Meilisearch
_geoplus simple lat/lng columns.
Consequences.
- (+) Boring, well-understood operations; every candidate we interview knows it; first-class Laravel support.
- (−) No transactional DDL — migrations that fail mid-way need manual cleanup; we keep migrations small and reversible.
- (−) If Vision-phase analytics outgrow MySQL, we add a read replica + warehouse (e.g., ClickHouse/BigQuery export) rather than migrating the OLTP store.
ADR-004: Meilisearch over Elasticsearch / Algolia¶
Status: Accepted — 2026
Context. Marketplace search is a core MVP feature: instant, typo-tolerant, faceted (category, city, price range, rating), bilingual Arabic/English, geo-aware. Index size is small (thousands of vendors/listings, not millions of documents).
Decision. Self-hosted Meilisearch 1.x via Laravel Scout, with a queued indexing pipeline (see Integrations).
Alternatives considered.
- Elasticsearch/OpenSearch. Vastly more powerful (aggregations, custom analyzers). Rejected: heavy to operate (JVM, cluster sizing), and our facet/filter needs are squarely within Meilisearch's feature set. ES would be justified only for Vision-phase analytics — which belongs in a warehouse, not the search engine.
- Algolia. Best-in-class DX and relevance; native Laravel Scout driver. Rejected on cost trajectory (per-record + per-operation pricing punishes frequent re-indexing of availability/ratings) and data-residency optics for GCC expansion. Migration path exists: Scout makes Algolia a driver swap if Meilisearch relevance disappoints.
Consequences.
- (+) Millisecond search, built-in typo tolerance that behaves acceptably on Arabic,
_geofiltering, one small binary to operate. - (−) Single-node at our scale; index is rebuildable from MySQL at any time (
scout:import), so search is allowed to be briefly stale/degraded — it is never the system of record. - (−) Weaker Arabic stemming than a tuned ES analyzer; mitigated with synonym lists (e.g., قاعه/قاعة variants) and indexing both
*_arand*_enfields.
ADR-005: Flutter over React Native¶
Status: Accepted — 2026
Context. Two mobile apps' worth of surface (customer and vendor experiences) on iOS + Android, Arabic-first with full RTL, image-heavy listing UIs, from a small mobile team.
Decision. Flutter 3.x, single codebase, flavor/role-based experiences for customer and vendor.
Alternatives considered.
- React Native (Expo). Would share language (TS) with the Next.js web team. Rejected: RTL and Arabic typography are noticeably more polished out of the box in Flutter; rendering consistency across low-end Android devices (significant in the GCC market) is better with Flutter's own rasterizer; fewer native-module surprises for camera/file upload/payment webview flows.
- Native Swift + Kotlin. Best platform fidelity, double the cost. Rejected for MVP economics.
Consequences.
- (+) One codebase, strong RTL, consistent pixel-perfect rendering of listing cards/galleries on both platforms.
- (−) Dart is a third language in the stack (PHP, TS, Dart); hiring pool smaller than RN in some markets.
- (−) PSP payment pages render in an in-app webview (MyFatoorah/Tap hosted page) — this is true for RN too, but must be tested carefully with KNET redirects and Apple Pay entitlements.
ADR-006: Next.js 15 for Web¶
Status: Accepted — 2026
Context. The web app carries double duty: (a) the full customer product, and (b) SEO-critical public pages — vendor profiles, category landing pages, CMS pages — that must rank for Arabic queries like "قاعات افراح الكويت".
Decision. Next.js 15 App Router; server components + SSR/ISR for public pages, client components for the authenticated app; next-intl for ar/en routing (/ar/..., /en/...) with full RTL stylesheets (logical CSS properties).
Alternatives considered.
- SPA (Vite + React) + separate marketing site. Rejected: splits the codebase, and vendor profile pages — our biggest organic-acquisition asset — need SSR with structured data (LocalBusiness/Product JSON-LD).
- Laravel Blade + Inertia. Keeps one repo and team; rejected because the web client must stay a pure API consumer (API-first principle) and share nothing with server rendering of the monolith, preserving the client/API contract used by Flutter.
Consequences.
- (+) ISR gives fast, cacheable, indexable vendor/category pages; App Router layouts map cleanly to the ar/en locale tree.
- (−) Two rendering environments (server/client) add complexity; all data access goes through the same
/api/v1— no direct DB access from Next.js server components, ever.
ADR-007: MyFatoorah / Tap behind an Abstraction, not Stripe¶
Status: Accepted — 2026
Context. Kuwait payments are KNET-first — the local debit network dominates consumer payments, and a checkout without KNET is dead on arrival. We also need Visa/Mastercard, Apple Pay, Google Pay, refunds, and KWD with 3 decimal places. Stripe has no KNET acquiring and limited Kuwait presence; MyFatoorah and Tap are the regional leaders with native KNET, KWD 3dp handling, and local settlement.
Decision. Integrate one regional PSP at launch (MyFatoorah primary candidate, Tap as the vetted alternative), strictly behind an in-house PaymentProviderInterface (see Integrations). No PSP types, SDK calls, or webhook payload shapes leak outside the Financial module.
Alternatives considered.
- Stripe. Best API and docs in the industry; rejected because KNET is non-negotiable in Kuwait and Stripe is weak there. Revisit only for markets where Stripe has strong local rails.
- Direct KNET integration. Rejected: bank onboarding burden, certification cycles, and it still leaves cards/wallets unsolved.
- Both MyFatoorah and Tap at launch. Rejected: doubles certification and reconciliation work for zero launch value. The abstraction keeps the second provider a config-plus-adapter task (est. 2–3 weeks) for KSA/UAE expansion or failover.
Consequences.
- (+) KNET, cards, Apple Pay/Google Pay, and KWD 3dp correctness from a provider that lives and breathes GCC payments; sandbox environments available for both candidates.
- (+) The abstraction makes provider choice reversible and expansion (KSA: mada; UAE: local acquiring) additive.
- (−) Regional PSP APIs are less polished than Stripe (webhook reliability, idempotency semantics vary) — we compensate with our own idempotency keys, signature verification, and a reconciliation job (see Integrations).
- (−) Hosted payment pages (redirect/iframe) constrain checkout UX vs. Stripe Elements; acceptable trade for KNET compliance scope staying with the PSP.
Related pages: Architecture Overview · Integrations · Security · API Conventions