Comandos¶
Um command é a unidade arquitetural de trabalho executavel no MIDDAG. Ele carrega o payload necessário para que um handler realize uma ação discreta.
O bus resolve o handler automaticamente por convenção de nome, sem middleware nem hooks.
Criar um command¶
Um command é um value object imutavel que estende base\command. Deve implementar to_payload() e from_payload() para suportar serialização (necessária para dispatch async).
namespace local_meuplugin\extensions\catalogo\item;
use local_middag\base\command;
final class publish_item_command extends command
{
public function __construct(
public readonly int $item_id,
public readonly int $user_id,
) {}
public function to_payload(): array
{
return [
'item_id' => $this->item_id,
'user_id' => $this->user_id,
];
}
public static function from_payload(array $payload): static
{
return new static(
item_id: (int) ($payload['item_id'] ?? 0),
user_id: (int) ($payload['user_id'] ?? 0),
);
}
}
Criar um handler¶
O handler é resolvido automaticamente por convenção: o bus concatena _handler ao FQCN do command. O handler deve estar no mesmo namespace e implementar __invoke().
namespace local_meuplugin\extensions\catalogo\item;
use local_middag\base\command_handler;
final class publish_item_command_handler extends command_handler
{
public function __construct(
private readonly item_service_interface $items,
) {}
public function __invoke(publish_item_command $command): void
{
$this->items->publish($command->item_id, $command->user_id);
}
}
O handler é resolvido do container DI, portanto pode receber qualquer dependência injetavel no construtor.
Convencao de nome
publish_item_command → publish_item_command_handler (mesmo namespace, sufixo _handler).
Dispatch síncrono¶
Resolve o handler e executa imediatamente:
use local_middag\facade\command_bus;
command_bus::handle(new publish_item_command(
item_id: 42,
user_id: 1,
));
Dispatch assíncrono¶
Serializa o command via to_payload() e enfileira como Moodle adhoc task. O handler sera executado pelo cron:
use local_middag\facade\command_bus;
command_bus::dispatch_async(new publish_item_command(
item_id: 42,
user_id: 1,
));
O adhoc task reconstroi o command via from_payload() e executa o handler normalmente.
Ciclo de vida¶
sequenceDiagram
participant C as Controller/Service
participant B as command_bus
participant H as Handler
C ->> B: handle(command)
B ->> B: Resolve handler (FQCN + _handler)
B ->> H: __invoke(command)
H ->> H: Executa lógica
Para dispatch async, o bus serializa e delega ao cron:
sequenceDiagram
participant C as Controller/Service
participant B as command_bus
participant Q as adhoc_task (cron)
participant H as Handler
C ->> B: dispatch_async(command)
B ->> Q: Serializa (to_payload) e enfileira
Q ->> B: Reconstroi (from_payload) e handle()
B ->> H: __invoke(command)
Quando usar jobs¶
Se você precisa de retry, correlação ou observabilidade, não use dispatch_async diretamente. Use o job_service:
use local_middag\facade\job_service;
$job = job_service::dispatch(new \local_middag\framework\domain\job\job_dto(
command: new publish_item_command(42, 1),
// ... opções de retry, correlation_id, etc.
));
O job governa a execução, registra tentativas e permite acompanhamento.
| Cenario | Mecanismo |
|---|---|
| Execução imediata | command_bus::handle() |
| Background simples | command_bus::dispatch_async() |
| Retry, correlação, observabilidade | job_service::dispatch() |
Regras¶
- Um command carrega apenas dados (value object). Nenhuma lógica de negocio.
- Um handler realiza a ação. Pode depender de services, repositories, etc.
- O handler deve estar no mesmo namespace que o command.
to_payload()efrom_payload()são obrigatorios (contractcommand_interface).- Estender
base\commandebase\command_handleré recomendado, mas não estritamente obrigatório para o bus funcionar.