# Modular Hexagonal DDD — ONE-SHOT APPLY (codeigniter) > **Self-contained.** An AI agent should fetch **this file only** and execute it. > **Core version:** `1.0.0-draft` > **Short HTML entry:** `/codeigniter` · **This file:** `/apply/codeigniter.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 **codeigniter 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/codeigniter.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 — CodeIgniter host mapping | | | | --- | --- | | **Target** | **CodeIgniter 4.7.x** (e.g. 4.7.4 as of 2026-07; PHP **8.1+**, check release notes) | | **Docs** | https://codeigniter4.github.io/CodeIgniter4/ · Modules: user guide “Code Modules” | | **Package** | `codeigniter4/framework` | | **Adapter HTML** | `/v1/adapters/codeigniter` | CI4 supports **PSR-4 modules** via `app/Config/Autoload.php` and Composer. Prefer a module folder per BC with Core layers — do not put Domain logic in stock `app/Models`. | Core | CodeIgniter 4.7 place | | ---- | --------------------- | | Module root | e.g. `modules/{Module}/` or `app/Modules/{Module}/` registered in Autoload `$psr4` | | Shared | `modules/Shared/` or `app/Shared/` | | Composition root | Module `Config/Services.php` extending `CodeIgniter\Config\BaseService` (auto-discovered) + `app/Config/Services.php` — **factories only** | | Persistence | `Infrastructure/Persistence/` — CI Models / Query Builder **only here**; map to Domain Entities | | ACL adapter | Infra class returned from Services factory bound to local port | | Translation listener | CLI Command / queued job (if using a queue lib) in Infra → inbound Use Case | | UI | Module Controllers under `UI/Http` or `Controllers` **inside module namespace**; explicit routes in `Config/Routes.php` (auto-routing legacy off by default) | ### Models quarantine - `CodeIgniter\Model` subclasses are Infrastructure persistence details. - Use Cases receive Domain Entities via repository ports — never `model('X')` inside Use Cases. ### Services (composition) ```php // modules/Ordering/Config/Services.php namespace Modules\Ordering\Config; use CodeIgniter\Config\BaseService; class Services extends BaseService { public static function warehouseAvailabilityPort($getShared = true) { return static::getSharedInstance('warehouseAvailabilityPort', static function () { return new \Modules\Ordering\Infrastructure\ExternalServices\WarehouseAvailabilityAclAdapter( Services::warehouseModule() // peer façade from Warehouse module Services ); }); } } ``` Prefer constructor injection into controllers from a thin factory — avoid service locator sprawl inside Domain. ### Events - CI4 Events/`Triggers` may wrap Shared `EventDispatcherInterface` in Infra. - Cross-module: translation listener in consumer module; rich payload + idempotency. ### Tooling - PHPUnit (shipped); PHPStan; `spark` CLI for generators — generated classes must land in correct layer. - Keep `app/Config/Modules.php` discovery tuned so Composer packages are not over-scanned. --- # Part D — Apply procedure (CodeIgniter) ## D1 — Confirm `codeigniter4/framework` ^4.7 ## D2 — README with one-shot URL + module PSR-4 roots ## D3–D5 — ROADMAP / PRD / `.ai/.../ARCHITECTURE.full.md` ## D6 — Register namespaces in `app/Config/Autoload.php` + composer.json ## D7 — First module + `Config/Services.php` + routes ## D8 — Tests asserting no Model usage in Application/Domain ## D9 — Part E — no commit unless asked --- # Part F — Sketch Controller → PlaceOrderUseCase → availability port (Services) → Warehouse façade; event consumer Command in Warehouse module. --- # 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 codeigniter-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/codeigniter.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/codeigniter-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.