title: Conceito: Command description: O Command como unidade de trabalho serializável e independente do mecanismo de execução.
Command¶
O Command representa a intenção de uma ação a ser realizada pelo framework. É uma unidade de trabalho independente, serializável e que separa "o que deve ser feito" de "como e quando deve ser executado".
O que é¶
É um objeto (DTO) que carrega todos os dados necessários (o payload) para que um Handler específico execute uma operação de negócio.
Um Command não contém lógica de execução; ele contém apenas os parâmetros da ação (ex: item_id, user_id, new_status). O Handler, registrado no Container de DI, é quem possui o conhecimento técnico para processar o Command.
Por que existe¶
Em sistemas baseados em eventos e arquiteturas de filas, o Command resolve três problemas fundamentais: 1. Independência de Runtime: O mesmo Command pode ser executado de forma síncrona (no mesmo request) ou assíncrona (via Jobs e Cron). 2. Facilidade de Teste: É simples testar a lógica de um Handler isoladamente, passando um objeto Command predefinido. 3. Serialização: O Command pode ser transformado em uma string (JSON) para ser armazenado no banco de dados e executado "depois", preservando o contexto original do request.
O uso de Commands evita o acoplamento excessivo de lógica de negócio dentro de scheduled_tasks ou adhoc_tasks do Moodle.
Como se relaciona com outros conceitos¶
- Command vs Job: O Command é a instrução; o Job é o registro de execução e governança. Nem todo Command precisa abrir um Job (se for executado de forma síncrona).
- Command vs Signal: O Signal informa que algo aconteceu (passado); o Command ordena que algo seja feito (futuro). Frequentemente, um listener de um Signal decide despachar um Command.
- Command vs Handler: O Command é o "papel da receita"; o Handler é o "cozinheiro" (o service resolvido via DI).
Decisões de design¶
- Payload Simples: O payload de um Command deve conter apenas dados primitivos ou IDs, facilitando a serialização segura para o banco de dados. Evite colocar instâncias de objetos complexas ou conexões de banco dentro do Command.
- Idempotência: Handlers de Command devem ser desenhados para serem idempotentes (executar a mesma ação duas vezes não deve causar inconsistência). Isso protege o sistema contra duplicação em caso de retries automáticos.
- Registry via Container: Diferente de simples arquivos, cada Command Handler é registrado e resolvido pelo Container, permitindo que ele receba todas as dependências necessárias via Injeção de Dependência.
O que não é¶
- Não é um Formalismo para Tudo: Não é necessário criar um Command para cada método de um service. Use Commands apenas quando houver ganho real em reuso, adiamento, isolamento ou governança.
- Não é uma Substituição para Services: O Handler do Command é um service. O Command é apenas o envelope do input dessa lógica.
- Não é Orientado a Dados (DAO): O Command representa uma Ação, não apenas um registro de banco.
Perspectiva para builders de extensions¶
Ao desenvolver uma extension:
1. Modele Casos de Uso Adiados: Se sua funcionalidade envolve envio de e-mails, integração com APIs externas ou processamento de lotes, crie um Command.
2. Identifique seu Handler: O Handler deve viver em {extension}/command/ e herdar da classe base apropriada para ser auto-descoberto pelo framework.
3. Pense em Retry: Se o seu Handler falhar, o framework usará o Job associado para tentar novamente. Garanta que sua lógica suporte essa re-execução (verifique se a ação já não foi realizada).
Exemplo ilustrativo¶
Estrutura de um Command para enviar um Webhook:
// 1. O Objeto Command (Payload)
namespace local_middag\extensions\webhooks\command;
final class send_webhook_command {
public function __construct(
public readonly int $id,
public readonly string $url,
public readonly array $data
) {}
}
// 2. O Handler (Lógica de Execução)
final class send_webhook_command_handler {
public function __construct(
private http_client_interface $http // Injetado via DI
) {}
public function handle(send_webhook_command $command): void {
$this->http->post($command->url, $command->data);
}
}