| name | use-case |
| description | Guide for implementing CQRS use cases in the Application layer. Use when creating Commands, Queries, Handlers, and their tests. |
| allowed-tools | Read, Write, Edit, Glob, Grep |
Use Case Implementation Guide
This Skill provides patterns for implementing CQRS use cases.
When to Use
- Creating a new Command (write operation)
- Creating a new Query (read operation)
- Implementing Handler logic
- Writing integration tests for use cases
CQRS Pattern
Commands (Write Operations)
- Modify state
- May or may not return Output
- Input implements
Command interface
- Handler implements
CommandHandler interface
Queries (Read Operations)
- Never modify state
- Always return Output
- Input implements
Query interface
- Handler implements
QueryHandler interface
Directory Structure
src/{BoundedContext}/Application/{Aggregate}/{UseCase}/
├── Input.php # Command or Query
├── Handler.php # Business logic
└── Output.php # Optional for Commands, required for Queries
Input Pattern (Command)
namespace Dairectiv\{Context}\Application\{Aggregate}\{UseCase};
use Dairectiv\SharedKernel\Application\Command\Command;
final readonly class Input implements Command
{
public function __construct(
public string $name,
public string $description,
) {}
}
Input Pattern (Query)
namespace Dairectiv\{Context}\Application\{Aggregate}\{UseCase};
use Dairectiv\SharedKernel\Application\Query\Query;
final readonly class Input implements Query
{
public function __construct(
public string $id,
) {}
}
Handler Pattern
namespace Dairectiv\{Context}\Application\{Aggregate}\{UseCase};
use Dairectiv\SharedKernel\Application\Command\CommandHandler;
final readonly class Handler implements CommandHandler
{
public function __construct(
private {Aggregate}Repository $repository,
// Other dependencies
) {}
public function __invoke(Input $input): Output
{
$id = {Aggregate}Id::fromString($input->id);
$aggregate = $this->repository->get{Aggregate}ById($id);
$aggregate->doSomething($input->value);
$this->repository->save();
();
}
}
Output Pattern
final readonly class Output
{
public function __construct(
public Rule $rule,
) {}
}
PHPStan Rules (UseCaseRule)
The project enforces these conventions:
- Handler must implement
QueryHandler or CommandHandler
- Input must be in same namespace as Handler
__invoke must have exactly one parameter named input
- Input must implement
Command or Query interface
- QueryHandler must return an Output (not void)
Testing Pattern
#[Group('integration')]
#[Group('{bounded-context}')]
#[Group('use-case')]
final class {UseCase}Test extends IntegrationTestCase
{
public function testItShouldExecuteUseCase(): void
{
$existingEntity = $this->createEntity();
$output = $this->execute(new Input($existingEntity->id));
self::assertDomainEventHasBeenDispatched({Event}::class);
self::assertInstanceOf(Output::class, $output);
self::assertSame($expectedValue, $output->entity->value);
$persisted = $this->findEntity(Entity::, [ => ], : );
::(, ->value);
}
{
->({Entity}::);
->( ());
}
}
Using DataProviders
public static function provideTestCases(): iterable
{
yield 'case one' => ['input' => 'value1', 'expected' => 'result1'];
yield 'case two' => ['input' => 'value2', 'expected' => 'result2'];
}
#[DataProvider('provideTestCases')]
public function testItShouldHandleVariousCases(string $input, string $expected): void
{
$output = $this->execute(new Input($input));
self::assertSame($expected, $output->value);
self::assertDomainEventHasBeenDispatched(Event::class);
}
Calling Use Cases
From Tests (IntegrationTestCase)
$output = $this->execute(new DraftRuleInput($name, $description));
$output = $this->fetch(new GetRuleInput($id));
From Controllers
public function __construct(
private CommandBus $commandBus,
private QueryBus $queryBus,
) {}
public function create(Request $request): Response
{
$output = $this->commandBus->execute(new DraftRuleInput(...));
}
public function get(string $id): Response
{
$output = $this->queryBus->fetch(new GetRuleInput($id));
}
Checklist
When creating a use case:
When testing:
Reference Files
api/src/Authoring/Application/Rule/DraftRule/ - Command example
api/src/Authoring/Application/Rule/GetRule/ - Query example
api/tests/Integration/Authoring/Application/Rule/DraftRuleTest.php
api/src/SharedKernel/Application/Command/Command.php
api/src/SharedKernel/Application/Query/Query.php