5 min readCalm pace · scan the outline anytime

Use cases and DTOs

First-level UseCase entry points, nested helpers, and Application vs Domain DTO rules.

Use cases and DTOs

Naming conventions

ComponentConventionExample
Use Case[Name]UseCasePlaceOrderUseCase
Application DTO[Name]DTOPlaceOrderDTO
Domain DTO[Name]DTOOrderLineDTO
Port[Name]…InterfaceOrderRepositoryInterface
Entity[Name]EntityOrderEntity

Use Case suffix and folder depth

The UseCase suffix is governed by folder depth under Application/UseCases/.

LocationSuffix required?Example
Directly inside UseCases/{Capability}/Yes*UseCasePlaceOrderUseCase.php
Inside any nested subfolder under {Capability}/NoOrderProcessor.php

Mirror the same {Capability}/ folders for:

  • Domain/Ports/{Capability}/
  • Application/DTO/{Capability}/
  • Outbound ACL ports → Domain/Ports/Acl/
  • Module façades → Domain/Ports/Module/

{Capability} is a documentation placeholder — never a literal Feature/ directory.

Application/UseCases/
└── Order/
    ├── PlaceOrderUseCase.php              ✅ UseCase suffix
    ├── CancelOrderUseCase.php             ✅ UseCase suffix
    └── Processing/                        nested — no suffix required
        ├── OrderProcessor.php
        └── Payment/
            └── CreditCardPaymentHandler.php

Entry-point boundary (strict)

  • Only first-level *UseCase classes may be invoked from UI, CLI, jobs, or admin actions in the same module.
  • Nested helpers are internal. UI must never call them.
  • Other modules must never call this module’s Use Cases. Cross-module work uses ACL or Events only.

Entry method: __invoke

Every first-level Use Case exposes exactly one public entry: __invoke.

RuleDetail
Method nameAlways __invoke — not execute() / handle()
Invocation($useCase)($dto) or $useCase->__invoke($dto)
Nested helpersNot Use Cases; no __invoke requirement
class PlaceOrderUseCase
{
    public function __construct(
        private OrderRepositoryInterface $orders,
        private WarehouseAvailabilityPortInterface $warehouse,
    ) {}

    public function __invoke(PlaceOrderDTO $dto): OrderEntity
    {
        // orchestrate Domain + ports
    }
}

Application DTO vs Domain DTO

LayerPathPurposeTypical flow
ApplicationApplication/DTO/Carry validated UI input into a Use CaseController → Use Case
DomainDomain/DTO/Structured data inside the moduleUse Case ↔ Entity / Port
  • Application DTOs receive external input. Controllers map validated request data into them.
  • Domain DTOs never cross the HTTP/UI boundary as the primary API contract.

Illustrative Ordering examples

  • Application/DTO/Order/PlaceOrderDTO.php — API input when placing an order.
  • Domain/DTO/OrderLineDTO.php — line data while building the order.
  • Domain/DTO/TotalsBreakdownDTO.php — totals consumed by another Use Case in the same module.

Composition inside Application

A first-level Use Case may delegate to nested helpers in the same feature tree. Helpers receive already-injected ports (or are resolved with the same port interfaces). They must not import ORM types or become a second UI entry point.

Next: ports & persistence.

Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft