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-draftShort HTML entry:/laravel· This file:/apply/laravel.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 laravel 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/laravel.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 — 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 | <ORIGIN>/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/Modelsfor modular BCs — keep per-module Infra models (or explicitly document a temporary exception).
Container & providers
// 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
SerializesModelswith Eloquent models). - Dispatch via Shared
EventDispatcherInterfaceadapter 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).
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
Architecture: Modular Hexagonal Domain-Driven Design
Canon: <ORIGIN>/v1/core (1.0.0-draft)
One-shot: <ORIGIN>/apply/laravel.full.md
Host: Laravel 13.x → <ORIGIN>/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
"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:
.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
# Modular Hexagonal DDD — Core rules
Canon: <ORIGIN>/v1/core (1.0.0-draft)
One-shot: <ORIGIN>/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
---
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/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.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.