Pular para conteúdo

Commands, Jobs e Scheduling

Esta página detalha a direção arquitetural esperada pelo ADR-0006.

O objetivo é separar com clareza:

  • command: o que deve ser feito;
  • job: governança da execução quando necessário;
  • schedule: quando um gatilho periódico deve acontecer;
  • scheduled_task / adhoc_task: adapters do runtime do Moodle.

Regra principal

O framework não deve acoplar command diretamente a cron.

O modelo esperado é:

  1. um command representa a unidade de trabalho;
  2. um schedule declara o gatilho periódico daquele trabalho;
  3. um CLI gera ou sincroniza adapters em classes/task/* e entradas em db/tasks.php;
  4. o runtime do Moodle continua executando as tasks;
  5. a lógica principal continua no handler do command ou no orquestrador do framework.

Fluxo esperado

flowchart TB
    Command["command"]
    Schedule["schedule declarativo"]
    CLI["CLI de sync/generate"]
    Task["generated scheduled_task adapter"]
    Moodle["cron do Moodle"]
    Orchestrator["cron_orchestrator ou command_dispatcher"]
    Handler["command handler"]
    Job["job (opcional)"]

    Command --> Schedule
    Schedule --> CLI
    CLI --> Task
    CLI --> db["db/tasks.php"]
    Task --> Moodle
    Moodle --> Orchestrator
    Orchestrator --> Handler
    Orchestrator --> Job

O que o gerador deve fazer

O gerador por CLI deve:

  • localizar commands agendáveis e seus schedules declarados;
  • gerar ou sincronizar adapters em classes/task/*;
  • gerar ou sincronizar as entradas correspondentes em db/tasks.php;
  • preservar naming e previsibilidade dos classnames;
  • evitar mutação dinâmica em runtime;
  • respeitar que o Moodle continua autoridade final sobre customização operacional de cron.

O que o gerador não deve fazer

  • não deve assumir que todo command é periódico;
  • não deve criar task para command sem schedule explícito;
  • não deve sobrescrever automaticamente customizações operacionais do admin;
  • não deve mover regra principal de negócio para a task gerada;
  • não deve recriar um scheduler paralelo ao Moodle.

Estrutura esperada

Command

<?php

namespace local_middag\extensions\ecommerce\command;

final class sync_products_command
{
    public function __construct(
        public readonly int $store_item_id,
    ) {}
}

Schedule declarativo

O schedule é artefato separado do command.

Exemplo educacional:

<?php

namespace local_middag\extensions\ecommerce\schedule;

use local_middag\extensions\ecommerce\command\sync_products_command;

final class sync_products_schedule
{
    public const COMMAND = sync_products_command::class;

    public const TASK_CLASS = 'local_middag\\task\\ecommerce_sync_products';

    public const MINUTE = '*/15';
    public const HOUR = '*';
    public const DAY = '*';
    public const MONTH = '*';
    public const DAYOFWEEK = '*';

    public const BLOCKING = false;
    public const DISABLED_BY_DEFAULT = false;
}

Separação deliberada

O schedule aponta para o command, mas não se confunde com ele. O mesmo command pode ser executado por cron, por adhoc_task, por dispatch() ou até de forma síncrona.

Generated scheduled_task adapter

O adapter gerado em classes/task/* deve ser fino:

<?php

namespace local_middag\task;

use core\task\scheduled_task;
use local_middag\middag;

final class ecommerce_sync_products extends scheduled_task
{
    public function get_name(): string
    {
        return 'E-commerce - Sync products';
    }

    public function execute(): void
    {
        middag::dispatch(
            new \local_middag\extensions\ecommerce\command\sync_products_command(
                store_item_id: 0,
            )
        );
    }
}

O exemplo acima é apenas educacional. Na prática, o adapter pode chamar um cron_orchestrator, um command_dispatcher ou outro serviço de infraestrutura do framework, desde que a regra principal continue fora da task.

Como cada camada deve aplicar

Framework

O core pode declarar schedules próprios e também manter tasks coringa para intervalos comuns.

Exemplo de uso principal:

<?php

final class clean_audit_logs_command
{
}

Exemplo de regra:

  • clean_audit_logs_command tem schedule explícito;
  • o CLI gera classes/task/clean_audit_logs.php;
  • a task delega ao framework;
  • se houver governança forte, o fluxo pode abrir job.

Extension do ecossistema

A extension declara command e schedule. O CLI sincroniza os adapters necessários.

<?php

namespace local_middag\extensions\webhooks\command;

final class flush_pending_webhooks_command
{
}
<?php

namespace local_middag\extensions\webhooks\schedule;

final class flush_pending_webhooks_schedule
{
    public const COMMAND = \local_middag\extensions\webhooks\command\flush_pending_webhooks_command::class;
    public const TASK_CLASS = 'local_middag\\task\\webhooks_flush_pending';
    public const MINUTE = '*/5';
    public const HOUR = '*';
    public const DAY = '*';
    public const MONTH = '*';
    public const DAYOFWEEK = '*';
}

Plugin terceiro

O plugin terceiro pode seguir o mesmo modelo, se o framework abrir a geração/sincronização para fora.

Se não seguir o modelo, continua livre para manter db/tasks.php e tasks próprias do jeito tradicional do Moodle.

<?php

// caminho tradicional do Moodle em plugin terceiro
$tasks = [
    [
        'classname' => \local_partner\task\sync_orders::class,
        'blocking' => 0,
        'minute' => '*/10',
        'hour' => '*',
        'day' => '*',
        'month' => '*',
        'dayofweek' => '*',
    ],
];

Jobs: quando entram

Job não é obrigatório para todo command assíncrono.

Abra job quando houver necessidade de:

  • retry controlado;
  • deduplicação;
  • correlação;
  • vínculo explícito com item, user, course, lote ou sujeito de domínio;
  • observabilidade operacional.

Exemplo educacional:

<?php

final class send_partner_webhook_command
{
    public function __construct(
        public readonly int $item_id,
        public readonly string $endpoint,
    ) {}
}

Esse command pode:

  • rodar sem job, se for simples e descartável;
  • ou abrir job, se precisar de retry, rastreio e correlação.

Papel do orquestrador de cron

Quando existir comportamento dinâmico de cron, o caminho recomendado é um orquestrador do framework, não lógica rica dentro de cada scheduled_task.

Esse orquestrador pode:

  • decidir quais commands disparar;
  • abrir jobs quando necessário;
  • acionar dispatch() quando o gatilho periódico representar uma ocorrência do framework;
  • aplicar regras por extension, licença, configuração, janela e prioridade.

Exemplo educacional:

<?php

final class cron_orchestrator
{
    public function run(string $task_class): void
    {
        // resolve schedules aplicáveis
        // decide qual command disparar
        // decide se abre job
        // delega ao dispatcher ou ao command handler
    }
}

Tasks coringa do core

O core pode oferecer scheduled tasks coringa para intervalos comuns, como:

  • a cada 5 minutos;
  • a cada 1 hora;
  • 2 vezes por dia.

Essas tasks devem:

  • ficar desativadas por padrão;
  • existir como compatibilidade leve para desenvolvedores;
  • permitir registro por hook ou integração simples;
  • não substituir o modelo principal baseado em command + schedule.

Exemplo de hook compatível:

<?php

middag::add_action('middag/cron/every_five_minutes', function (): void {
    // integração leve
});

Caminho auxiliar

Tasks coringa são camada auxiliar de compatibilidade. Elas não devem virar o modelo principal do core para trabalho periódico de negócio.

Autoridade final do cron

Mesmo com geração por CLI:

  • o Moodle continua sendo a autoridade final sobre runtime;
  • o admin continua podendo customizar horários;
  • o framework não deve sobrescrever automaticamente essas customizações em produção.

Especificação esperada do CLI

O CLI de sincronização deve, no mínimo:

  1. localizar schedules declarativos elegíveis;
  2. validar referência ao command alvo;
  3. validar unicidade de TASK_CLASS;
  4. gerar ou atualizar adapter em classes/task/*;
  5. gerar ou atualizar bloco correspondente em db/tasks.php;
  6. preservar comentários ou customizações manuais fora da área gerada, se esse for o modelo adotado;
  7. falhar de forma explícita em conflito de naming ou de schedule.

Regra prática

  • quero trabalho relevante e reutilizável -> command
  • quero governança operacional -> job
  • quero gatilho periódico -> schedule
  • quero execução pelo cron do Moodle -> generated scheduled_task
  • quero compatibilidade leve por intervalo comum -> task coringa do core, se habilitada