16 min readCalm pace · scan the outline anytime

CodeIgniter one-shot apply (full architecture)

Self-contained Modular Hexagonal DDD + CodeIgniter 4.7 apply playbook for AI agents.

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).

  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

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)

StrictnessAreaAllowedForbidden
HardestDomainOwn Domain types + Shared kernel portsOther modules; host facades/helpers; ORM; framework traits on Domain Events
StrictApplication Use Cases / App DTOsOwn Domain + Shared portsFacades, ORM, foreign Application/Domain, peer *ModuleInterface
RelaxedComposition rootContainer binds, routes, config mergeBusiness rules
OuterInfrastructure & UIFramework, ORM, HTTP, ACL adapters, adminLeaking 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}/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.

NeedBridge
Peer answer nowSync ACL: local {Need}PortInterface → Infra ACL adapter → thin peer {This}ModuleInterface
Peer reacts laterAsync 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)

SituationWhere
Tech used by one moduleThat module’s Infrastructure
Same tech used by ≥2 modulesPromote to Shared
Cross-module business conceptNever 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

TargetCodeIgniter 4.7.x (e.g. 4.7.4 as of 2026-07; PHP 8.1+, check release notes)
Docshttps://codeigniter4.github.io/CodeIgniter4/ · Modules: user guide “Code Modules”
Packagecodeigniter4/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.

CoreCodeIgniter 4.7 place
Module roote.g. modules/{Module}/ or app/Modules/{Module}/ registered in Autoload $psr4
Sharedmodules/Shared/ or app/Shared/
Composition rootModule Config/Services.php extending CodeIgniter\Config\BaseService (auto-discovered) + app/Config/Services.phpfactories only
PersistenceInfrastructure/Persistence/ — CI Models / Query Builder only here; map to Domain Entities
ACL adapterInfra class returned from Services factory bound to local port
Translation listenerCLI Command / queued job (if using a queue lib) in Infra → inbound Use Case
UIModule 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)

// 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:

.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.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.

Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft