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-draftShort HTML entry:/codeigniter· This file:/apply/codeigniter.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 codeigniter 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/codeigniter.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 — 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 | <ORIGIN>/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\Modelsubclasses are Infrastructure persistence details.- Use Cases receive Domain Entities via repository ports — never
model('X')inside Use Cases.
Services (composition)
// 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/
Triggersmay wrap SharedEventDispatcherInterfacein Infra. - Cross-module: translation listener in consumer module; rich payload + idempotency.
Tooling
- PHPUnit (shipped); PHPStan;
sparkCLI for generators — generated classes must land in correct layer. - Keep
app/Config/Modules.phpdiscovery 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:
.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
# Modular Hexagonal DDD — Core rules
Canon: <ORIGIN>/v1/core (1.0.0-draft)
One-shot: <ORIGIN>/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
---
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/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.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.