Pular para conteúdo

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);
    }
}

Referências