Architecture¶
System Topology¶
graph TB
subgraph Clients
Browser["Browser / Mobile"]
end
subgraph Server["Laravel 10 + Octane v2 (persistent workers)"]
CS["CustomSession MW\nnamesapced cookies\n{name}_user / {name}_admin"]
WEB["Web Surface\nuser middleware group\nInertia + Vue3"]
ADMIN["Admin Surface\nweb+admin group\nBlade + Inertia"]
API["API Surface\napi group\n(thin, Sanctum stub)"]
WEBHOOK["Webhook Surface\nno auth, no CSRF\nSBPS + Atone"]
end
subgraph DomainLayer["Domain Layer (app/Modules/)"]
CTL["Controllers\nthin delegation"]
ACTION["Actions\nCQRS single-ops\nspatie DTO input"]
MGR["Managers\nstateful coordinators"]
QB["QueryBuilders\n9 custom Eloquent builders"]
end
subgraph Data
MYSQL["MySQL"]
REDIS["Redis\ncache + queue"]
end
subgraph Scheduler["Octane Scheduler"]
PJ["PublishJob @1min"]
LJ["LotteryWinnerJob @1min"]
OJ["OriconJob @22:00 JST"]
BJ["BillboardJob @22:00 JST"]
end
Browser --> CS
CS --> WEB
CS --> ADMIN
Browser --> API
Browser --> WEBHOOK
WEB --> CTL
ADMIN --> CTL
API --> CTL
WEBHOOK --> CTL
CTL --> ACTION
CTL --> MGR
ACTION --> QB
MGR --> QB
QB --> MYSQL
ACTION --> REDIS
MGR --> REDIS
PJ --> MYSQL
LJ --> MYSQL
LJ --> REDIS
OJ --> SFTP["Oricon SFTP"]
BJ --> FTP_BIL["Billboard FTP"]
Application Style¶
Modular monolith. Domain logic lives in app/Modules/ (11 modules). Modules self-register via their own Providers/ subdirectory. No nwidart/laravel-modules — modules are plain PHP namespaces, not packages.
No dedicated Services/ layer. Service logic replaced by:
- Actions/ — single-responsibility CQRS units (stateless, accept DTOs)
- Managers/ — stateful coordinators for multi-step operations
- Interfaces/ + Providers/ — contract/implementation binding per module
Modules¶
| Module | Class | Core Purpose |
|---|---|---|
| Product | CORE | Catalog: artists, products, variations, pricing, CKC codes |
| Order | CORE | Purchase flow, order state, fulfilment, reporting exports |
| Payment | CORE | Payment provider facade (SBPS + Atone) |
| Lottery | CORE | Lottery application, winner selection, winner notification |
| LotteryCart | CORE | Lottery application staging area |
| Cart | CORE | Standard shopping cart (guest + authenticated) |
| Membership | CORE | User auth, profile, address, CKC account linking |
| Sales | CORE | Revenue aggregation, label views, Oricon/Billboard exports |
| Chekicha | CORE | CKC serial code import and distribution |
| Maps | SUPPORT | Postcode → address lookup (Google Maps) |
| Inquiry | SUPPORT | Customer inquiry form + auto-reply |
Cross-Module Dependencies¶
graph LR
MEMBERSHIP["Membership\nUser + Auth"] --> CART["Cart"]
MEMBERSHIP --> LOTTERY_CART["LotteryCart"]
CART -->|"checkout"| ORDER["Order"]
LOTTERY_CART -->|"draw result"| LOTTERY["Lottery"]
LOTTERY -->|"winner"| ORDER
ORDER --> PAYMENT["Payment\nSBPS / Atone"]
ORDER --> CHEKICHA["Chekicha\nCKC codes"]
PRODUCT["Product\nArtist + Variation"] --> CART
PRODUCT --> LOTTERY_CART
PRODUCT --> CHEKICHA
ORDER --> SALES["Sales\nread-only aggregate"]
MAPS["Maps\nGoogle Maps"] --> MEMBERSHIP
Purchase flow switch: Product.sale_type enum determines routing:
- normal → Cart flow → Order → SBPS/Atone
- lottery → LotteryCart flow → Lottery draw → Order (winners only)
HTTP Surfaces¶
Three separate access contexts, all going through CustomSession middleware for cookie namespacing:
| Surface | Routes | Guard | UI |
|---|---|---|---|
| Web | routes/web/ (5 files) |
web (User) |
Inertia + Vue3 |
| Admin | routes/admin/ (6 files) |
admin (Admin) |
Blade + Inertia |
| API | routes/api.php |
Sanctum (unused) | JSON |
| Webhooks | routes/webhook.php |
None | JSON |
System Hotspots¶
Complexity¶
- Order fulfilment: DB transaction with
lockForUpdateon variations + ckc_codes + externalCkcConnect::attach()call inside the transaction → external API latency holds DB locks - Lottery winner selection: concurrent applications,
GROUP_CONCATtruncation risk, idempotency gap onis_sentflag - Dual payment provider: SBPS (6 methods, redirect-based) + Atone (inline, BNPL) with separate webhook reconciliation
Active Change Areas (from git log)¶
- Lottery — 3 of last 5 commits;
SendLotteryWinnerEmailJobhas uncommitted changes onstaging - Data migrations — bulk upsert + chunk-size tuning ongoing in
database/migrations/data_migration/ - HasDownloadFile — recently reimplemented
- Admin Users — new routes and views added
Risk Signals¶
SendToOriconswallows allThrowable→ silent FTP failures- SBPS webhook has no inbound HMAC verification
CkcConnect::attach()inside DB transaction → Chekicha API downtime = order rollback- Zero automated test coverage across all 11 modules (placeholder PHPUnit scaffold only)
- Soft deletes not on
Order— order hard-delete is irreversible
Critical Dependencies¶
| Dependency | Risk |
|---|---|
| SBPS gateway | All normal purchases blocked if down |
| Chekicha API | Order fulfilment rolls back on API failure |
| MySQL | Single DB, no read replica visible |
| Redis | Cart session + queue; if down, cart broken and notifications fail |
| spatie/laravel-data v3 | DTOs used platform-wide; major version bump = mass breakage |
| Octane v2 | Persistent workers; Config::set() mutation in CustomSession risks cross-request bleed |