# Modular Hexagonal DDD — ONE-SHOT APPLY (yii) > **Self-contained.** An AI agent should fetch **this file only** and execute it. > **Core version:** `1.0.0-draft` > **Short HTML entry:** `/yii` · **This file:** `/apply/yii.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 **yii 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/yii.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 — Yii host mapping | | | | --- | --- | | **Target (new apps)** | **Yii3** (`yiisoft/*` packages, production-ready ecosystem; PHP **≥ 8.2**, through 8.5) — **no single metapackage version**; pin packages individually | | **Legacy** | **Yii 2.0.55+** still maintained for existing apps — map the same Core roles; prefer Yii3 for greenfield | | **Docs** | https://yii3.yiiframework.com/ · https://yiisoft.github.io/docs/ · Yii2: https://www.yiiframework.com/doc/guide/2.0/en | | **Adapter HTML** | `/v1/adapters/yii` | Yii3 treats a **module as a design boundary** (not necessarily a base class): group Domain/Application/Infra/UI + `config/common/di/*.php` bindings ([Yii3 modules guide](https://yiisoft.github.io/docs/guide/structure/module.html)). | Core | Yii3 place | | ---- | ---------- | | Module root | `src/{Module}/` (or package) with Application/Domain/Infrastructure/UI | | Shared | `src/Shared/` | | Composition root | `config/common/di/{module}.php` (+ web/console splits) — **wiring only** | | Persistence | `Infrastructure/Persistence/` using **Cycle ORM** via `yiisoft/yii-cycle` (recommended) — map Cycle entities ↔ Domain Entities in repositories | | ACL adapter | Infra service bound in DI to local port interface | | Translation listener | Queue/event consumer in Infra (e.g. Yii console worker / queue package) → inbound Use Case | | UI | HTTP controllers / handlers under `UI/` calling Use Cases | ### Cycle ORM / ActiveRecord quarantine - Cycle entities and `yiisoft/yii-cycle` config stay in Infrastructure. - Domain ports return Domain Entities/DTOs only. - Do not inject Cycle `ORMInterface` into Use Cases. ### DI (Yii3) ```php // config/common/di/ordering.php return [ \App\Ordering\Domain\Ports\Acl\WarehouseAvailabilityPortInterface::class => \App\Ordering\Infrastructure\ExternalServices\WarehouseAvailabilityAclAdapter::class, ]; ``` ### Yii 2 note (brownfield) If stuck on Yii2: put modules under custom PSR-4 roots; use `Yii::$container` bindings in Module `init()` / bootstrap — still **no** `ActiveRecord` in Domain/Use Cases; AR only in Infra repositories. ### Tooling - Psalm/PHPStan (Yii3 packages are strict); PHPUnit; composer scripts. - Prefer package-per-module when a BC becomes reusable (`extra.config-plugin` for di/routes). --- # Part D — Apply procedure (Yii) ## D1 — Confirm host Greenfield: Yii3 app template with `yiisoft/di` + router. Brownfield Yii2: state version explicitly. ## D2 — README ```text One-shot: /apply/yii.full.md Host: Yii3 (yiisoft/*) or Yii2.0.x brownfield Modules: src/{Module}/ · DI: config/common/di/ ``` ## D3–D5 — ROADMAP / PRD / copy ARCHITECTURE.full.md into `.ai/` ## D6 — PSR-4 + first module DI file ## D7 — Optional Cycle schema paths per module Infra ## D8 — Static analysis + boundary conventions for Part B13 ## D9 — Part E — no commit unless asked --- # Part F — Sketch Ordering port → Warehouse module service via DI; async handler in Warehouse Infra. --- # 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 yii-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/yii.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/yii-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.