Apex assíncrono – Scheduled Apex – Parte 4

Chegamos ao nosso último post da série sobre Apex assíncrono, falei aqui sobre métodos futuros, classes Queueable e Batch Apex, e hoje para fechar o tema, vamos falar sobre as classes Scheduled Apex, bom mas chega de enrolação e vamos logo para o que interessa não é mesmo?

O que é o Scheduled Apex?

podemos programar para que ele seja executado TODOS os dias em um determinado horário

As Scheduled Apex nos permite realizar o agendamento de uma execução do Apex em horário específicos, diferentemente que fizemos com o agendamento das classes Batch Apex, onde só podíamos adiar a sua execução, com o Scheduled Apex ganhamos um poder de escolha muito maior, podemos programar para que ele seja executado TODOS os dias em um determinado horário. Mas claro que podemos fazer ainda mais do que isso, podemos agendar uma execução para 2x por dia, ou até mesmo 1x por semana, você é quem escolhe quando o processo será executado.

Quando usar uma Scheduled Apex

O cenário mais comum é exatamente quando temos que executar um conjunto de código do Apex em um horário e dia especificos.

Estrutura de uma Scheduled Apex

As Scheduled Apex também são classes que precisam implementar uma interface para o seu funcionamento, ao implementar a interface Schedulable, é preciso implementar o método execute que recebe por parâmetro um SchedulableContext, a estrutura básica de uma classe Scheduled Apex é assim:

global class scheduledSample implements Schedulable {
    global void execute(SchedulableContext SC) {
        Id triggerId = SC.getTriggerId();
        System.debug(triggerId);  

        SampleClass sample = newSampleClass();
        sample.execute();
    }
}

Note que no exemplo, é instanciado uma classe e chamado um método dessa classe, isso é uma boa pratica abordado pelo Salesforce, a ideia é que a sua classe Scheduled Apex tenha o mínimo de código possível, e toda a sua lógica esteja descentralizada, facilitando por exemplo a sua cobertura de testes e manutenção.

Agendando a Execução de uma Batch Apex

Lembra da nossa classe Batch Apex, pois então, com todo aquele poder de processamento, já imaginou poder casar a flexibilidade do agendamento oferecida pela Scheduled Apex com o processamento da Batch Apex, acredite isso é possível, e não precisamos de nenhuma mágica para isso, só precisamos unir os dois conceitos, imagine então que você já tenha a sua classe Batch Apex criada e queira então que ela seja executada em uma data e hora especifica, para esse caso nosso código ficaria assim:

global class scheduledSample implements Schedulable {
   global void execute(SchedulableContext SC) {
      Database.executeBatch(new batchCenarioStatefull());
   }
}

Entendendo o flow de agendamento

Se você algum dia precisou agendar um processo no Cron do Linux, então com certeza vai entender facilmente as tabelas abaixo, se não, então essa hora chegou.

Para realizar um agendamento, são necessários informar 3 parâmetros para o Schedule, o primeiro parâmetro é um campo String, que você usa para nomear o seu processo, o segundo parâmetro é o timer para disparo do seu agendamento, é nesse  ponto que precisamos de maior entendimento, e o terceiro e último parâmetro, é a instância da sua classe Scheduled Apex.

proschedule p = new proschedule();
String sch = '0 0 8 13 2 ?';
System.schedule('One Time Pro', sch, p);

No exemplo acima ele será executado no próximo dia 13 de fevereiro às 8 horas da manhã.

O Schedule deve respeitar a expressão na seguinte ordem:

Nome Valor aceitos Caracter especial
Segundos 0–59 Nenhum
Minutos 0–59 Nenhum
Horas 0–23 Nenhum
Dias do mês 1–31 , – * ? / L W
Mês 1–12 ou:
  • JAN
  • FEB
  • MAR
  • APR
  • MAY
  • JUN
  • JUL
  • AUG
  • SEP
  • OCT
  • NOV
  • DEC
, – * /
Dia da Semana 1–7 ou:
  • SUN
  • MON
  • TUE
  • WED
  • THU
  • FRI
  • SAT
, – * ? / L #
Ano (Opcional) null or 1970–2099 , – * /

Ou seja, primeiro informamos os segundos, no nosso exemplo informamos 0, e seguida os minutos, no nosso exemplo informamos 0, o terceiro valor são as horas, no nosso caso informamos 8, seguido do dia do mês, no nosso caso informamos 13, em seguida informamos 2 para o mês e por último ? para o dia da semana, que nesse caso como informamos o dia do mês, não precisamos indicar a semana.

Os caracteres especiais são interpretados da seguinte forma:

Caractere especial Descrição
, Delimita valores. Por exemplo, use JAN, MAR, APR para especificar mais de um mês.
Especifica um intervalo. Por exemplo, use JAN-MAR para especificar mais de um mês.
* Especifica todos os valores. Por exemplo, se Mês for especificado como *, o trabalho será agendado para cada mês.
? Não especifica nenhum valor específico. Isso só está disponível para Day_of_month e Day_of_week e é geralmente usado ao especificar um valor para um e não o outro.
/ Especifica incrementos. O número antes da barra especifica quando os intervalos serão iniciados e o número após a barra é o valor do intervalo.
Por exemplo, se você especificar 1/5 para Day_of_month, a classe Apex é executada a cada cinco dias do mês, começando no primeiro dia do mês.
L Especifica o final de um intervalo (último). Isso só está disponível para Day_of_month e Day_of_week. Quando usado com o dia do mês, L sempre significa o último dia do mês, como 31 de janeiro, 29 de fevereiro para anos bissextos e assim por diante. Quando usado com Day_of_week por si só, isso sempre significa7 ou SAT. Quando usado com um valor Day_of_week, significa o último desse tipo de dia no mês. Por exemplo, se você especificar  2L, você está especificando a última segunda-feira do mês. Não use um intervalo de valores com L, os resultados podem ser inesperados.
W Especifica o dia da semana mais próximo (de segunda a sexta) do dia determinado. Isso só está disponível para Day_of_month.
Use o L e W juntos para especificar o último dia da semana do mês.
# Especifica o enésimo dia do mês, no formato dia da semana #day_of_month. Isso só está disponível para Day_of_week. O número antes do # especifica o dia da semana (SUN-SAT). O número após o especifica o dia do mês. Por exemplo, especificando 2#2 significa que a classe é executada na segunda segunda-feira de cada mês.

Alguns exemplos de uso da expressão:

Expressão Descrição
0 0 13 * * ? A classe é executada todos os dias às 13:00.
0 0 22 ? * 6L A classe é executada na última sexta-feira de cada mês às 22h.
0 0 10 ? * MON-FRI A classe funciona de segunda a sexta, às 10 horas.
0 0 20 * * ? 2020 A classe é executada todos os dias às 20h durante o ano de 2020.

Acompanhando o progresso de um Scheduled Apex

Uma vez que a sua classe Scheduled Apex tiver sido agendada, você poderá obter mais informações sobre ela executando uma consulta SOQL no CronTrigger e recuperando alguns campos, como o número de vezes que o trabalho foi executado e a data e hora em que o trabalho está agendado para execução novamente, como mostrado neste exemplo.

CronTrigger ct = [SELECT TimesTriggered, NextFireTime
                  FROM CronTrigger 
                  WHERE Id = :jobID];
System.debug(ct);

O exemplo anterior pressupõe que você tenha ID de agendamento que foi retornado pelo System.schedule. Se você está realizando essa consulta dentro do método execute de sua classe agendada, você poderá obter o ID atual chamando o método getTriggerId na variável SchedulableContext. Assumindo que este nome de variável seja sc, o exemplo modificado se torna:

CronTrigger ct = [SELECT TimesTriggered, NextFireTime
                  FROM CronTrigger 
                  WHERE Id = :sc.getTriggerId()];
System.debug(ct);

Você também pode obter o nome da tarefa e o tipo da tarefa no registro CronJobDetail associado ao registro CronTrigger. Para fazer isso, use o CronJobDetail relacionamento ao executar uma consulta no CronTrigger. Este exemplo recupera o registro CronTrigger mais recente com o nome e o tipo de trabalho do CronJobDetail.

CronTrigger job = [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType 
                   FROM CronTrigger ORDER BY CreatedDate DESC LIMIT 1];
System.debug(job);

Como alternativa, você pode consultar o CronJobDetail diretamente para obter o nome e o tipo do trabalho. Este próximo exemplo obtém o nome e o tipo da tarefa para o registro CronTrigger consultado no exemplo anterior. O ID do registro CronJobDetail correspondente é obtido pelo CronJobDetail.Id do registro CronTrigger.

CronJobDetail ctd = [SELECT Id, Name, JobType 
                     FROM CronJobDetail 
                     WHERE Id = :job.CronJobDetail.Id];
System.debug(ctd);

Para obter a contagem total de todos os trabalhos agendados do Apex, excluindo todos os outros tipos de trabalhos agendados, execute a consulta a seguir. Observe que o valor ‘7’ é especificado para o tipo de trabalho Scheduled Apex.

Integer total = [SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7'];
System.debug(total);

Cobertura de testes unitários de uma classe Scheduled Apex

Vamos imaginar o cenário onde temos uma classe agendada para atualizar os dados de contas 1 vez por mês.

global class TestScheduledApexFromTestMethod implements Schedulable {
    //Executa todo dia 1º de cada mês as 2 horas e 30 minutos
    public static String CRON_EXP = '0 30 2 1 * ?'; 
   
    global void execute(SchedulableContext ctx) {
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
                          FROM CronTrigger 
                          WHERE Id = :ctx.getTriggerId()];

        System.assertEquals(CRON_EXP, ct.CronExpression);
        System.assertEquals(0, ct.TimesTriggered);

        Account a = [SELECT Id, Name 
                     FROM Account 
                     WHERE Name = 'testScheduledApexFromTestMethod'];

        a.name = 'testScheduledApexFromTestMethodUpdated';
        update a;
    }   
}

Para que os métodos assíncronos sejam executados é necessário fazer o uso do Test.startTest() e Test.stopTest(), e sim, essa  é a única forma de garantir que os testes serão executados antes do final do sua classe de teste, então a nossa classe de cobertura de Batch Apex ficará assim:

@istest
class TestClass {
    @istest
    static void test() {
        Test.startTest();

        Account a = new Account();
        a.Name = 'testScheduledApexFromTestMethod';
        insert a;

        String jobId = System.schedule('testBasicScheduledApex', 
                       TestScheduledApexFromTestMethod.CRON_EXP, new TestScheduledApexFromTestMethod());

        // Obtém informações do agendamento
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime
                           FROM CronTrigger WHERE id = :jobId];

        // Verifica se a expressão de agendamento é a mesma da que esta em execução
        System.assertEquals(TestScheduledApexFromTestMethod.CRON_EXP, ct.CronExpression);

        // Verifica se o agendamento não foi executado ainda
        System.assertEquals(0, ct.TimesTriggered);

        //Checa se o nome esta realmente diferente antes do teste ser executado
        System.assertNotEquals('testScheduledApexFromTestMethodUpdated', [SELECT id, name FROM account WHERE id = :a.id].name);

        Test.stopTest();
   
       //Checa se o nome foi alterado
       System.assertEquals('testScheduledApexFromTestMethodUpdated', [SELECT Id, Name FROM Account WHERE Id = :a.Id].Name);
    }
}

Limites de uma Schedule Apex

  • Você pode ter apenas 100 trabalhos agendados do Apex ao mesmo tempo. Você pode avaliar sua contagem atual visualizando a página Tarefas agendadas no Salesforce e criando uma exibição personalizada com um filtro de tipo igual a “Apex agendado”. Você também pode consultar programaticamente os objetos CronTrigger e CronJobDetail para obter a contagem de tarefas agendadas do Apex.
  • O número máximo de execuções agendadas do Apex por um período de 24 horas é de 250.000 ou o número de licenças de usuário em sua organização multiplicado por 200, o que for maior.

Boas práticas ao usar uma Scheduled Apex

A execução real pode ser atrasada com base na disponibilidade do serviço

  • O Salesforce agenda a classe para execução no horário especificado. A execução real pode ser atrasada com base na disponibilidade do serviço.
  • Tome muito cuidado se estiver planejando agendar uma classe dentro de uma trigger. Você deve garantir que a trigger não irá adicionar mais classes programadas do que o limite. Em particular, considere atualizações em bulk API, assistentes de importação, alterações de registro em massa por meio da interface do usuário e todos os casos em que mais de um registro pode ser atualizado por vez.
  • Embora seja possível fazer processamentos pesados dentro do método execute, recomendamos que todo o processamento ocorram em uma classe separada.
  • Chamadas de serviço da Web síncronas não são suportadas pelo Schedule Apex. Para poder fazer chamadas, faça uma chamada assíncrona colocando a anotação em um método com @future(callout = true) e chame esse método a partir da Schedule Apex. No entanto, se o seu Scheduled Apex executar uma Batch, as chamadas serão suportadas.
  • As tarefas do Scheduled Apex para execução durante um período de inatividade de manutenção do serviço Salesforce serão agendadas para serem executadas depois que o serviço for reativado, quando os recursos do sistema estiverem disponíveis. Se um trabalho agendado do Apex estava em execução quando se iniciou o período de inatividade, o trabalho é revertido e agendado novamente depois que o serviço é reativado. Observe que, após as principais atualizações de serviço, pode haver atrasos mais longos do que o normal para iniciar os trabalhos agendados do Apex devido a picos de uso do sistema.

 

Um forte abraço trailblazer e até o próximo post :)

 

Fernando Sousa

Senior Salesforce Developer

Bacharel em Sistemas da Informação pela Universidade de Taubaté (UNITAU) e MBA em Projeto de Aplicações para Dispositivos Móveis pelo IGTI – Instituto de Gestão em Tecnologia da Informação. 

Comecei a programar bem cedo, por volta de 10 anos de idade, de maneira auto-didata passei por várias linguagens.

Em 2015 me conectei a plataforma Salesforce pela primeira vez, para fazer una integração entre um Aplicativos Mobile em android e o Salesforce Platform. 

Atualmente com as certificações Salesforce Certified Platform Developer I, Salesforce Certified Platform App BuilderSalesforce Certified Platform Developer IISalesforce Administrator e Sharing and Visibility.

Acompanhe meu Trailhead aqui.

 

ApexApex AssíncronoSalesforceSalesforce DeveloperScheduledScheduled Apex