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-draftShort HTML entry:/cakephp· This file:/apply/cakephp.full.mdAI-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).
- Treat this document as the complete working standard for this session.
- Execute Part D in order after reading Parts A–C.
- When writing module code, obey Part B (Core) and Part C (host).
- Do not create a git commit unless the human explicitly asks.
- Rename illustrative contexts
Ordering,Warehouse,Directory,Identityto the human’s domains. - If AI vendor is
none, skip Part E.
Programmer prompt
Apply Modular Hexagonal DDD from <ORIGIN>/apply/cakephp.full.md
AI assistant: <cursor|claude|gemini|copilot|generic|none>
Bounded contexts: <list>
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)
{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)
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}/SomethingUseCasewith single public__invoke. - Nested helpers: no
UseCasesuffix; 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
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 | <ORIGIN>/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
Entityas Domain Entity. - Prefer constructor injection of ports into Use Cases.
- From CakePHP 5.3+,
TableContainerdelegate enables injecting*Tableinto Infra services — still keep Tables out of Domain/Use Cases (DI / TableContainer).
DI example
// 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
EventDispatcherInterfaceadapter. - Consumer plugin Infra subscriber/listener → Use Case; idempotent
eventId.
Tooling
- PHPUnit; PHPStan;
bin/cakebake 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:
.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
# Modular Hexagonal DDD — Core rules
Canon: <ORIGIN>/v1/core (1.0.0-draft)
One-shot: <ORIGIN>/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
---
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
- [ ] 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.mdor.gemini/instructions.md← pointer + E0- Shared
.ai/*
E6 — GitHub Copilot
.github/copilot-instructions.md← E0 + short host section
E7 — Generic
AGENTS.mdpointing at.ai/modular-hexagonal-ddd/ARCHITECTURE.full.md
E8 — Multiple vendors
One shared .ai/modular-hexagonal-ddd/; vendor entry files only point to it.