Esse texto foi traduzido usando o sistema de tradução automatizado do Salesforce. Pegue nossa enquisa para fornecer feedback sobre esse conteúdo e diga-nos o que você gostaria de ver em seguida.

As arquiteturas modernas do Salesforce são cada vez mais alimentadas por processamento assíncrono; não como uma conveniência, mas como um requisito estratégico de escala. Nos últimos anos, vimos mais e mais empresas enfrentando volumes de dados crescentes, integrações complexas que envolvem vários pontos de contato e o surgimento de sistemas autônomos que funcionam 24 horas por dia, 7 dias por semana. Todas essas coisas levam os arquitetos a projetar sistemas assíncronos em primeiro lugar.

O processamento assíncrono no Salesforce geralmente significa projetar em torno dos limites e da complexidade do controlador. Esses limites atuam como proteções e restrições arquitetônicas que ajudam a produzir sistemas escalonáveis e seguros em massa. Embora nenhum limite de plataforma sirva diretamente para gerenciar a complexidade, os padrões de design podem ajudar a mitigar o risco nessa frente. Interna, o Salesforce frequentemente aprofunde os limites da plataforma para testar novos recursos e automatizar processos comerciais complexos. Criamos uma Estrutura de processamento assíncrono baseada em etapas para executar trabalhos assíncronos com um número arbitrário de etapas. Cada etapa pode ser executada, tentada novamente e reiniciada de modo independente com controles de governança compartilhados e visibilidade operacional total por meio de registro centralizado. Este documento descreve os principais componentes arquitetônicos: Apex enfileirável e finalizadores, fluxo agendado, cursores do Apex, ações invocáveis e integrações com o Slack. Juntos, esses componentes fornecem uma arquitetura modular, escalonável e observável adequada para necessidades empresariais em evolução.

  • As arquiteturas modernas do Salesforce devem adotar uma abordagem assíncrona primeiro para alcançar escala, resiliência e transparência operacional.
  • Dividir trabalho complexo em etapas executáveis de modo independente habilita desempenho previsível, novas tentativas mais seguras, pontuação de verificação, reversão e evolução modular sem reengenhar fluxos de trabalho principais.
  • A estrutura fornece uma alternativa escalonável a trabalhos em lote monolíticos e antigos, chamadas assíncronas encadeadas e fluxos profundamente aninhados, e é criada para cargas de trabalho de alto volume que devem ser dimensionadas horizontalmente dentro do Salesforce sem orquestração fora da plataforma.
  • A execução determinística e observável garante rastreamento de progresso, monitoramento de SLA, diagnóstico de falha e transparência em nível de auditoria por meio de registro e governança centralizados.
  • Projetado para rigor de nível corporativo, incluindo governança unificada, conformidade e controle de estado distribuído em processos de negócios de longa duração.

Antes de revisar os requisitos, aqui estão algumas considerações sobre quando usar uma estrutura como esta. Acima de tudo, considere qual sistema é a única fonte da verdade. Se sua organização do Salesforce depender minimamente de dados externos, mas precisar escalar de centenas a milhões de registros, considere uma estrutura assíncrona baseada em etapas.

Use essa estrutura se:

  • A maioria (ou todas) das informações com base nas quais realizar ações já existe em seu CRM.
  • O custo inicial ou contínuo de manter um trabalho de Extrair carga de transformação (ETL) para harmonizar dados externos é muito alto.
  • Você precisa adiar o processamento de um grande número de registros do Salesforce em uma agenda definida.
  • Você pode dividir o processamento em etapas separadas. Por exemplo, você pode criar um conjunto hierárquico ou baseado em árvore de registros, especialmente se o volume de dados passar pela hierarquia ou pela árvore.

Não use essa estrutura se:

  • Criar ou atualizar registros requer recálculo imediato.
  • A integração é um desafio porque os sistemas externos hospedem dados primários para atualizações de registro. (Considere enviar dados atualizados por push ao Salesforce com a API em massa.)

Considerando essas práticas, vamos revisar nossos requisitos e começar a criar.

Considere a instrução de problema:

Para um trabalho que precisa ser executado diariamente, verifique se determinados registros cumprem critérios predefinidos para processamento adicional. Se eles fizerem isso, inicie esses trabalhos de processamento. Processar registros pode significar extrair dados de vários sistemas externos para realizar cálculos. As etapas nos trabalhos devem notificar as pessoas por meio do Slack de que os registros processados estão prontos para revisão. As etapas também devem escalar as notificações para gerentes e superiores na hierarquia de papéis com base em um atraso configurável após a primeira rodada de notificações.

Esse problema envolve várias etapas diferentes, algumas das quais podem ocorrer independentemente uma da outra. Há muitas maneiras de dividir o trabalho. Aqui está um agrupamento:

  • O agendador.
  • A interface da etapa e as implementações concretas que processam registros (independentemente do tipo de processamento).
  • O processador que organiza as etapas.
  • O Apex invocável chamado pelo agendador.
  • A parte da notificação. Usamos o Slack SDK do Apex.
    • Há alguma complexidade oculta na frase "delay configurable". Revisaremos essa complexidade mais adiante neste artigo.

Aqui está um diagrama definido para a estrutura integrada:

Diagrama arquitetônico mostrando o agendador de fluxo, processamento assíncrono do Apex e diferentes implementações de etapas possíveis Agora, divida esse diagrama e comece a criar as peças.

O Fluxo agendado oferece várias vantagens como mecanismo de agendamento:

  • Fluxos agendados podem ser empacotados e implementados como metadados. Isso não se aplica a trabalhos agendados por meio do Apex (ou pela página Trabalhos agendados).
  • O elemento Aguardar é crítico para estruturas que exigem chamadas. Ao usá-lo no Fluxo, as chamadas não são necessárias na parte Invocável da estrutura.
  • A granularidade do agendamento atende aos requisitos: o intervalo mínimo para Fluxos agendados é diário. Se você precisar de uma frequência maior (por exemplo, por hora), reconsidere Fluxo agendado para esse requisito.

Outra consideração ao configurar o Fluxo agendado é o encaminhamento de ambiente. Antes de invocar a ação do Apex, adicione um elemento Decisão que avalie a variável {!$Api.Enterprise_Server_URL_100}. Isso garante que o trabalho seja executado apenas nos ambientes pretendidos, como UAT e Produção. Esse padrão é importante porque os sandboxes são atualizados com frequência ou recém-criados durante o SDLC, e sem uma verificação de ambiente explícita, um Fluxo agendado pode ser executado inadvertidamente em ambientes em que a estrutura não deve ser executada. Usar o operador contains no elemento Decisão torna a configuração resiliente a futuras criações de sandbox ou alterações de URL.

Por fim, considere como a estrutura deve capturar falhas. Sempre adicione um caminho de falha quando o Fluxo chamar qualquer Ação; por exemplo, você pode encaminhar falhas à ação "Adicionar entrada de registro" do Nebula Logger. Como o Nebula Logger grava registros em objetos personalizados, os clientes devem estar cientes de que os dados de registro consomem o armazenamento da organização. Por padrão, os registros são armazenados por 14 dias em uma organização e, em seguida, são limpos. Esse período de retenção é configurável. O Nebula Logger também usa os Eventos da plataforma para publicar registros, de modo que as entradas do registro são salvas independentemente da transação principal de processamento de dados – isso garante que as falhas sejam capturadas mesmo que a ação principal do Fluxo ou do Apex seja revertida. Os clientes devem avaliar os requisitos de retenção e volume de registros esperados ao considerar a adição de uma estrutura de registro.

Esta é a aparência do Fluxo:

O Fluxo agendado contendo um elemento Decisão para executar ou não, um elemento Pausar para permitir chamadas e a ação Invocável do Apex chamada junto com o caminho de falha do Nebula Logger.

Vamos passar para as primeiras partes do Apex code com o requisito de agendamento agora atendido.

Defina uma interface de Step:

Para este artigo, a interface de step é mostrada como uma classe externa para maior clareza. A estrutura em si é flexível – as equipes podem organizar a interface e suas implementações usando qualquer padrão de empacotamento do Apex que preferem, desde que todas as classes de Etapa façam referência à mesma interface.

Há alguns aspectos a serem considerados sobre os métodos definidos em nossa interface:

  • execute, embora sem argumento no momento, melhora quando passamos uma classe de State (ou interface) para orquestrar dados entre etapas quando a ordem importa.
  • Os getName podem retornar um valor de System.Type em vez de um String. A meta é dar à camada de orquestração uma maneira de registrar nomes de etapa sem expor outras propriedades.

Esta é a primeira implementação concreta para mostrar como essas peças se encaixam. Com uma exceção, recomendamos usar o Apex enfileirável para implementar processamento assíncrono no Apex; o Apex em lote normalmente não é necessário (e os métodos de @future são desencorajados). O Apex enfileirável é iniciado rapidamente e, com os Cursores do Apex, tem muitas vantagens em relação ao Apex em lote.

Os Cursores do Apex oferecem uma alternativa moderna ao modelo Apex em lote tradicional. De modo semelhante ao processamento em lote, uma implementação de cursor pode buscar registros em blocos (até 2.000 por lote). No entanto, os cursores permitem várias busca em uma única transação, permitindo uma taxa de transferência significativamente maior para operações de grande volume.

Ao adotar cursores como parte dessa estrutura, as equipes devem estar cientes das limitações atuais de teste e simulação. Como o comportamento do cursor em testes pode ser diferente do comportamento de produção, é importante projetar estratégias de teste que evitem depender de elementos internos do cursor e validar a lógica de orquestração nos limites. Conforme a plataforma evolui, essas áreas continuarão melhorando, mas a orientação principal permanece: Os cursores proporcionam um desempenho maior e uma sobrecarga de orquestração reduzida em comparação ao Apex em lote para muitos casos de uso.

Para definir um limite claro entre o cursor fornecido pelo sistema e seu próprio código, recomendamos criar uma representação semelhante ao cursor ao implementar a interface de Step. Considere este código:

Observe a aula de Cursor. Os cursores do Apex são instâncias de Database.Cursor, mas nossa implementação de Cursor nos dá flexibilidade em torno das falhas dos cursores. Aqui está a implementação:

Para o restante deste artigo, omitimos as declarações de sharing ao fazer referência a classes do Apex. Na prática, garanta que as classes de nível superior sejam usadas explicitamente com ou sem compartilhamento para estar de acordo com seu modelo de objeto e permissões.

Observe também que nossa implementação de Cursor delega para a plataforma Database.Cursor, com benefícios adicionais discutidos a seguir.

Primeiro, aqui estão os testes correspondentes:

Ao tornar as Cursor virtuais, as implementações de CursorStep concretas podem operar sem uma Database.Cursor quando não precisam de iterar um grande conjunto de registros, semelhante a retornar uma System.Iterable<T> em vez de uma Database.QueryLocator no Apex em lote. Aqui está um exemplo:

Observe que, como essa classe também é abstrata, ela deixa a implementação concreta de innerExecute para subclasses.

Há também uma alternativa à subclasse interna CursorLike. Se você souber que versões concretas de uma etapa como esta não passarão pelos outros limites do regulador, poderá retornar this.records de CursorLike.fetch e substituir o CursorStep.shouldRestart() pai para retornar falso. Isso permite que você itere em uma lista limitada apenas pelo limite de heap do Apex de 12 MB por transação assíncrona.

Nossa implementação baseada em cursor nos dá muita flexibilidade ao paginar em grandes quantidades de dados. A interface de Step, entretanto, nos dá a flexibilidade de descrever e encapsular passos de todos os tipos.

Considere uma etapa baseada em fluxo:

Como os Fluxos não podem retornar parâmetros de saída que correspondam a um tipo definido pelo Apex, verificamos um parâmetro de saída de shouldRestart antes de usá-lo.

Algumas etapas podem ser sinalizadas por recurso. Você pode implementar lógica para decidir quais etapas incluir ou usar uma etapa no-op para um recurso desabilitado. O padrão de objeto nulo é uma maneira comum de reduzir a complexidade dentro da camada de orquestração:

Agora temos vários blocos de construção com os quais trabalhar. Vamos dar uma olhada na camada de orquestração responsável pela iteração entre etapas.

O processador é um ponto de inflexão na arquitetura. Devemos decidir quem define quais etapas serão inicializadas e onde. As opções incluem:

  • Peça para o processador definir quais etapas são mapeadas para a lógica de negócios. Essa opção é simples, mas tem uma escala ruim para legibilidade.
  • Defina o mapeamento com Metadados personalizados (CMDT). Os campos Relacionamento de metadados não oferecem suporte a ApexClass, o que combina facilmente a ortografia de nomes de classe à configuração do seu processo de negócios. Você pode reduzir o risco administrativo tornando o campo uma lista de opções e validando o tipo existente (Type.forName() ou consultando ApexClass), mas como os registros CMDT não suportam acionadores, a validação ocorre no tempo de execução. Essa rota é testável, mas os administradores ainda podem criar registros CMDT apenas em produção. Continue com cuidado.
  • Defina o mapeamento com registros. Não administradores podem configurar etapas, mas as implementações ficam mais difíceis e os ambientes podem se desviar. Prossiga com cuidado.

Há uma famosa citação do Código Limpo sobre como lidar com essa parte específica da complexidade:

A solução para esse problema é enterrar a declaração de switch [para fazer objetos] no porão de uma fábrica abstrata, e nunca deixar ninguém vê-la.

Com isso em mente, e como nosso número atual de etapas é bem definido e provavelmente não crescerá demais, o processador de etapas também pode ser a fábrica das etapas. Isso pode usar uma enumeração para conduzir a instrução switch:

E então pela nossa StepProcessor:

Os métodos de fábrica mostrados, como o addTypeOneSteps(), podem delegar preocupações como a sinalização de recursos; o cleanSteps() realiza uma verificação única das etapas coletadas para garantir que não haja etapas “vazias” antes de ir realmente assíncrono. Isso pode se parecer com isto:

Não discutimos o tratamento de erros desde a menção ao Nebula Logger na seção Fluxo agendado. Isso ocorre porque o System.Finalizer permite que o registro de todas as condições de erro seja abrangido sem adicionar tratamento de erros específico em cada etapa. Cada Step se concentra na corrida, enquanto registramos e recuperamos caminhos infelizes para que apareçam em testes de unidade. Isso dá suporte à iteração segura e ao alerta em nível de produção (usando o plug-in Slack Logger para Nebula para todos os logs WARN e ERROR).

Uma nota sobre o registro de erros: passar a instância da etapa para mensagens de log assume um nível de Trust no que fica visível nos logs. A toString() padrão para classes do Apex inclui todas as propriedades estáticas e no nível da instância na mensagem. Isso pode ser desejável ou pode vazar informações confidenciais. Embora o registro e a segurança não sejam o foco aqui, observe que, para alguns sistemas, a adesão a uma interface como Step também pode envolver forçar uma substituição para toString().

Esse método coloca a responsabilidade de cada criador de objeto decidir o que é permitido imprimir, o que pode ser desejável.

Nos níveis de desmatamento: no nível da StepProcessor, utilizamos o INFO, o nível mais elevado sem erros. Conforme você fica mais granular no aplicativo, os níveis de registro devem diminuir de acordo. As etapas individuais podem usar DEBUG para informações de alto nível, com FINE, FINER e FINEST reservados para resultados cada vez mais detalhados. Registrar é tanto uma arte quanto uma ciência, mas seguir esses princípios ajuda a manter os registros consistentes e úteis.

Antes de avançar, vamos refletir brevemente sobre a decisão de fazer com que nosso processador de etapas hospede a lógica para a qual as etapas são usadas. Em uma grande base de códigos, considere tornar as StepProcessor virtuais ou abstratas e faça com que as subclasses identifiquem etapas específicas para estabelecer uma separação adequada das preocupações.

O agendador eventualmente chama o Apex. Com o restante da configuração concluída, a seção Apex invocável pode decidir quais etapas devem ser executadas e passar o List<StepType> para o processador:

Esta é uma parte simples da equação: usando registros, dados ou lógica para determinar quais tipos de etapa executar. A Ação invocável é simples porque encapsulamos a complexidade em outro lugar. Também protegemos contra exceções inesperadas e facilitamos o teste de cada peça em isolamento.

O Apex Slack SDK está além do escopo deste artigo, mas um possível problema dos requisitos é revisitar: notificar as pessoas para cima na hierarquia de papéis com base em um atraso configurável. No papel, isso é simples, e você pode (corretamente) considerar a System.enqueueJob(this) na StepProcessor. Com o System.AsyncOptions, nossa inclinação inicial era usar a sobrecarga de enqueueJob para satisfazer esse requisito.

Por enquanto, no entanto, o atraso máximo via System.AsyncOptions.MinimumQueueableDelayInMinutes é de 10 minutos. Como o requisito é de 120 minutos, restam algumas opções. Uma abordagem ingénua pode se parecer com esta:

Na prática, o atraso seria passado para essa classe porque o atraso é conduzido por configuração.

Não recomendamos essa abordagem, a menos que você tenha certeza de que haverá apenas um tipo de notificação atrasada. Ele executa 11 trabalhos assíncronos adicionais antes do início (ou mais, se o atraso aumentar). Esse custo pode ser bom para um trabalho, não para muitos. Você também precisaria adicionar um método à interface de Step para que cada etapa possa dizer ao processador quanto tempo esperar antes de reiniciar, o que aumenta o ruído.

Isso nos deixa com duas possibilidades interessantes:

  • Você poderá inserir a etapa atrasada na estrutura de trabalho existente se já tiver um trabalho de pesquisa agendado em um intervalo adequado. Você também deve estar certo com o atraso especificado atingindo até 15 minutos mais tarde (15 minutos é o intervalo mínimo de atualização para uma expressão CRON agendada pelo Apex). Isso corresponde aproximadamente ao exemplo do Apex invocável; o agendamento é realizado por meio do Apex. Em outras palavras, você pode reutilizar a mesma arquitetura baseada em Step para processar registros com base em um carimbo de data e hora "Iniciar após" e decidir quais etapas usar com base em um mapeamento de lista de opções ou de lista de opções de seleção múltipla de volta aos valores de enumeração de StepType mostrados anteriormente.
  • Como alternativa, se você se sentir à vontade definindo uma classe do Apex externa extra, volte para o Apex em lote (ao contrário do Apex enfileirável, que suporta classes internas, as classes do Apex em lote devem ser classes externas) usando o System.scheduleBatch().

Considere o exemplo do Apex em lote. Embora geralmente recomendamos o Apex enfileirável para flexibilidade e controle, este é um caso em que o Apex em lote ainda domina:

E então, na StepProcessor, imagine que o método addTypeOneSteps() mostrado anteriormente seja atualizado com esta etapa atrasada:

Embora geralmente não recomendássemos esse tipo de salto, esse atraso de etapa se torna outro bloco de criação reutilizável. Até que atrasos mais longos sejam permitidos no Apex enfileirável, também representa a maneira mais fácil de produzir esse efeito (sem um mecanismo de consulta, como discutido).

Usamos o design orientado a objetos para atender aos requisitos e criamos um sistema que será dimensionado enquanto equilibra o custo de construção e manutenção de longo prazo. Embora a declaração de etapas e a instanciação possam, por fim, ultrapassar o seu lugar na StepProcessor, há pouca dívida técnica adicional aqui. Com a FlowStep, administradores e desenvolvedores podem decidir juntos quando as soluções sem código ou pro-código fazem mais sentido.

Usando a interface de System.Finalizer na estrutura Queueable do Apex, juntamente com o Nebula Logger, criamos um sistema robusto e testável que nos alerta sobre falhas imprevistas, mesmo que as etapas futuras não tenham registro explícito. Para nós, esse sistema está felizmente aumentando os números e reduzindo o custo e a complexidade. Ele também nos deu percepções valiosas sobre o comportamento dos cursores do Apex sob cargas de trabalho reais, ajudando-nos a refinar nossa abordagem enquanto melhora o próprio recurso.

Ao dividir cargas de trabalho complexas de alto volume em etapas de execução modulares, a estrutura de Estrutura de processamento assíncrono baseada em etapas transforma restrições de plataforma em vantagens de engenharia, permitindo desempenho previsível, observabilidade e governança em escala corporativa. As etapas podem ser configuradas por administradores e desenvolvedores, e em ambos os casos, os autores de etapas podem se concentrar com segurança em observar os limites básicos do controlador de plataforma (como linhas DML e linhas de consulta recuperadas) sem precisar se preocupar com a escala de cada etapa.

Para operacionalizar e adotar esse padrão em implementações corporativas, os arquitetos devem:

  • Avaliar as automações existentes para identificar áreas em que a orquestração assíncrona pode ajudar a melhorar o desempenho e melhorar a visibilidade.
  • Divida grandes processos em etapas separadas e executáveis de forma independente com metas de processamento claras e pontos de autor separados (como Fluxo ou Apex).
  • Defina e agrupe tipos de etapas para acelerar a reutilização de etapas e a padronização entre unidades de negócios.
  • Pilote a abordagem com novos processos ou automações existentes. Você pode se surpreender ao encontrar quantos casos de ponta você encontra gratuitamente dentro de etapas, cuidando de seu registro integrado e capacidade de observação!

James Simone é engenheiro de software principal na Salesforce e tem mais de uma década de experiência trabalhando na plataforma. Ele era um cliente da Salesforce — e proprietário do produto — antes de se mudar para o desenvolvimento, e está escrevendo detalhes técnicos sobre o Salesforce desde 2019 no The Joys Of Apex. Ele publicou artigos anteriormente no blog Salesforce Developer e também no blog Salesforce Engineering.