15 min readCalm pace · scan the outline anytime

Yii3 one-shot apply (full architecture)

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

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-draftShort HTML entry: /yii · This file: /apply/yii.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 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

Apply Modular Hexagonal DDD from <ORIGIN>/apply/yii.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 — 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
LegacyYii 2.0.55+ still maintained for existing apps — map the same Core roles; prefer Yii3 for greenfield
Docshttps://yii3.yiiframework.com/ · https://yiisoft.github.io/docs/ · Yii2: https://www.yiiframework.com/doc/guide/2.0/en
Adapter HTML<ORIGIN>/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).

CoreYii3 place
Module rootsrc/{Module}/ (or package) with Application/Domain/Infrastructure/UI
Sharedsrc/Shared/
Composition rootconfig/common/di/{module}.php (+ web/console splits) — wiring only
PersistenceInfrastructure/Persistence/ using Cycle ORM via yiisoft/yii-cycle (recommended) — map Cycle entities ↔ Domain Entities in repositories
ACL adapterInfra service bound in DI to local port interface
Translation listenerQueue/event consumer in Infra (e.g. Yii console worker / queue package) → inbound Use Case
UIHTTP 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)

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

One-shot: <ORIGIN>/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:

.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

# Modular Hexagonal DDD — Core rules
Canon: <ORIGIN>/v1/core (1.0.0-draft)
One-shot: <ORIGIN>/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

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

Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft