# Modular Hexagonal DDD — ONE-SHOT APPLY (cakephp) > **Self-contained.** An AI agent should fetch **this file only** and execute it. > **Core version:** `1.0.0-draft` > **Short HTML entry:** `/cakephp` · **This file:** `/apply/cakephp.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 **cakephp 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/cakephp.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 — CakePHP host mapping | | | | --- | --- | | **Target** | **CakePHP 5.4.x** (e.g. 5.4.1 as of 2026-07; PHP **8.2–8.5**) | | **Docs** | https://book.cakephp.org/5/ · Plugins · Dependency Injection | | **App skeleton** | `cakephp/app` ^5.4 | | **Adapter HTML** | `/v1/adapters/cakephp` | Map each bounded context to a **CakePHP plugin** (or `src/{Module}` namespaces) containing Core layers. CakeORM `Table` / `Entity` classes are **Infrastructure**, not Domain Entities. | Core | CakePHP 5.4 place | | ---- | ----------------- | | Module root | `plugins/{Module}/` (plugin) or `src/{Module}/` | | Shared | `plugins/Shared/` or `src/Shared/` | | Composition root | `Application::services()` and plugin `Plugin::services(ContainerInterface)` — **bindings only** | | Persistence | `Infrastructure/Persistence/` — `Table` classes + Cake `Entity` maps → Domain Entities in repositories | | ACL adapter | Infra service registered in DI; inject local port into Use Cases | | Translation listener | Queue listener / Cake event subscriber in Infra → inbound Use Case | | UI | Controllers in plugin `UI` / `Controller` calling Use Cases; no business rules in Tables | ### ORM quarantine - Do **not** treat Cake `Entity` as Domain Entity. - Prefer constructor injection of ports into Use Cases. - From CakePHP 5.3+, `TableContainer` delegate enables injecting `*Table` into Infra services — still keep Tables out of Domain/Use Cases ([DI / TableContainer](https://book.cakephp.org/5/en/development/dependency-injection.html)). ### DI example ```php // plugins/Ordering/src/OrderingPlugin.php public function services(ContainerInterface $container): void { $container->add(WarehouseAvailabilityPortInterface::class) ->setConcrete(WarehouseAvailabilityAclAdapter::class); $container->add(PlaceOrderUseCase::class) ->addArgument(WarehouseAvailabilityPortInterface::class) /* + other ports */; } ``` Enable plugins in `Application::bootstrap()`. ### Events - Wrap Cake events or a queue in Shared `EventDispatcherInterface` adapter. - Consumer plugin Infra subscriber/listener → Use Case; idempotent `eventId`. ### Tooling - PHPUnit; PHPStan; `bin/cake` bake **only** into Infrastructure/UI paths you specify. - Rector/Cake upgrades: keep Domain pure when upgrading 5.x minors. --- # Part D — Apply procedure (CakePHP) ## D1 — Confirm CakePHP 5.4 app (`cakephp/cakephp` ^5.4) ## D2 — README one-shot + plugin layout ## D3–D5 — ROADMAP / PRD / `.ai` ARCHITECTURE copy ## D6 — Create first plugin module with Application/Domain/Infrastructure/UI ## D7 — Register plugin + `services()` bindings ## D8 — Tests: no Table/Entity in Application/Domain ## D9 — Part E — no commit unless asked --- # Part F — Sketch Ordering controller → Use Case → ACL port → Warehouse plugin façade service; async subscriber in Warehouse plugin. --- # Part G — Done checklist (standard) --- # 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 cakephp-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/cakephp.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/cakephp-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.