# Modular Hexagonal DDD — ONE-SHOT APPLY (laravel) > **Self-contained.** An AI agent should fetch **this file only** and execute it. > **Core version:** `1.0.0-draft` > **Short HTML entry:** `/laravel` · **This file:** `/apply/laravel.full.md` > **AI-neutral:** assistant files are optional; architecture does not require any AI vendor. > **Host baseline researched:** see Part C header (versions as of 2026-08). --- ## 0. Agent mandate You are bootstrapping a **laravel application repository** (not the docs site). 1. Treat this document as the complete working standard for this session. 2. Execute **Part D** in order after reading Parts A–C. 3. When writing module code, obey **Part B (Core)** and **Part C (host)**. 4. **Do not create a git commit** unless the human explicitly asks. 5. Rename illustrative contexts `Ordering`, `Warehouse`, `Directory`, `Identity` to the human’s domains. 6. If AI vendor is `none`, skip Part E. ### Programmer prompt ```text Apply Modular Hexagonal DDD from /apply/laravel.full.md AI assistant: Bounded contexts: Do not commit unless I ask. ``` --- # Part A — Design test > If the peer module is deleted and replaced by HTTP, does this module’s **Application** and **Domain** still compile? --- # Part B — Complete Core architecture ## B1. Purpose Build applications as **replaceable bounded-context modules** using Hexagonal Architecture (ports & adapters) + DDD. This docs site is the **sole canon**; consuming repos keep thin pointers, not a forked essay. ## B2. Module layout (MUST) ```text {Module}/ Application/ # UseCases, Application DTO, Providers / composition root Domain/ # Entities, Ports, Events, Enums, Domain DTOs Infrastructure/ # Persistence adapters, ExternalServices (ACL), Config, listeners UI/ # Controllers, Routes, validation, admin UI, console Shared/ # Promoted technical ports/adapters only (after promote rule) ``` Host roots vary (`app/Modules`, `src/Modules`, packages). **Roles do not.** ## B3. Strictness ladder (MUST) | Strictness | Area | Allowed | Forbidden | | ---------- | ---- | ------- | --------- | | Hardest | **Domain** | Own Domain types + Shared kernel ports | Other modules; host facades/helpers; ORM; framework traits on Domain Events | | Strict | **Application Use Cases / App DTOs** | Own Domain + Shared ports | Facades, ORM, foreign Application/Domain, peer `*ModuleInterface` | | Relaxed | **Composition root** | Container binds, routes, config merge | Business rules | | Outer | **Infrastructure & UI** | Framework, ORM, HTTP, ACL adapters, admin | Leaking ORM/foreign Domain into Domain/Use Cases | Dependency direction: **Domain ← Application ← Infrastructure/UI**. Ports in Domain; adapters in Infrastructure. ## B4. Golden flow (MUST) ```text UI → Application DTO → first-level *UseCase::__invoke → Domain (Entity / Domain service) → Domain Port → Infrastructure adapter (ORM / ACL / HTTP) → back as Entity / result → UI presents ``` ## B5. Use Cases & DTOs (MUST) - First-level: `Application/UseCases/{Capability}/SomethingUseCase` with single public `__invoke`. - Nested helpers: **no** `UseCase` suffix; **not** called from UI. - Mirror `{Capability}/` for DTOs and Ports. - ACL need ports: `Domain/Ports/Acl/`. Module façades: `Domain/Ports/Module/`. - Never create a literal `Feature/` directory. - Application DTO = UI/CLI input. Domain DTO = intra-module only. - Other modules never call Use Cases — only ACL / Events. ## B6. Cross-module bridges (MUST) Application/Domain of A **never** import Application/Domain of B. | Need | Bridge | | ---- | ------ | | Peer answer **now** | **Sync ACL**: local `{Need}PortInterface` → Infra ACL adapter → thin peer `{This}ModuleInterface` | | Peer reacts **later** | **Async Domain Event** → consumer Infra **translation listener** → inbound Use Case | - Mapping only in ACL adapter; façades thin + HTTP-mappable; Prefer ACL **validate before** local commit. - Events: pure Domain class; Shared `EventDispatcherInterface`; `eventId` + `occurredAt` + `schemaVersion` + rich happy-path payload; idempotent consumers; no Domain re-fetch on happy path; prefer outbox. - No cross-module write transaction. Reports: ACL reads or owned projections. ## B7. Shared promote rule (MUST) | Situation | Where | | --------- | ----- | | Tech used by one module | That module’s Infrastructure | | Same tech used by ≥2 modules | Promote to Shared | | Cross-module **business** concept | Never Shared — ACL / Events | Typical Shared: `DomainException`, `DatabaseTransactionInterface`, `EventDispatcherInterface`, `ClockInterface`, `ConfigReaderInterface`, `Ensure`. ## B8–B9. Persistence & UI (MUST) - Map persistence records ↔ Domain Entities in Infrastructure; never leak ORM into Use Cases/Domain. - UI: authorize → DTO → Use Case → present. ## B10. Decision tree ```text Only this module? → Golden flow Need peer now? → ACL Peer later? → Event Multi-step? → Orchestration (still ACL+Events) Report across contexts? → Projection / ACL read Tech in 2+ modules? → Shared Business in 2+ modules? → ACL/Events — never Shared ``` ## B11. Anti-patterns Peer imports in Use Cases; god façades; thin-ID events; Shared business enums; ORM in Domain; Entity extends ORM; literal `Feature/`; cross-module DB TX; ORM joins across modules for policy. ## B12. Examples `Ordering`, `Warehouse`, `Directory`, `Identity` only — rename to your domains. ## B13. Quality assertions Boundary tests must fail CI if: foreign Application/Domain imports; peer `*ModuleInterface` in Use Cases; ORM/facades in Domain/Use Cases; literal `Feature/` folders. Also: behaviour tests, formatter, static analysis, event idempotency. --- # Part C — Laravel host mapping | | | | --- | --- | | **Target** | **Laravel 13.x** (stable line as of 2026-08; PHP **≥ 8.3**, typically 8.3–8.5) | | **Docs** | https://laravel.com/docs/13.x | | **Adapter HTML** | `/v1/adapters/laravel` | Laravel 13 keeps the modern slim skeleton: `bootstrap/app.php` application builder, `routes/web.php` (+ optional `install:api`), Pest/PHPUnit under `tests/`, Pint, optional first-party AI/JSON:API/Scout features — **none of those may enter Domain/Use Cases**. | Core | Laravel 13 place | | ---- | ---------------- | | Module root | `app/Modules/{Module}/` (PSR-4 in `composer.json`) | | Shared | `app/Shared/` | | Composition root | Module `Application/Providers/*ServiceProvider.php` registered from `bootstrap/providers.php` (or `AppServiceProvider`) | | Persistence | `Infrastructure/Persistence/Eloquent/` — Models, Repositories, Migrations | | ACL adapter | `Infrastructure/ExternalServices/*AclAdapter.php` | | Translation listener | Infra listener / queued Job → inbound Use Case (prefer queue) | | UI | `UI/Http` Controllers + Form Requests; optional Filament under `UI/Filament/` | ### Eloquent (MUST quarantine) - Eloquent Models **only** under Infrastructure. - Repositories map Model ↔ Domain Entity manually. - Use Cases depend on Domain repository **ports** only. - Do **not** put business models in default `app/Models` for modular BCs — keep per-module Infra models (or explicitly document a temporary exception). ### Container & providers ```php // Module ServiceProvider $this->app->bind( WarehouseAvailabilityPortInterface::class, WarehouseAvailabilityAclAdapter::class, ); ``` Register the provider in `bootstrap/providers.php` (Laravel 11+ / 13 style). Routes: load from module provider or `routes/` included by provider — **no business rules** in providers. ### Events & queues - Domain Events: pure PHP in Domain (no `SerializesModels` with Eloquent models). - Dispatch via Shared `EventDispatcherInterface` adapter wrapping Laravel’s dispatcher/bus. - Prefer **queued** listeners/jobs for cross-module side effects. - Outbox: same DB transaction as aggregate write when possible. ### Tooling - **Pint** for style; **Pest** (or PHPUnit) + architecture tests for Part B13. - **PHPStan/Larastan** on module code. - Optional Filament: UI-only, always call Use Cases ([filament page](/v1/adapters/laravel/filament-ui)). ### Forbidden Laravel shortcuts in Domain/Use Cases `config()`, `now()`, `DB::`, `Event::`, Facades, `Model::query()` — wrap behind ports. --- # Part D — Apply procedure (Laravel) ## D1 — Confirm host `composer.json` requires `laravel/framework` **^13** (or document if older; still apply Core, note gaps). ## D2 — README pointer ```text Architecture: Modular Hexagonal Domain-Driven Design Canon: /v1/core (1.0.0-draft) One-shot: /apply/laravel.full.md Host: Laravel 13.x → /v1/adapters/laravel Modules: app/Modules/ · Shared: app/Shared/ ``` ## D3–D4 — ROADMAP / optional PRD Product-only. Architecture MUST → one-shot / requirements URL. ## D5 — Local architecture mirror Copy this one-shot to `.ai/modular-hexagonal-ddd/ARCHITECTURE.full.md`. ## D6 — Composer PSR-4 ```json "App\\Modules\\": "app/Modules/", "App\\Shared\\": "app/Shared/" ``` ## D7 — Optional first module scaffold `app/Modules/{Module}/{Application,Domain,Infrastructure,UI}` + provider registration in `bootstrap/providers.php`. ## D8 — Arch tests Pest arch: forbid foreign module imports, Eloquent in Application/Domain, peer ModuleInterface in Use Cases, literal `Feature/`. ## D9 — Part E (AI) then report done — no commit unless asked. --- # Part F — Ordering ↔ Warehouse sketch ACL availability check before place-order commit; `OrderFulfilled` rich event → Warehouse translation listener → deduct Use Case. --- # Part G — Done README + `.ai/.../ARCHITECTURE.full.md` + AI files (if any) + arch-test plan; no unauthorized commit. --- # Part E — Assistant files (optional) Skip if AI vendor is `none`. Create shared folder: ```text .ai/modular-hexagonal-ddd/ ARCHITECTURE.full.md # copy of the host one-shot you fetched RULES.core.md SKILL.md CHECKLIST.md laravel-host.md # host-only notes from Part C ``` ### E0 — RULES.core.md ```markdown # Modular Hexagonal DDD — Core rules Canon: /v1/core (1.0.0-draft) One-shot: /apply/laravel.full.md Local: .ai/modular-hexagonal-ddd/ARCHITECTURE.full.md 1. Domain hardest: no host framework, ORM, other modules. 2. Use Cases: own Domain + Shared ports only. 3. Cross-module: ACL or Domain Events only. 4. Thin façades; rich events (eventId, schemaVersion); idempotent consumers. 5. No cross-module write TX; ACL validate-before-commit. 6. Shared = technical ≥2 modules only. 7. *UseCase + __invoke; no literal Feature/ folders. 8. Design test: peer→HTTP, Application/Domain still compile. ``` ### E1 — SKILL.md ```markdown --- name: modular-hexagonal-ddd description: Enforce Modular Hexagonal DDD. Modules, ACL, Events, Shared, Use Cases. --- Follow .ai/modular-hexagonal-ddd/ARCHITECTURE.full.md ``` ### E2 — CHECKLIST.md ```markdown - [ ] First-level *UseCase entry - [ ] App DTO → __invoke - [ ] I/O via Domain ports - [ ] No ORM/facades in Application/Domain - [ ] Sync peer → local ACL port → thin façade - [ ] Async → rich event + translation listener → inbound UC - [ ] Idempotent eventId - [ ] No cross-module DB TX - [ ] No foreign Application/Domain imports - [ ] Shared only technical ≥2 - [ ] No Feature/ directories - [ ] Replaceability test = yes ``` ### E3 — Cursor - `.cursor/rules/modular-hexagonal-ddd.mdc` ← E0 + path to ARCHITECTURE.full.md - `.cursor/rules/laravel-host.mdc` ← Part C summary - `.cursor/skills/modular-hexagonal-ddd/SKILL.md` ← E1 ### E4 — Claude - `CLAUDE.md` ← pointer to ARCHITECTURE.full.md + E0 - Shared `.ai/modular-hexagonal-ddd/*` ### E5 — Gemini - `GEMINI.md` or `.gemini/instructions.md` ← pointer + E0 - Shared `.ai/*` ### E6 — GitHub Copilot - `.github/copilot-instructions.md` ← E0 + short host section ### E7 — Generic - `AGENTS.md` pointing at `.ai/modular-hexagonal-ddd/ARCHITECTURE.full.md` ### E8 — Multiple vendors One shared `.ai/modular-hexagonal-ddd/`; vendor entry files only point to it.