This post is linked with the detailed article Real-life DDD in an "onionshell" and explains with code examples specific version of a standard CQRS command requests. This customization allows us to handle different types of command requests integrating with bounded contexts.
Our service has two types of API requests: external "usecase" type and "command request" type, that can be external and internal. The second type can be managed differently, depending on itβs goal. It can be a simple external request to handle a command. Another time it can be a complex task with transaction in it. So we should use a different approach for every version of the task according to the configuration pre-set. Either way we create a task request and manage it using a message broker.
We have "usecase" and "request-commands" concept. Last one should be used for any server-to-server or context-to-context interaction.
Request command should be created via CommandRunner (RequestCommandHandlerService), that operates with command handlers and have the following interface:
/**
* Interface CommandRunner
* @package Utility\RequestCommand
*/
interface CommandRunner
{
/**
* @param TaskRequestInterface $taskRequest
* @throws JsonException
*/
public function send(TaskRequestInterface $taskRequest): void;
/**
* @param TaskRequestInterface $taskRequest
* @throws CommandRequestException
*/
public function run(TaskRequestInterface $taskRequest): void;
}
All of handle pairs should be determined in configuration config.php/requestCommand.php
. For example,
If request command is going to be called externally you are going to need alias for this call:
/*
|------------------------------------------------------------
| Handlers Aliases
|------------------------------------------------------------
*/
'requestAliases' => [
'makeMessage' => MakeMessageTaskRequest::class,
'createTeam' => CreateTeamTaskRequest::class,
],
That's way we can add multi-purpose Handler-Request pair to 'requestCommand'. It can be simple peer-to-peer:
AddScenarioStatisticTaskRequest::class => AddScenarioStatisticHandler::class
Or transaction mode (all handlers are going to be committed in one transaction):
MakeMessageTaskRequest::class => [
MakeMessageTaskRequest::class => MakeMessageHandler::class,
UpdateSessionLastSeenTaskRequest::class => UpdateSessionLastSeenHandler::class
],
Pay attention here: you should create only one request via command handler, for example 'MakeMessageTaskRequest' and all other requests are going to be created automatically according with your chain of requests/handlers.
After that you can create specific TaskRequest with entity-specific DTO in Infrastructure/TaskRequest and Handler for it in Application/CommandHandler.
TaskRequest example:
Yep, it's going to be DeactivateUserTariffPlanDTO instead of RequestDTO immediately after we update PHP version to 8.0 :)
CommandHandler example (it can be in any context):
As you can see, handler 'process' method should call Application Service accordingly with procedure, that you need. Add Middleware Validators for each of them accordingly in Handler.
Handler without Transactions should implement RequestCommandHandler
interface and Transaction Handlers should implement RequestCommandHandler,TransactionRequestCommandHandler
interfaces.
There are two types of transaction handlers: "insiders" and "closing". Insiders (implements InsiderTransactionRequestCommandHandler
interface as well) should create DTO for the next transaction request in the process()
method via creating
$this->nextTransactionRequestDTO
and return it in the following way:
/**
* @return bool
*/
public function isInTransaction(): bool
{
return true;
}
/**
* @return RequestDTO|null
*/
public function getNextTransactionRequestDTO(): RequestDTO
{
return $this->nextTransactionRequestDTO;
}
Closing one closes transaction (the last request on the list), so it should only implement isInTransaction
method:
/**
* @return bool
*/
public function isInTransaction(): bool
{
return false;
}
I am planning to create two different strategies for "insiders" and "closing" request, but it haven't done yet.
TaskHandler
is going to handle all inner processes, such as setup transactions decorator service with UnitOfWork, obtain and handle request-commands pairs and request data from the configuration service, handle low-level exceptions and so on.
All we have left to do is to start listen "request-command" queue via message broker (we use RabbitMQ, but it's going to work with any of brokers, anyway).
Example for DeactivateUserTariffPlan:
From any of bounded contexts services inside API:
$this->commandRunner->send(new DeactivateUserTariffPlanTaskRequest(new DeactivateUserTariffPlanDTO(['userId' => 1])));
From external services (for example, API Gateway):
Send json data to the "request-command" queue:
[
'userId' => 1
]
Top comments (0)