Spraxium logoSpraxium

Drivers e operações

O módulo de schedule oferece suporte a drivers plugáveis para ambientes de processo único e distribuídos. Aprenda a usar MemoryDriver, RedisScheduleDriver e a API de runtime de ScheduleService.

Por que os drivers importam

Em um bot de processo único, cada job agendado roda exatamente uma vez porque existe apenas uma instância do scheduler. Quando você escala para vários shards ou processos, o mesmo job cron pode disparar ao mesmo tempo em cada instância, causando execuções duplicadas. O sistema de drivers resolve isso ao introduzir um mecanismo de lock que garante que apenas uma instância adquira o direito de executar determinado job a cada disparo.

Os drivers são plugáveis. Você configura o driver uma vez na config de schedule, e o framework cuida do restante de forma transparente. Os métodos dos seus jobs não precisam saber qual driver está ativo. Isso significa que você pode desenvolver localmente com MemoryDriver e trocar para RedisScheduleDriver em produção sem mudar nenhum código de serviço.

O modelo de lock é otimista. Quando um job dispara, o driver tenta adquirir um lock usando o nome do job como chave. Se o lock for adquirido, o job roda. Se outro processo já estiver com o lock, o job é silenciosamente ignorado naquela instância. O lock tem um TTL configurável que é liberado automaticamente após a janela esperada de execução.

Drivers disponíveis

DriverPackageMecanismo de lockMelhor uso
MemoryDriver@spraxium/scheduleSempre adquire, sem operação realBots de processo único e desenvolvimento local
RedisScheduleDriver@spraxium/scheduleRedis SET NX + Lua releaseProdução com múltiplos shards e múltiplos processos

Configurando drivers

O MemoryDriver padrão não exige nenhuma configuração. Ele sempre adquire o lock, então cada job roda sempre que dispara. Esse é o comportamento correto para bots de processo único.

Em ambientes distribuídos, troque para RedisScheduleDriver, passando as opções de conexão do Redis e um lock TTL opcional. O driver usa SET key NX EX ttl para adquirir locks de forma atômica e um script Lua para liberá-los com segurança, sem condições de corrida.

import { defineSchedule } from '@spraxium/schedule';

export default defineSchedule({
  timezone: 'UTC',
});

API de ScheduleService

O ScheduleService é um provider injetável que oferece controle total, em runtime, sobre os jobs registrados. Injete-o em qualquer serviço, command handler ou controller para pausar, retomar, destruir ou disparar jobs manualmente pelo nome. O serviço é exportado pelo ScheduleModule global, então fica disponível em todos os módulos sem imports explícitos.

Todo método recebe o name do job, conforme definido nas opções do decorator. Se você não definir um nome explícito, o framework usará um nome gerado a partir da classe e do método. Nomes explícitos são fortemente recomendados para qualquer job que você pretenda administrar em runtime.

MétodoRetornoDescrição
pause(name)Sem valor de retornoInterrompe um job em execução para que ele possa ser retomado depois
resume(name)Sem valor de retornoRetoma um job pausado anteriormente
destroy(name)Sem valor de retornoRemove permanentemente um job e seu timer
get(name)JobStatus ou nullRecupera um job registrado pelo nome
getAll()Lista de entradas JobStatusLista todos os jobs registrados com seu estado
runNow(name)Promise sem valorExecuta imediatamente um job fora do cronograma normal

Usando ScheduleService em um command

Um caso de uso prático para ScheduleService é criar comandos administrativos que permitem a donos de guild ou administradores do bot controlar tarefas em segundo plano sem reiniciar o bot. O exemplo abaixo mostra um prefix command handler que pausa ou retoma um job pelo nome. O mesmo padrão funciona com slash commands ou endpoints de dashboard.

src/modules/admin/handlers/schedule-admin.handler.ts
import { Ctx, PrefixArg, PrefixCommandHandler } from '@spraxium/common';
import { ScheduleService } from '@spraxium/schedule';
import type { Message } from 'discord.js';
import { ScheduleAdminCommand } from '../commands/schedule-admin.command';

@PrefixCommandHandler(ScheduleAdminCommand)
export class ScheduleAdminHandler {
  constructor(private readonly scheduleService: ScheduleService) {}

  async handle(
    @Ctx() message: Message,
    @PrefixArg('action') action: string,
    @PrefixArg('jobName') jobName: string,
  ): Promise<void> {
    const job = this.scheduleService.get(jobName);
    if (!job) {
      await message.reply(`Job \`${jobName}\` not found.`);
      return;
    }

    if (action === 'pause') {
      this.scheduleService.pause(jobName);
      await message.reply(`Job \`${jobName}\` paused.`);
    } else if (action === 'resume') {
      this.scheduleService.resume(jobName);
      await message.reply(`Job \`${jobName}\` resumed.`);
    } else if (action === 'run') {
      await this.scheduleService.runNow(jobName);
      await message.reply(`Job \`${jobName}\` triggered manually.`);
    }
  }
}