From 50b01ec6d386e4e14abd3df371f57affa3e7d2e1 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:00:41 +0700 Subject: [PATCH 01/16] fix: restrict domain dto paths --- config/deptrac/README.md | 2 +- config/deptrac/README.ru.md | 2 +- config/deptrac/README.zh.md | 2 +- config/deptrac/depfile.yaml | 2 +- docs/conventions/core-patterns/dto.md | 8 +++++++- docs/conventions/core-patterns/factory.md | 3 ++- docs/conventions/core-patterns/service.md | 3 ++- docs/conventions/layers/layers.md | 6 +++--- src/Deptrac/CrossModuleDomainRule.php | 22 ++++++++++++++++----- tests/Deptrac/CrossModuleDomainRuleTest.php | 8 ++++++++ 10 files changed, 43 insertions(+), 15 deletions(-) diff --git a/config/deptrac/README.md b/config/deptrac/README.md index 55a82b2..ab8621e 100644 --- a/config/deptrac/README.md +++ b/config/deptrac/README.md @@ -30,7 +30,7 @@ Deptrac catches such violations **deterministically** — in CI, on every commit | `Domain` | Entities, VOs, repository interfaces | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Domain enum (closed) | -| `DomainDto` | Domain DTOs (→ Domain, VO, Enum) | +| `DomainDto` | Domain Service DTOs (→ Domain, VO, Enum) | | `Application` | Use cases, services | | `ApplicationDto` | Application DTOs (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.ru.md b/config/deptrac/README.ru.md index 0dc2aa8..b04c0e5 100644 --- a/config/deptrac/README.ru.md +++ b/config/deptrac/README.ru.md @@ -30,7 +30,7 @@ Deptrac ловит такие нарушения **детерминирован | `Domain` | Сущности, VO, интерфейсы репозиториев | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Enum домена (замкнут) | -| `DomainDto` | DTO домена (→ Domain, VO, Enum) | +| `DomainDto` | DTO доменного сервиса (→ Domain, VO, Enum) | | `Application` | Use cases, сервисы | | `ApplicationDto` | DTO приложения (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.zh.md b/config/deptrac/README.zh.md index 04cdd39..99a87f8 100644 --- a/config/deptrac/README.zh.md +++ b/config/deptrac/README.zh.md @@ -30,7 +30,7 @@ Deptrac **确定性地**捕获此类违规——在 CI 中,每次提交时都 | `Domain` | 实体、值对象、仓库接口 | | `DomainVo` | 值对象(→ Domain, Enum) | | `DomainEnum` | 领域枚举(封闭) | -| `DomainDto` | 领域 DTO(→ Domain, VO, Enum) | +| `DomainDto` | 领域服务 DTO(→ Domain, VO, Enum) | | `Application` | 用例、服务 | | `ApplicationDto` | 应用 DTO(→ DTO, Enum) | | `ApplicationCommand` | 命令(CQRS) | diff --git a/config/deptrac/depfile.yaml b/config/deptrac/depfile.yaml index 52d4f6f..8e44fc3 100644 --- a/config/deptrac/depfile.yaml +++ b/config/deptrac/depfile.yaml @@ -123,7 +123,7 @@ deptrac: - name: DomainDto collectors: - type: classLike - value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\.*Dto$ + value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\Service\\.*Dto$ - name: ApplicationDto collectors: diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index 6beaf56..0ae516f 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -28,6 +28,7 @@ description: Правила создания и использования об [Query DTO](../layers/presentation/query-dto.md), [Response DTO](../layers/presentation/response-dto.md). - Именование: суффикс `Dto`. Контекст — в имени/namespace (`RequestDto`, `ResponseDto`, `ResultDto`, `...Dto`). +- DTO в `Domain` — исключение для входа/выхода конкретного `Domain Service`; общий каталог `Domain\Dto` запрещён. ## Зависимости @@ -85,6 +86,9 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Domain\Service\{ServiceName}\{Name}Dto ``` +`Domain\Dto\{Name}Dto` не используем: у доменного DTO всегда есть локальный контекст сервиса. Если DTO начинает +переиспользоваться за пределами одного сервиса, заменяем его на `VO` или выносим контракт на уровень `Application`. + ## Как используем - В Application `Use Case` принимает/возвращает DTO, маппит из/в доменные модели через мапперы. @@ -96,7 +100,8 @@ description: Правила создания и использования об - Для передачи данных между слоями и границами подсистем. - В Application разрешено общее DTO, если один и тот же набор данных переиспользуется в нескольких Use Case. -- В Domain используем DTO, когда нужно принять/вернуть данные конкретного сервиса. Если DTO начинает «гулять» по коду, рассмотрите перенос в Value Object (VO). +- В Domain используем DTO только когда нужно принять/вернуть данные конкретного сервиса. Если DTO начинает «гулять» по коду, + рассмотрите перенос в Value Object (VO). ## Пример @@ -187,6 +192,7 @@ final readonly class InferenceRequestDto - [ ] Нет зависимостей на сервисы/репозитории/компоненты и внешнее окружение. - [ ] Коллекции типизированы через PHPDoc (`@var FooDto[]`). - [ ] Название и namespace отражают контекст использования (`Request/Response/Result` при необходимости). +- [ ] Domain DTO лежит рядом с конкретным сервисом (`Domain\Service\...`), а не в общем `Domain\Dto`. - [ ] Денежные/точные величины представлены `numeric-string` или VO. - [ ] DTO используется на границах слоя, а не подменяет доменные сущности. - [ ] Для Presentation transport DTO дополнительно соблюдены профильные presentation-conventions. diff --git a/docs/conventions/core-patterns/factory.md b/docs/conventions/core-patterns/factory.md index c9d6fa0..9ca0652 100644 --- a/docs/conventions/core-patterns/factory.md +++ b/docs/conventions/core-patterns/factory.md @@ -22,7 +22,8 @@ description: Правила создания и использования фа **Разрешено**: - Примитивы (только конфигурационные), Enum, VO своего модуля. -- DTO — только в Application/Presentation, не в Domain. +- DTO — в Application/Presentation. В Domain не создаём общие DTO; узкие DTO конкретного Domain Service допустимы + только по правилам [DTO](dto.md) и не размещаются в `Domain\Dto`. - Через DI Спецификации, другие фабрики своего модуля. **Запрещено**: diff --git a/docs/conventions/core-patterns/service.md b/docs/conventions/core-patterns/service.md index e0d5306..e81fce1 100644 --- a/docs/conventions/core-patterns/service.md +++ b/docs/conventions/core-patterns/service.md @@ -42,7 +42,8 @@ description: Правила создания и использования се - Используется только внутри слоя **Domain**. - Работает только с объектами домена: `Entity`, `VO`, `Enum`, `UuidInterface`, примитивы. - Для входа/выхода предпочтительны доменные `VO` (включая именованные result/input объекты), если есть инварианты и доменный смысл. -- `DTO` в Domain допустим как технический carrier в узком месте, но не как замена доменных типов. +- `DTO` в Domain допустим как технический carrier в узком месте, только рядом с конкретным сервисом + (`Domain\Service\{GroupName?}\{Name}Dto`), но не как замена доменных типов. Общий каталог `Domain\Dto` запрещён. - Может зависеть от: - других сервисов своего модуля; - `Specification`; diff --git a/docs/conventions/layers/layers.md b/docs/conventions/layers/layers.md index 79cca2d..1a3b335 100644 --- a/docs/conventions/layers/layers.md +++ b/docs/conventions/layers/layers.md @@ -72,7 +72,7 @@ Application зависит только от Domain: Infrastructure реализует интерфейсы Domain: - Реализует `RepositoryInterface` из Domain -- Может использовать доменные типы (`VO`, `Enum`, доменные `DTO`) в сигнатурах и маппинге +- Может использовать доменные типы (`VO`, `Enum`, локальные для сервиса (service-local) доменные `DTO`) в сигнатурах и маппинге - Подключается через DI-контейнер - Не используется напрямую из Application @@ -81,7 +81,7 @@ Infrastructure реализует интерфейсы Domain: Integration вызывает Application чужого модуля: - Обрабатывает внешние события и инициирует соответствующие Use Cases. - Реализует интеграции через интерфейсы сервисов своего Domain. -- Может использовать доменные типы в сигнатурах контрактов (`VO`, `Enum`, доменные `DTO`). +- Может использовать доменные типы в сигнатурах контрактов (`VO`, `Enum`, локальные для сервиса доменные `DTO`). - Не зависит от слоя Infrastructure. - Формирует антикоррупционный слой (ACL) на границе с внешними системами. @@ -102,7 +102,7 @@ Presentation зависит только от Application: | **Integration** | ✅* | ✅ | ❌ | — | ❌ | | **Presentation** | ❌ | ✅ | ❌ | ❌ | — | -\* Только контракты и типы Domain (интерфейсы сервисов, `VO`/`Enum`/доменные `DTO` в сигнатурах), без зависимости на доменные реализации. +\* Только контракты и типы Domain (интерфейсы сервисов, `VO`/`Enum`/локальные для сервиса доменные `DTO` в сигнатурах), без зависимости на доменные реализации. ## Расположение diff --git a/src/Deptrac/CrossModuleDomainRule.php b/src/Deptrac/CrossModuleDomainRule.php index f4c2de6..0800dc3 100644 --- a/src/Deptrac/CrossModuleDomainRule.php +++ b/src/Deptrac/CrossModuleDomainRule.php @@ -17,6 +17,7 @@ * * All other cross-module dependencies are violations, regardless of layer. * Shared data types (ValueObject, Enum, Dto) are excluded — safe to use across modules. + * Domain DTOs are shared only when scoped to a concrete Domain Service. * Domain\Entity → foreign Domain\Entity is excluded — Doctrine ORM relations require class references. * Infrastructure\*Criteria\Mapper → foreign Domain\Entity is excluded — query building requires entity references. * @@ -57,7 +58,7 @@ public function onProcessEvent(ProcessEvent $event): void return; } - if ($this->isSharedDataType($dependent['path'])) { + if ($this->isSharedDataType($dependent['layer'], $dependent['path'])) { return; } @@ -140,12 +141,23 @@ private function parseModuleClass(string $className): ?array /** * ValueObject, Enum and Dto are shared data types — safe for cross-module usage. + * Domain DTOs must be service-local: Domain\Service\...Dto. */ - private function isSharedDataType(string $path): bool + private function isSharedDataType(string $layer, string $path): bool { - return str_starts_with($path, 'ValueObject\\') - || str_starts_with($path, 'Enum\\') - || (bool) preg_match('/Dto$/', $path); + if (str_starts_with($path, 'ValueObject\\') || str_starts_with($path, 'Enum\\')) { + return true; + } + + if ((bool) preg_match('/Dto$/', $path) === false) { + return false; + } + + if ($layer !== 'Domain') { + return true; + } + + return str_starts_with($path, 'Service\\'); } private function dependentLayerName(ProcessEvent $event): string diff --git a/tests/Deptrac/CrossModuleDomainRuleTest.php b/tests/Deptrac/CrossModuleDomainRuleTest.php index 6af6f33..3425ed1 100644 --- a/tests/Deptrac/CrossModuleDomainRuleTest.php +++ b/tests/Deptrac/CrossModuleDomainRuleTest.php @@ -384,6 +384,14 @@ public function testInfrastructureUsesForeignDomainEnum(): void public function testInfrastructureUsesForeignDomainDto(): void { $this->assertNoViolation( + 'App\Common\Module\DynamicLoop\Infrastructure\Service\SomeService', + 'App\Common\Module\ChainExecution\Domain\Service\ChainResult\ChainResultAuditDto', + ); + } + + public function testInfrastructureUsesForeignDomainDtoFromCommonDtoDirectory(): void + { + $this->assertViolation( 'App\Common\Module\DynamicLoop\Infrastructure\Service\SomeService', 'App\Common\Module\ChainExecution\Domain\Dto\ChainResultAuditDto', ); From 12776772ad7210ccfc91295a21e22c5d521e94c1 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:07:50 +0700 Subject: [PATCH 02/16] fix: keep domain dto collector broad --- config/deptrac/README.md | 2 +- config/deptrac/README.ru.md | 2 +- config/deptrac/README.zh.md | 2 +- config/deptrac/depfile.yaml | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config/deptrac/README.md b/config/deptrac/README.md index ab8621e..539c6db 100644 --- a/config/deptrac/README.md +++ b/config/deptrac/README.md @@ -30,7 +30,7 @@ Deptrac catches such violations **deterministically** — in CI, on every commit | `Domain` | Entities, VOs, repository interfaces | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Domain enum (closed) | -| `DomainDto` | Domain Service DTOs (→ Domain, VO, Enum) | +| `DomainDto` | Domain DTOs; valid path: `Domain\Service\...` (→ Domain, VO, Enum) | | `Application` | Use cases, services | | `ApplicationDto` | Application DTOs (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.ru.md b/config/deptrac/README.ru.md index b04c0e5..78cf5bf 100644 --- a/config/deptrac/README.ru.md +++ b/config/deptrac/README.ru.md @@ -30,7 +30,7 @@ Deptrac ловит такие нарушения **детерминирован | `Domain` | Сущности, VO, интерфейсы репозиториев | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Enum домена (замкнут) | -| `DomainDto` | DTO доменного сервиса (→ Domain, VO, Enum) | +| `DomainDto` | DTO домена; валидный путь: `Domain\Service\...` (→ Domain, VO, Enum) | | `Application` | Use cases, сервисы | | `ApplicationDto` | DTO приложения (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.zh.md b/config/deptrac/README.zh.md index 99a87f8..65d7545 100644 --- a/config/deptrac/README.zh.md +++ b/config/deptrac/README.zh.md @@ -30,7 +30,7 @@ Deptrac **确定性地**捕获此类违规——在 CI 中,每次提交时都 | `Domain` | 实体、值对象、仓库接口 | | `DomainVo` | 值对象(→ Domain, Enum) | | `DomainEnum` | 领域枚举(封闭) | -| `DomainDto` | 领域服务 DTO(→ Domain, VO, Enum) | +| `DomainDto` | 领域 DTO;有效路径:`Domain\Service\...`(→ Domain, VO, Enum) | | `Application` | 用例、服务 | | `ApplicationDto` | 应用 DTO(→ DTO, Enum) | | `ApplicationCommand` | 命令(CQRS) | diff --git a/config/deptrac/depfile.yaml b/config/deptrac/depfile.yaml index 8e44fc3..0dcdfb5 100644 --- a/config/deptrac/depfile.yaml +++ b/config/deptrac/depfile.yaml @@ -121,9 +121,11 @@ deptrac: value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\Enum\\.* - name: DomainDto + # Keep this collector broad to classify all Domain DTO-like classes. + # Valid path is enforced by DtoStructureSniff: Domain\Service\...Dto. collectors: - type: classLike - value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\Service\\.*Dto$ + value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\.*Dto$ - name: ApplicationDto collectors: From 95bdb53bd50515b0508c7d4038b02b3e632ccdda Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:10:44 +0700 Subject: [PATCH 03/16] fix: forbid domain dto directory --- docs/conventions/core-patterns/dto.md | 6 +++--- docs/conventions/core-patterns/factory.md | 2 +- docs/conventions/core-patterns/service.md | 2 +- src/Sniffs/Structure/DtoStructureSniff.php | 11 ++++++----- tests/fixtures.php | 8 ++++++++ .../Module/Example/Domain/Dto/WrongPathPayload.inc | 8 ++++++++ 6 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/src/Module/Example/Domain/Dto/WrongPathPayload.inc diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index 0ae516f..c0ce125 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -28,7 +28,7 @@ description: Правила создания и использования об [Query DTO](../layers/presentation/query-dto.md), [Response DTO](../layers/presentation/response-dto.md). - Именование: суффикс `Dto`. Контекст — в имени/namespace (`RequestDto`, `ResponseDto`, `ResultDto`, `...Dto`). -- DTO в `Domain` — исключение для входа/выхода конкретного `Domain Service`; общий каталог `Domain\Dto` запрещён. +- DTO в `Domain` — исключение для входа/выхода конкретного `Domain Service`; любые классы в `Domain\Dto\*` запрещены. ## Зависимости @@ -86,7 +86,7 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Domain\Service\{ServiceName}\{Name}Dto ``` -`Domain\Dto\{Name}Dto` не используем: у доменного DTO всегда есть локальный контекст сервиса. Если DTO начинает +`Domain\Dto\*` не используем: у доменного DTO всегда есть локальный контекст сервиса. Если DTO начинает переиспользоваться за пределами одного сервиса, заменяем его на `VO` или выносим контракт на уровень `Application`. ## Как используем @@ -192,7 +192,7 @@ final readonly class InferenceRequestDto - [ ] Нет зависимостей на сервисы/репозитории/компоненты и внешнее окружение. - [ ] Коллекции типизированы через PHPDoc (`@var FooDto[]`). - [ ] Название и namespace отражают контекст использования (`Request/Response/Result` при необходимости). -- [ ] Domain DTO лежит рядом с конкретным сервисом (`Domain\Service\...`), а не в общем `Domain\Dto`. +- [ ] Domain DTO лежит рядом с конкретным сервисом (`Domain\Service\...`); классов в `Domain\Dto\*` нет. - [ ] Денежные/точные величины представлены `numeric-string` или VO. - [ ] DTO используется на границах слоя, а не подменяет доменные сущности. - [ ] Для Presentation transport DTO дополнительно соблюдены профильные presentation-conventions. diff --git a/docs/conventions/core-patterns/factory.md b/docs/conventions/core-patterns/factory.md index 9ca0652..03bc451 100644 --- a/docs/conventions/core-patterns/factory.md +++ b/docs/conventions/core-patterns/factory.md @@ -23,7 +23,7 @@ description: Правила создания и использования фа **Разрешено**: - Примитивы (только конфигурационные), Enum, VO своего модуля. - DTO — в Application/Presentation. В Domain не создаём общие DTO; узкие DTO конкретного Domain Service допустимы - только по правилам [DTO](dto.md) и не размещаются в `Domain\Dto`. + только по правилам [DTO](dto.md); любые классы в `Domain\Dto\*` запрещены. - Через DI Спецификации, другие фабрики своего модуля. **Запрещено**: diff --git a/docs/conventions/core-patterns/service.md b/docs/conventions/core-patterns/service.md index e81fce1..46beb09 100644 --- a/docs/conventions/core-patterns/service.md +++ b/docs/conventions/core-patterns/service.md @@ -43,7 +43,7 @@ description: Правила создания и использования се - Работает только с объектами домена: `Entity`, `VO`, `Enum`, `UuidInterface`, примитивы. - Для входа/выхода предпочтительны доменные `VO` (включая именованные result/input объекты), если есть инварианты и доменный смысл. - `DTO` в Domain допустим как технический carrier в узком месте, только рядом с конкретным сервисом - (`Domain\Service\{GroupName?}\{Name}Dto`), но не как замена доменных типов. Общий каталог `Domain\Dto` запрещён. + (`Domain\Service\{GroupName?}\{Name}Dto`), но не как замена доменных типов. Любые классы в `Domain\Dto\*` запрещены. - Может зависеть от: - других сервисов своего модуля; - `Specification`; diff --git a/src/Sniffs/Structure/DtoStructureSniff.php b/src/Sniffs/Structure/DtoStructureSniff.php index 0120b10..eb713d4 100644 --- a/src/Sniffs/Structure/DtoStructureSniff.php +++ b/src/Sniffs/Structure/DtoStructureSniff.php @@ -27,6 +27,8 @@ public function register(): array public function process(File $phpcsFile, $stackPtr): void { + $this->assertNoDomainDtoDirectory($phpcsFile, $stackPtr); + $className = $phpcsFile->getDeclarationName($stackPtr); if ($className === '' || str_ends_with($className, 'Dto') === false) { return; @@ -37,8 +39,6 @@ public function process(File $phpcsFile, $stackPtr): void return; } - $this->assertDomainDtoPath($phpcsFile, $stackPtr); - $this->assertFinalReadonly($phpcsFile, $stackPtr); $scopeStart = $tokens[$stackPtr]['scope_opener']; @@ -53,7 +53,7 @@ public function process(File $phpcsFile, $stackPtr): void $this->assertNoMembers($phpcsFile, $stackPtr, $scopeStart, $scopeEnd); } - private function assertDomainDtoPath(File $phpcsFile, int $classPtr): void + private function assertNoDomainDtoDirectory(File $phpcsFile, int $classPtr): void { $normalizedPath = str_replace('\\', '/', $phpcsFile->getFilename()); @@ -62,8 +62,9 @@ private function assertDomainDtoPath(File $phpcsFile, int $classPtr): void } $phpcsFile->addError( - 'Domain DTOs must be placed inside a Service context:' - . ' Domain/Service/{ServiceName}/{Name}Dto, not Domain/Dto/{Name}Dto.' . self::DOC_REF, + 'Classes must not be placed inside Domain/Dto.' + . ' Domain DTOs must be placed inside a Service context:' + . ' Domain/Service/{ServiceName}/{Name}Dto.' . self::DOC_REF, $classPtr, self::ERROR_DOMAIN_DTO_PATH, ); diff --git a/tests/fixtures.php b/tests/fixtures.php index eb5712d..c1a4cff 100644 --- a/tests/fixtures.php +++ b/tests/fixtures.php @@ -319,6 +319,14 @@ ], 'warnings' => [], ], + // DtoStructureSniff — any class in Domain/Dto/ — wrong path + [ + 'file' => __DIR__ . '/fixtures/src/Module/Example/Domain/Dto/WrongPathPayload.inc', + 'errors' => [ + 6 => 1, + ], + 'warnings' => [], + ], // DtoStructureSniff — Domain DTO in Domain/Service/{GroupName}/ — correct path [ 'file' => __DIR__ . '/fixtures/src/Module/Example/Domain/Service/Payment/InitPaymentResultDto.inc', diff --git a/tests/fixtures/src/Module/Example/Domain/Dto/WrongPathPayload.inc b/tests/fixtures/src/Module/Example/Domain/Dto/WrongPathPayload.inc new file mode 100644 index 0000000..3ae18df --- /dev/null +++ b/tests/fixtures/src/Module/Example/Domain/Dto/WrongPathPayload.inc @@ -0,0 +1,8 @@ + Date: Thu, 4 Jun 2026 11:17:39 +0700 Subject: [PATCH 04/16] fix: allow contextual domain dto paths --- config/deptrac/README.md | 2 +- config/deptrac/README.ru.md | 2 +- config/deptrac/README.zh.md | 2 +- config/deptrac/depfile.yaml | 2 +- docs/conventions/core-patterns/dto.md | 18 ++++++++++++------ docs/conventions/core-patterns/factory.md | 2 +- docs/conventions/core-patterns/service.md | 4 ++-- docs/conventions/layers/layers.md | 6 +++--- src/Deptrac/CrossModuleDomainRule.php | 7 ++++--- src/Sniffs/Structure/DtoStructureSniff.php | 3 +-- tests/Deptrac/CrossModuleDomainRuleTest.php | 8 ++++++++ tests/fixtures.php | 8 +++++++- .../Calculator/Pricing/PricingResultDto.inc | 12 ++++++++++++ .../Module/Example/Domain/Dto/WrongPathDto.inc | 2 +- .../Service/Payment/InitPaymentResultDto.inc | 2 +- 15 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 tests/fixtures/src/Module/Example/Domain/Calculator/Pricing/PricingResultDto.inc diff --git a/config/deptrac/README.md b/config/deptrac/README.md index 539c6db..52b6dec 100644 --- a/config/deptrac/README.md +++ b/config/deptrac/README.md @@ -30,7 +30,7 @@ Deptrac catches such violations **deterministically** — in CI, on every commit | `Domain` | Entities, VOs, repository interfaces | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Domain enum (closed) | -| `DomainDto` | Domain DTOs; valid path: `Domain\Service\...` (→ Domain, VO, Enum) | +| `DomainDto` | Domain DTOs; `Domain\Dto\*` is forbidden (→ Domain, VO, Enum) | | `Application` | Use cases, services | | `ApplicationDto` | Application DTOs (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.ru.md b/config/deptrac/README.ru.md index 78cf5bf..9fd2e13 100644 --- a/config/deptrac/README.ru.md +++ b/config/deptrac/README.ru.md @@ -30,7 +30,7 @@ Deptrac ловит такие нарушения **детерминирован | `Domain` | Сущности, VO, интерфейсы репозиториев | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Enum домена (замкнут) | -| `DomainDto` | DTO домена; валидный путь: `Domain\Service\...` (→ Domain, VO, Enum) | +| `DomainDto` | DTO домена; `Domain\Dto\*` запрещён (→ Domain, VO, Enum) | | `Application` | Use cases, сервисы | | `ApplicationDto` | DTO приложения (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.zh.md b/config/deptrac/README.zh.md index 65d7545..335f1c2 100644 --- a/config/deptrac/README.zh.md +++ b/config/deptrac/README.zh.md @@ -30,7 +30,7 @@ Deptrac **确定性地**捕获此类违规——在 CI 中,每次提交时都 | `Domain` | 实体、值对象、仓库接口 | | `DomainVo` | 值对象(→ Domain, Enum) | | `DomainEnum` | 领域枚举(封闭) | -| `DomainDto` | 领域 DTO;有效路径:`Domain\Service\...`(→ Domain, VO, Enum) | +| `DomainDto` | 领域 DTO;禁止 `Domain\Dto\*`(→ Domain, VO, Enum) | | `Application` | 用例、服务 | | `ApplicationDto` | 应用 DTO(→ DTO, Enum) | | `ApplicationCommand` | 命令(CQRS) | diff --git a/config/deptrac/depfile.yaml b/config/deptrac/depfile.yaml index 0dcdfb5..b5357e3 100644 --- a/config/deptrac/depfile.yaml +++ b/config/deptrac/depfile.yaml @@ -122,7 +122,7 @@ deptrac: - name: DomainDto # Keep this collector broad to classify all Domain DTO-like classes. - # Valid path is enforced by DtoStructureSniff: Domain\Service\...Dto. + # Invalid common directory is enforced by DtoStructureSniff: Domain\Dto\* is forbidden. collectors: - type: classLike value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\.*Dto$ diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index c0ce125..3f7db6f 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -28,7 +28,7 @@ description: Правила создания и использования об [Query DTO](../layers/presentation/query-dto.md), [Response DTO](../layers/presentation/response-dto.md). - Именование: суффикс `Dto`. Контекст — в имени/namespace (`RequestDto`, `ResponseDto`, `ResultDto`, `...Dto`). -- DTO в `Domain` — исключение для входа/выхода конкретного `Domain Service`; любые классы в `Domain\Dto\*` запрещены. +- DTO в `Domain` — исключение для входа/выхода конкретного доменного контекста; любые классы в `Domain\Dto\*` запрещены. ## Зависимости @@ -80,14 +80,20 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Infrastructure\Component\{Component}\Dto\{Name}Dto ``` -- DTO доменного уровня (вход/выход конкретного Domain Service): +- DTO доменного уровня размещаем рядом с владельцем контекста: + +``` +{ProjectName}\Common\Module\{ModuleName}\Domain\{Context}\{GroupName?}\{Name}Dto +``` + +Например, для результата доменного сервиса: ``` {ProjectName}\Common\Module\{ModuleName}\Domain\Service\{ServiceName}\{Name}Dto ``` -`Domain\Dto\*` не используем: у доменного DTO всегда есть локальный контекст сервиса. Если DTO начинает -переиспользоваться за пределами одного сервиса, заменяем его на `VO` или выносим контракт на уровень `Application`. +`Domain\Dto\*` не используем: у доменного DTO всегда есть локальный владелец (`Service`, `Calculator`, `Specification` и т.п.). Если DTO начинает +переиспользоваться за пределами одного контекста, заменяем его на `VO` или выносим контракт на уровень `Application`. ## Как используем @@ -100,7 +106,7 @@ description: Правила создания и использования об - Для передачи данных между слоями и границами подсистем. - В Application разрешено общее DTO, если один и тот же набор данных переиспользуется в нескольких Use Case. -- В Domain используем DTO только когда нужно принять/вернуть данные конкретного сервиса. Если DTO начинает «гулять» по коду, +- В Domain используем DTO только когда нужно принять/вернуть данные конкретного доменного контекста. Если DTO начинает «гулять» по коду, рассмотрите перенос в Value Object (VO). ## Пример @@ -192,7 +198,7 @@ final readonly class InferenceRequestDto - [ ] Нет зависимостей на сервисы/репозитории/компоненты и внешнее окружение. - [ ] Коллекции типизированы через PHPDoc (`@var FooDto[]`). - [ ] Название и namespace отражают контекст использования (`Request/Response/Result` при необходимости). -- [ ] Domain DTO лежит рядом с конкретным сервисом (`Domain\Service\...`); классов в `Domain\Dto\*` нет. +- [ ] Domain DTO лежит рядом с владельцем контекста (`Domain\{Context}\...`); классов в `Domain\Dto\*` нет. - [ ] Денежные/точные величины представлены `numeric-string` или VO. - [ ] DTO используется на границах слоя, а не подменяет доменные сущности. - [ ] Для Presentation transport DTO дополнительно соблюдены профильные presentation-conventions. diff --git a/docs/conventions/core-patterns/factory.md b/docs/conventions/core-patterns/factory.md index 03bc451..9b1c12b 100644 --- a/docs/conventions/core-patterns/factory.md +++ b/docs/conventions/core-patterns/factory.md @@ -22,7 +22,7 @@ description: Правила создания и использования фа **Разрешено**: - Примитивы (только конфигурационные), Enum, VO своего модуля. -- DTO — в Application/Presentation. В Domain не создаём общие DTO; узкие DTO конкретного Domain Service допустимы +- DTO — в Application/Presentation. В Domain не создаём общие DTO; узкие DTO конкретного доменного контекста допустимы только по правилам [DTO](dto.md); любые классы в `Domain\Dto\*` запрещены. - Через DI Спецификации, другие фабрики своего модуля. diff --git a/docs/conventions/core-patterns/service.md b/docs/conventions/core-patterns/service.md index 46beb09..d61626a 100644 --- a/docs/conventions/core-patterns/service.md +++ b/docs/conventions/core-patterns/service.md @@ -42,8 +42,8 @@ description: Правила создания и использования се - Используется только внутри слоя **Domain**. - Работает только с объектами домена: `Entity`, `VO`, `Enum`, `UuidInterface`, примитивы. - Для входа/выхода предпочтительны доменные `VO` (включая именованные result/input объекты), если есть инварианты и доменный смысл. -- `DTO` в Domain допустим как технический carrier в узком месте, только рядом с конкретным сервисом - (`Domain\Service\{GroupName?}\{Name}Dto`), но не как замена доменных типов. Любые классы в `Domain\Dto\*` запрещены. +- `DTO` в Domain допустим как технический carrier в узком месте, только рядом с владельцем контекста + (`Domain\{Context}\{GroupName?}\{Name}Dto`), но не как замена доменных типов. Любые классы в `Domain\Dto\*` запрещены. - Может зависеть от: - других сервисов своего модуля; - `Specification`; diff --git a/docs/conventions/layers/layers.md b/docs/conventions/layers/layers.md index 1a3b335..e0ad58a 100644 --- a/docs/conventions/layers/layers.md +++ b/docs/conventions/layers/layers.md @@ -72,7 +72,7 @@ Application зависит только от Domain: Infrastructure реализует интерфейсы Domain: - Реализует `RepositoryInterface` из Domain -- Может использовать доменные типы (`VO`, `Enum`, локальные для сервиса (service-local) доменные `DTO`) в сигнатурах и маппинге +- Может использовать доменные типы (`VO`, `Enum`, локальные для владельца контекста доменные `DTO`) в сигнатурах и маппинге - Подключается через DI-контейнер - Не используется напрямую из Application @@ -81,7 +81,7 @@ Infrastructure реализует интерфейсы Domain: Integration вызывает Application чужого модуля: - Обрабатывает внешние события и инициирует соответствующие Use Cases. - Реализует интеграции через интерфейсы сервисов своего Domain. -- Может использовать доменные типы в сигнатурах контрактов (`VO`, `Enum`, локальные для сервиса доменные `DTO`). +- Может использовать доменные типы в сигнатурах контрактов (`VO`, `Enum`, локальные для владельца контекста доменные `DTO`). - Не зависит от слоя Infrastructure. - Формирует антикоррупционный слой (ACL) на границе с внешними системами. @@ -102,7 +102,7 @@ Presentation зависит только от Application: | **Integration** | ✅* | ✅ | ❌ | — | ❌ | | **Presentation** | ❌ | ✅ | ❌ | ❌ | — | -\* Только контракты и типы Domain (интерфейсы сервисов, `VO`/`Enum`/локальные для сервиса доменные `DTO` в сигнатурах), без зависимости на доменные реализации. +\* Только контракты и типы Domain (интерфейсы сервисов, `VO`/`Enum`/локальные для владельца контекста доменные `DTO` в сигнатурах), без зависимости на доменные реализации. ## Расположение diff --git a/src/Deptrac/CrossModuleDomainRule.php b/src/Deptrac/CrossModuleDomainRule.php index 0800dc3..41afb2d 100644 --- a/src/Deptrac/CrossModuleDomainRule.php +++ b/src/Deptrac/CrossModuleDomainRule.php @@ -17,7 +17,8 @@ * * All other cross-module dependencies are violations, regardless of layer. * Shared data types (ValueObject, Enum, Dto) are excluded — safe to use across modules. - * Domain DTOs are shared only when scoped to a concrete Domain Service. + * Domain DTOs are shared only when they are scoped to an owning Domain context, + * not placed in a common Domain\Dto directory. * Domain\Entity → foreign Domain\Entity is excluded — Doctrine ORM relations require class references. * Infrastructure\*Criteria\Mapper → foreign Domain\Entity is excluded — query building requires entity references. * @@ -141,7 +142,7 @@ private function parseModuleClass(string $className): ?array /** * ValueObject, Enum and Dto are shared data types — safe for cross-module usage. - * Domain DTOs must be service-local: Domain\Service\...Dto. + * Domain DTOs must not be placed in a common Domain\Dto directory. */ private function isSharedDataType(string $layer, string $path): bool { @@ -157,7 +158,7 @@ private function isSharedDataType(string $layer, string $path): bool return true; } - return str_starts_with($path, 'Service\\'); + return str_starts_with($path, 'Dto\\') === false; } private function dependentLayerName(ProcessEvent $event): string diff --git a/src/Sniffs/Structure/DtoStructureSniff.php b/src/Sniffs/Structure/DtoStructureSniff.php index eb713d4..85e9a87 100644 --- a/src/Sniffs/Structure/DtoStructureSniff.php +++ b/src/Sniffs/Structure/DtoStructureSniff.php @@ -63,8 +63,7 @@ private function assertNoDomainDtoDirectory(File $phpcsFile, int $classPtr): voi $phpcsFile->addError( 'Classes must not be placed inside Domain/Dto.' - . ' Domain DTOs must be placed inside a Service context:' - . ' Domain/Service/{ServiceName}/{Name}Dto.' . self::DOC_REF, + . ' Domain DTOs must be placed next to their owning Domain context.' . self::DOC_REF, $classPtr, self::ERROR_DOMAIN_DTO_PATH, ); diff --git a/tests/Deptrac/CrossModuleDomainRuleTest.php b/tests/Deptrac/CrossModuleDomainRuleTest.php index 3425ed1..dd0e447 100644 --- a/tests/Deptrac/CrossModuleDomainRuleTest.php +++ b/tests/Deptrac/CrossModuleDomainRuleTest.php @@ -389,6 +389,14 @@ public function testInfrastructureUsesForeignDomainDto(): void ); } + public function testInfrastructureUsesForeignDomainDtoOutsideServiceContext(): void + { + $this->assertNoViolation( + 'App\Common\Module\DynamicLoop\Infrastructure\Service\SomeService', + 'App\Common\Module\ChainExecution\Domain\Calculator\Pricing\PricingResultDto', + ); + } + public function testInfrastructureUsesForeignDomainDtoFromCommonDtoDirectory(): void { $this->assertViolation( diff --git a/tests/fixtures.php b/tests/fixtures.php index c1a4cff..884b602 100644 --- a/tests/fixtures.php +++ b/tests/fixtures.php @@ -327,12 +327,18 @@ ], 'warnings' => [], ], - // DtoStructureSniff — Domain DTO in Domain/Service/{GroupName}/ — correct path + // DtoStructureSniff — Domain DTO in Domain/Service/{GroupName}/ — example of a correct contextual path [ 'file' => __DIR__ . '/fixtures/src/Module/Example/Domain/Service/Payment/InitPaymentResultDto.inc', 'errors' => [], 'warnings' => [], ], + // DtoStructureSniff — Domain DTO outside Domain/Dto/ — correct contextual path + [ + 'file' => __DIR__ . '/fixtures/src/Module/Example/Domain/Calculator/Pricing/PricingResultDto.inc', + 'errors' => [], + 'warnings' => [], + ], // ServiceStructureSniff — Mapper class in Service/ directory — valid per convention [ 'file' => __DIR__ . '/fixtures/src/Module/Example/Infrastructure/Service/PublicAgentSession/SessionPayloadMapper.inc', diff --git a/tests/fixtures/src/Module/Example/Domain/Calculator/Pricing/PricingResultDto.inc b/tests/fixtures/src/Module/Example/Domain/Calculator/Pricing/PricingResultDto.inc new file mode 100644 index 0000000..c572b3b --- /dev/null +++ b/tests/fixtures/src/Module/Example/Domain/Calculator/Pricing/PricingResultDto.inc @@ -0,0 +1,12 @@ + Date: Thu, 4 Jun 2026 11:21:49 +0700 Subject: [PATCH 05/16] fix: remove domain dto path note from deptrac --- config/deptrac/README.md | 2 +- config/deptrac/README.ru.md | 2 +- config/deptrac/README.zh.md | 2 +- config/deptrac/depfile.yaml | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/config/deptrac/README.md b/config/deptrac/README.md index 52b6dec..55a82b2 100644 --- a/config/deptrac/README.md +++ b/config/deptrac/README.md @@ -30,7 +30,7 @@ Deptrac catches such violations **deterministically** — in CI, on every commit | `Domain` | Entities, VOs, repository interfaces | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Domain enum (closed) | -| `DomainDto` | Domain DTOs; `Domain\Dto\*` is forbidden (→ Domain, VO, Enum) | +| `DomainDto` | Domain DTOs (→ Domain, VO, Enum) | | `Application` | Use cases, services | | `ApplicationDto` | Application DTOs (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.ru.md b/config/deptrac/README.ru.md index 9fd2e13..0dc2aa8 100644 --- a/config/deptrac/README.ru.md +++ b/config/deptrac/README.ru.md @@ -30,7 +30,7 @@ Deptrac ловит такие нарушения **детерминирован | `Domain` | Сущности, VO, интерфейсы репозиториев | | `DomainVo` | Value Objects (→ Domain, Enum) | | `DomainEnum` | Enum домена (замкнут) | -| `DomainDto` | DTO домена; `Domain\Dto\*` запрещён (→ Domain, VO, Enum) | +| `DomainDto` | DTO домена (→ Domain, VO, Enum) | | `Application` | Use cases, сервисы | | `ApplicationDto` | DTO приложения (→ DTO, Enum) | | `ApplicationCommand` | Command (CQRS) | diff --git a/config/deptrac/README.zh.md b/config/deptrac/README.zh.md index 335f1c2..04cdd39 100644 --- a/config/deptrac/README.zh.md +++ b/config/deptrac/README.zh.md @@ -30,7 +30,7 @@ Deptrac **确定性地**捕获此类违规——在 CI 中,每次提交时都 | `Domain` | 实体、值对象、仓库接口 | | `DomainVo` | 值对象(→ Domain, Enum) | | `DomainEnum` | 领域枚举(封闭) | -| `DomainDto` | 领域 DTO;禁止 `Domain\Dto\*`(→ Domain, VO, Enum) | +| `DomainDto` | 领域 DTO(→ Domain, VO, Enum) | | `Application` | 用例、服务 | | `ApplicationDto` | 应用 DTO(→ DTO, Enum) | | `ApplicationCommand` | 命令(CQRS) | diff --git a/config/deptrac/depfile.yaml b/config/deptrac/depfile.yaml index b5357e3..52d4f6f 100644 --- a/config/deptrac/depfile.yaml +++ b/config/deptrac/depfile.yaml @@ -121,8 +121,6 @@ deptrac: value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\Enum\\.* - name: DomainDto - # Keep this collector broad to classify all Domain DTO-like classes. - # Invalid common directory is enforced by DtoStructureSniff: Domain\Dto\* is forbidden. collectors: - type: classLike value: ^(?:[A-Za-z_]+\\)?Common\\Module\\.*\\Domain\\.*Dto$ From 5b9cc3dbbced53a6ef76de0ef1dce0b874c677df Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:35:40 +0700 Subject: [PATCH 06/16] docs: clarify domain dto placement --- docs/conventions/core-patterns/dto.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index 3f7db6f..3404890 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -28,7 +28,8 @@ description: Правила создания и использования об [Query DTO](../layers/presentation/query-dto.md), [Response DTO](../layers/presentation/response-dto.md). - Именование: суффикс `Dto`. Контекст — в имени/namespace (`RequestDto`, `ResponseDto`, `ResultDto`, `...Dto`). -- DTO в `Domain` — исключение для входа/выхода конкретного доменного контекста; любые классы в `Domain\Dto\*` запрещены. +- В `Domain` не создаём общий каталог `Dto`; namespace `Domain\Dto\*` запрещён. +- Если доменной операции нужен DTO, размещаем его рядом с классом или группой, которые его используют. ## Зависимости @@ -80,10 +81,10 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Infrastructure\Component\{Component}\Dto\{Name}Dto ``` -- DTO доменного уровня размещаем рядом с владельцем контекста: +- DTO доменного уровня размещаем рядом с доменным артефактом или группой, для которых он нужен: ``` -{ProjectName}\Common\Module\{ModuleName}\Domain\{Context}\{GroupName?}\{Name}Dto +{ProjectName}\Common\Module\{ModuleName}\Domain\{ArtifactType}\{GroupName?}\{Name}Dto ``` Например, для результата доменного сервиса: @@ -92,8 +93,8 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Domain\Service\{ServiceName}\{Name}Dto ``` -`Domain\Dto\*` не используем: у доменного DTO всегда есть локальный владелец (`Service`, `Calculator`, `Specification` и т.п.). Если DTO начинает -переиспользоваться за пределами одного контекста, заменяем его на `VO` или выносим контракт на уровень `Application`. +`Domain\Dto\*` не используем. Примеры `{ArtifactType}`: `Service`, `Calculator`, `Specification`. Если DTO начинает +переиспользоваться за пределами одной группы, заменяем его на `VO` или выносим контракт на уровень `Application`. ## Как используем @@ -106,7 +107,7 @@ description: Правила создания и использования об - Для передачи данных между слоями и границами подсистем. - В Application разрешено общее DTO, если один и тот же набор данных переиспользуется в нескольких Use Case. -- В Domain используем DTO только когда нужно принять/вернуть данные конкретного доменного контекста. Если DTO начинает «гулять» по коду, +- В Domain используем DTO только для компактного входа/выхода конкретной доменной операции. Если DTO начинает «гулять» по коду, рассмотрите перенос в Value Object (VO). ## Пример @@ -198,7 +199,7 @@ final readonly class InferenceRequestDto - [ ] Нет зависимостей на сервисы/репозитории/компоненты и внешнее окружение. - [ ] Коллекции типизированы через PHPDoc (`@var FooDto[]`). - [ ] Название и namespace отражают контекст использования (`Request/Response/Result` при необходимости). -- [ ] Domain DTO лежит рядом с владельцем контекста (`Domain\{Context}\...`); классов в `Domain\Dto\*` нет. +- [ ] Domain DTO лежит рядом с доменным артефактом или группой (`Domain\{ArtifactType}\...`); классов в `Domain\Dto\*` нет. - [ ] Денежные/точные величины представлены `numeric-string` или VO. - [ ] DTO используется на границах слоя, а не подменяет доменные сущности. - [ ] Для Presentation transport DTO дополнительно соблюдены профильные presentation-conventions. From 83c15a61fd67980c3804d769b0ebeb4589dbe594 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:39:49 +0700 Subject: [PATCH 07/16] docs: streamline domain dto guidance --- docs/conventions/core-patterns/dto.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index 3404890..0196a9b 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -28,7 +28,6 @@ description: Правила создания и использования об [Query DTO](../layers/presentation/query-dto.md), [Response DTO](../layers/presentation/response-dto.md). - Именование: суффикс `Dto`. Контекст — в имени/namespace (`RequestDto`, `ResponseDto`, `ResultDto`, `...Dto`). -- В `Domain` не создаём общий каталог `Dto`; namespace `Domain\Dto\*` запрещён. - Если доменной операции нужен DTO, размещаем его рядом с классом или группой, которые его используют. ## Зависимости From dd0f7d699cc21ea19b38dedecb34932f9e164844 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:41:07 +0700 Subject: [PATCH 08/16] docs: rename domain dto path placeholder --- docs/conventions/core-patterns/dto.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index 0196a9b..ece59e1 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -80,10 +80,10 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Infrastructure\Component\{Component}\Dto\{Name}Dto ``` -- DTO доменного уровня размещаем рядом с доменным артефактом или группой, для которых он нужен: +- DTO доменного уровня размещаем рядом с доменным объектом или группой, для которых он нужен: ``` -{ProjectName}\Common\Module\{ModuleName}\Domain\{ArtifactType}\{GroupName?}\{Name}Dto +{ProjectName}\Common\Module\{ModuleName}\Domain\{DomainObjectType}\{GroupName?}\{Name}Dto ``` Например, для результата доменного сервиса: @@ -92,7 +92,7 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Domain\Service\{ServiceName}\{Name}Dto ``` -`Domain\Dto\*` не используем. Примеры `{ArtifactType}`: `Service`, `Calculator`, `Specification`. Если DTO начинает +`Domain\Dto\*` не используем. `{DomainObjectType}` — тип доменного объекта: `Service`, `Calculator`, `Specification` и т.п. Если DTO начинает переиспользоваться за пределами одной группы, заменяем его на `VO` или выносим контракт на уровень `Application`. ## Как используем @@ -198,7 +198,7 @@ final readonly class InferenceRequestDto - [ ] Нет зависимостей на сервисы/репозитории/компоненты и внешнее окружение. - [ ] Коллекции типизированы через PHPDoc (`@var FooDto[]`). - [ ] Название и namespace отражают контекст использования (`Request/Response/Result` при необходимости). -- [ ] Domain DTO лежит рядом с доменным артефактом или группой (`Domain\{ArtifactType}\...`); классов в `Domain\Dto\*` нет. +- [ ] Domain DTO лежит рядом с доменным объектом или группой (`Domain\{DomainObjectType}\...`); классов в `Domain\Dto\*` нет. - [ ] Денежные/точные величины представлены `numeric-string` или VO. - [ ] DTO используется на границах слоя, а не подменяет доменные сущности. - [ ] Для Presentation transport DTO дополнительно соблюдены профильные presentation-conventions. From 86281388def6f924d757396ad3457c247f4f97ad Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:41:57 +0700 Subject: [PATCH 09/16] docs: clarify common domain dto directory --- docs/conventions/core-patterns/dto.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index ece59e1..5991b73 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -92,7 +92,7 @@ description: Правила создания и использования об {ProjectName}\Common\Module\{ModuleName}\Domain\Service\{ServiceName}\{Name}Dto ``` -`Domain\Dto\*` не используем. `{DomainObjectType}` — тип доменного объекта: `Service`, `Calculator`, `Specification` и т.п. Если DTO начинает +Общие `Domain\Dto\*` не используем. `{DomainObjectType}` — тип доменного объекта: `Service`, `Calculator`, `Specification` и т.п. Если DTO начинает переиспользоваться за пределами одной группы, заменяем его на `VO` или выносим контракт на уровень `Application`. ## Как используем From c5e48bd01d4c58cb1983381e6848eec3b015af45 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:42:37 +0700 Subject: [PATCH 10/16] docs: simplify domain dto reuse guidance --- docs/conventions/core-patterns/dto.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conventions/core-patterns/dto.md b/docs/conventions/core-patterns/dto.md index 5991b73..8500eae 100644 --- a/docs/conventions/core-patterns/dto.md +++ b/docs/conventions/core-patterns/dto.md @@ -93,7 +93,7 @@ description: Правила создания и использования об ``` Общие `Domain\Dto\*` не используем. `{DomainObjectType}` — тип доменного объекта: `Service`, `Calculator`, `Specification` и т.п. Если DTO начинает -переиспользоваться за пределами одной группы, заменяем его на `VO` или выносим контракт на уровень `Application`. +переиспользоваться за пределами одной группы, заменяем его на `VO`. ## Как используем From 44d07342c29b6e960032ebac37572e37910b723d Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:44:49 +0700 Subject: [PATCH 11/16] docs: remove dto placement from factory --- docs/conventions/core-patterns/factory.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/conventions/core-patterns/factory.md b/docs/conventions/core-patterns/factory.md index 9b1c12b..443329c 100644 --- a/docs/conventions/core-patterns/factory.md +++ b/docs/conventions/core-patterns/factory.md @@ -22,8 +22,6 @@ description: Правила создания и использования фа **Разрешено**: - Примитивы (только конфигурационные), Enum, VO своего модуля. -- DTO — в Application/Presentation. В Domain не создаём общие DTO; узкие DTO конкретного доменного контекста допустимы - только по правилам [DTO](dto.md); любые классы в `Domain\Dto\*` запрещены. - Через DI Спецификации, другие фабрики своего модуля. **Запрещено**: From 1b47aad42dc52164e84e3e1af2e5050840225747 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:47:11 +0700 Subject: [PATCH 12/16] docs: link factory return object types --- docs/conventions/core-patterns/factory.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conventions/core-patterns/factory.md b/docs/conventions/core-patterns/factory.md index 443329c..2f36e28 100644 --- a/docs/conventions/core-patterns/factory.md +++ b/docs/conventions/core-patterns/factory.md @@ -11,7 +11,7 @@ description: Правила создания и использования фа ## Общие правила - В названии класса указывается постфикс `Factory`. -- Фабрика возвращает объекты (Entity, VO, DTO). +- Фабрика возвращает объекты ([Entity](../layers/domain/entity.md), [VO](value-object.md), [DTO](dto.md)). - Массивы запрещены, допускаются только строго типизированные коллекции (iterable, Collection) с явным интерфейсом. - Фабрика не должна содержать бизнес-логику домена — только логику создания. - Фабрика не должна изменять состояние базы данных или выполнять I/O-операции. @@ -21,7 +21,7 @@ description: Правила создания и использования фа ## Зависимости **Разрешено**: -- Примитивы (только конфигурационные), Enum, VO своего модуля. +- Примитивы (только конфигурационные), [Enum](enum.md), [VO](value-object.md), [DTO](dto.md) своего модуля. - Через DI Спецификации, другие фабрики своего модуля. **Запрещено**: @@ -41,7 +41,7 @@ description: Правила создания и использования фа {ProjectName}\Common\Module\{ModuleName}\Domain\Factory\{Name}Factory ``` -**Application слой** — фабрики для DTO и объектов use case: +**Application слой** — фабрики для [DTO](dto.md) и объектов use case: ``` {ProjectName}\Common\Module\{ModuleName}\Application\Factory\{Name}Factory From 4c44e8c53b0811acef5c8cff7c285e997504935c Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:49:02 +0700 Subject: [PATCH 13/16] docs: simplify service dto guidance --- docs/conventions/core-patterns/service.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/conventions/core-patterns/service.md b/docs/conventions/core-patterns/service.md index d61626a..1680be6 100644 --- a/docs/conventions/core-patterns/service.md +++ b/docs/conventions/core-patterns/service.md @@ -42,8 +42,7 @@ description: Правила создания и использования се - Используется только внутри слоя **Domain**. - Работает только с объектами домена: `Entity`, `VO`, `Enum`, `UuidInterface`, примитивы. - Для входа/выхода предпочтительны доменные `VO` (включая именованные result/input объекты), если есть инварианты и доменный смысл. -- `DTO` в Domain допустим как технический carrier в узком месте, только рядом с владельцем контекста - (`Domain\{Context}\{GroupName?}\{Name}Dto`), но не как замена доменных типов. Любые классы в `Domain\Dto\*` запрещены. +- `DTO` допустим только как простая структура входа/выхода без инвариантов; правила размещения описаны в [DTO](dto.md). - Может зависеть от: - других сервисов своего модуля; - `Specification`; From 868ec94ebb29e474ee5f05dbe4acadd73ba13d6e Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:50:55 +0700 Subject: [PATCH 14/16] docs: shorten service dto rule --- docs/conventions/core-patterns/service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conventions/core-patterns/service.md b/docs/conventions/core-patterns/service.md index 1680be6..91a9620 100644 --- a/docs/conventions/core-patterns/service.md +++ b/docs/conventions/core-patterns/service.md @@ -42,7 +42,7 @@ description: Правила создания и использования се - Используется только внутри слоя **Domain**. - Работает только с объектами домена: `Entity`, `VO`, `Enum`, `UuidInterface`, примитивы. - Для входа/выхода предпочтительны доменные `VO` (включая именованные result/input объекты), если есть инварианты и доменный смысл. -- `DTO` допустим только как простая структура входа/выхода без инвариантов; правила размещения описаны в [DTO](dto.md). +- [DTO](dto.md) допустим только как простая структура входа/выхода. - Может зависеть от: - других сервисов своего модуля; - `Specification`; From 556792ab7ac8c5888b60e4bae70baa2dc63f3397 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:51:49 +0700 Subject: [PATCH 15/16] docs: link domain service related types --- docs/conventions/core-patterns/service.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/conventions/core-patterns/service.md b/docs/conventions/core-patterns/service.md index 91a9620..2b18f09 100644 --- a/docs/conventions/core-patterns/service.md +++ b/docs/conventions/core-patterns/service.md @@ -40,14 +40,14 @@ description: Правила создания и использования се ## Общие правила - Используется только внутри слоя **Domain**. -- Работает только с объектами домена: `Entity`, `VO`, `Enum`, `UuidInterface`, примитивы. -- Для входа/выхода предпочтительны доменные `VO` (включая именованные result/input объекты), если есть инварианты и доменный смысл. +- Работает только с объектами домена: [Entity](../layers/domain/entity.md), [VO](value-object.md), [Enum](enum.md), `UuidInterface`, примитивы. +- Для входа/выхода предпочтительны доменные [VO](value-object.md) (включая именованные result/input объекты), если есть инварианты и доменный смысл. - [DTO](dto.md) допустим только как простая структура входа/выхода. - Может зависеть от: - других сервисов своего модуля; - - `Specification`; - - `Calculator`; - - репозиториев; + - [Specification](../layers/domain/specification.md); + - [Calculator](../layers/domain/calculator.md); + - [Repository](../layers/domain/repository.md); - общих компонентов. - ❗ **Запрещено** зависеть от внешней инфраструктуры. From 475a83f00b3e169ec5164c541b76e0ca3d6e2982 Mon Sep 17 00:00:00 2001 From: Dmitry Prikotov Date: Thu, 4 Jun 2026 11:54:42 +0700 Subject: [PATCH 16/16] docs: simplify layer type references --- docs/conventions/layers/layers.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/conventions/layers/layers.md b/docs/conventions/layers/layers.md index e0ad58a..0490718 100644 --- a/docs/conventions/layers/layers.md +++ b/docs/conventions/layers/layers.md @@ -14,7 +14,7 @@ description: Правила зависимостей между слоями а - Зависимости направлены **только внутрь**, к центру - Внутренние слои не зависят от внешних -- Внешние слои зависят от внутренних через контракты и согласованные типы (`interface`/`DTO`/`VO`/`Enum`) в рамках разрешённых правил +- Внешние слои зависят от внутренних через контракты (`interface`) и согласованные типы ([DTO](../core-patterns/dto.md), [VO](../core-patterns/value-object.md), [Enum](../core-patterns/enum.md)) в рамках разрешённых правил - DI-контейнер связывает интерфейсы с реализациями на уровне конфигурации ## Диаграмма зависимостей @@ -46,8 +46,8 @@ flowchart TB | Слой | Назначение | Зависимости | |------|------------|-------------| -| **Domain** | Бизнес-логика, сущности, Value Object, интерфейсы репозиториев | Нет | -| **Application** | Use Cases, оркестрация, DTO | Domain | +| **Domain** | Бизнес-логика, [Entity](domain/entity.md), [VO](../core-patterns/value-object.md), интерфейсы [Repository](domain/repository.md) | Нет | +| **Application** | Use Cases, оркестрация, [DTO](../core-patterns/dto.md) | Domain | | **Infrastructure** | Реализация репозиториев, кэш, персистентность | Domain (контракты и типы) | | **Integration** | Внешние API, события, межмодульное взаимодействие | Application, Domain (контракты и типы) | | **Presentation** | Web, API, Console, Blog — точки входа | Application | @@ -63,16 +63,16 @@ Domain слой не зависит ни от кого: ### Application → Domain Application зависит только от Domain: -- Вызывает методы сущностей и Value Object -- Использует интерфейсы репозиториев из Domain -- Использует спецификации и сервисы из Domain -- Важно: `Application DTO` не должны зависеть от `Domain` и остаются только в рамках `Application`. +- Вызывает методы [Entity](domain/entity.md) и [VO](../core-patterns/value-object.md) +- Использует интерфейсы [Repository](domain/repository.md) из Domain +- Использует [Specification](domain/specification.md) и [Service](../core-patterns/service.md) из Domain +- Важно: [Application DTO](../core-patterns/dto.md) не должны зависеть от `Domain` и остаются только в рамках `Application`. ### Infrastructure → Domain Infrastructure реализует интерфейсы Domain: -- Реализует `RepositoryInterface` из Domain -- Может использовать доменные типы (`VO`, `Enum`, локальные для владельца контекста доменные `DTO`) в сигнатурах и маппинге +- Реализует доменный `RepositoryInterface` ([Repository](domain/repository.md)) +- Может использовать доменные типы ([VO](../core-patterns/value-object.md), [Enum](../core-patterns/enum.md), [DTO](../core-patterns/dto.md)) в сигнатурах и маппинге - Подключается через DI-контейнер - Не используется напрямую из Application @@ -80,8 +80,8 @@ Infrastructure реализует интерфейсы Domain: Integration вызывает Application чужого модуля: - Обрабатывает внешние события и инициирует соответствующие Use Cases. -- Реализует интеграции через интерфейсы сервисов своего Domain. -- Может использовать доменные типы в сигнатурах контрактов (`VO`, `Enum`, локальные для владельца контекста доменные `DTO`). +- Реализует интеграции через интерфейсы доменных [Service](../core-patterns/service.md). +- Может использовать доменные типы в сигнатурах контрактов ([VO](../core-patterns/value-object.md), [Enum](../core-patterns/enum.md), [DTO](../core-patterns/dto.md)). - Не зависит от слоя Infrastructure. - Формирует антикоррупционный слой (ACL) на границе с внешними системами. @@ -102,7 +102,7 @@ Presentation зависит только от Application: | **Integration** | ✅* | ✅ | ❌ | — | ❌ | | **Presentation** | ❌ | ✅ | ❌ | ❌ | — | -\* Только контракты и типы Domain (интерфейсы сервисов, `VO`/`Enum`/локальные для владельца контекста доменные `DTO` в сигнатурах), без зависимости на доменные реализации. +\* Только контракты и типы Domain (интерфейсы доменных [Service](../core-patterns/service.md), [VO](../core-patterns/value-object.md), [Enum](../core-patterns/enum.md), [DTO](../core-patterns/dto.md) в сигнатурах), без зависимости на доменные реализации. ## Расположение