Skip to main content

Conhecer os princípios da camada de seletor

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:

  • Explicar os benefícios do uso da camada de seletor.
  • Explicar os recursos da camada de seletor.
  • Entender como a camada de seletor se encaixa na arquitetura de seu aplicativo e na plataforma.

Acompanhar com o Trail Together

Deseja acompanhar um especialista enquanto trabalha nesta etapa? Veja este vídeo que faz parte da série Trail Together.

(Este clipe começa na marca dos 36:04 minutos, caso você queira retroceder e ver o início da etapa novamente.)

Conhecer a camada de seletor

Ao longo deste módulo, um dos maiores focos foi garantir uma boa separação de preocupações, tornando bases de código de nível empresarial, complexas e grandes, mais claras, autodocumentadas, sólidas e adaptáveis a mudanças devido à refatoração ou a uma evolução de funções. Vamos continuar com isso.

Nesta unidade, você aprenderá sobre o padrão Selector, uma camada de código que encapsula a lógica responsável por consultar informações de objetos padrão e seus objetos personalizados. A camada de seletor alimenta esses dados na camada de domínio e no código da camada de serviço. Você também pode reutilizar classes de seletor de outras áreas que exijam consulta, como Apex em lote e controladores.

A figura a seguir mostra onde a classe de seletor fica na hierarquia de classes do padrão. Ela é usada principalmente por classes na camada de serviço (por trás do limite de serviço), por exemplo, as próprias classes de domínio e de serviço. No entanto, as classes do Apex em lote e os controladores de interface do usuário também podem utilizá-las diretamente.

Classe de serviço na hierarquia.

Uma camada de seletor contém código responsável por consultar registros no banco de dados. Embora você possa colocar consultas SOQL em outras camadas, algumas coisas podem acontecer à medida que seu código vai aumentando em complexidade.

  • Inconsistências de consulta: as mesmas consultas, feitas de lugares diferentes para a mesma informação ou com os mesmos critérios (ou variantes sutis), podem causar inconsistências no aplicativo. Pode ser que alguns critérios precisem ser aplicados e se percam quando o código é copiado e colado por aí ao longo do tempo. Pense em uma consulta como um trecho de lógica. Você sabe que não é ideal copiar e colar trechos de lógica para lá e para cá. Isso também não é ideal para código.
  • Inconsistências nos dados de consulta: quando dados do registro consultados (essencialmente sObjects) começam a fluir por sua lógica e pelo código na organização, os métodos de chamada que recebem os registros podem se tornar frágeis. Às vezes um trecho de código solicita a transmissão de uma conta ou oportunidade, mas não pode garantir de antemão que campos foram consultados e, consequentemente, o conteúdo resultante, causando um erro no tempo de execução System.SObjectException:SObject row was retrieved via SOQL without querying the requested field:X.Y. (System.SObjectException:A linha SObject foi recuperada por SOQL sem a consulta do campo solicitado:X.Y.) Os desenvolvedores podem acabar repetindo outra consulta em relação ao mesmo conjunto de registros apenas para consultar os campos obrigatórios. Ou talvez os caminhos de código fluam de tal forma que os registros de conta consultados por consultas diferentes sejam transmitidos para uma função compartilhada. De qualquer forma, isso não é bom.
  • Inconsistências de segurança: o Salesforce exige que todo o código do Apex obedeça à segurança do objeto do usuário em execução. O Apex também pode ser executado no nível do sistema, ou seja, o desenvolvedor é responsável por verificar a segurança antes da execução da consulta. Felizmente, não é necessário muito código do Apex para isso; no entanto, isso costuma ser ignorado e não é fácil testar isso nos testes de unidade. Manter isso em um seletor facilita muito a vida.

O padrão Selector de que vamos falar ajuda com essas questões.

Padrão Selector

Abaixo vemos a definição de Martin Fowler sobre o padrão Mapper que serve de base para o padrão Selector.

Padrão Mapper: “Uma camada de mapeadores (473) que move dados entre objetos e um banco de dados, ao mesmo tempo isolando-os uns dos outros e do próprio mapeador.” – Fowler, Martin

Este módulo usa o termo seletor em vez de Mapper. Selector reflete melhor a diferença principal na implementação do padrão Mapper no Force.com, uma que Stephen Willcock observou ao desenvolver a implementação original desse padrão no Apex. A classe fflib_SObjectSelector apresentada nesta unidade se baseia nele.

A diferença é que o padrão Selector não está mapeando um conjunto de resultados de banco de dados para um objeto de dados ou mesmo para uma representação de domínio. No contexto do Salesforce, sua função principal é fornecer registros SObject, a forma nativa da plataforma de expressar dados consultados do banco de dados. Em alguns casos, como você verá mais adiante na unidade, ele pode fornecer outras representações de dados Apex, mas, basicamente, ele está apenas selecionando dados nessa instância; daí vem a alteração de nome do padrão.

Em termos de separação de preocupações, o Selector se preocupa com o seguinte:

  • Visibilidade, reutilização e manutenção: o seletor facilita a localização e a manutenção da lógica de consulta ao banco de dados. Ao atualizar critérios de consulta ou refletir alterações no esquema, como a adição de campos mais usados com mais facilidade e menos risco para o resto da base de código. O seletor deve usar as referências de tempo de compilação para nomes de campo, mesmo na construção de consultas dinâmicas. Isso faz com que, quando os campos são excluídos, a plataforma impeça a exclusão em caso de referências no código.
  • Previsibilidade dos dados consultados: o que o seletor está fazendo e o que ele está retornando deve ficar claro no nome do método. O retorno de campos preenchidos de forma inconsistente nos registros sObject não é sempre um bom modelo, pois não fica claro para o chamador ou para quem receber os dados do registro resultantes o que era para vir nos campos, resultando em exceções no tempo de execução. O padrão Selector garante que os registros SObject consultados tenham um conjunto mínimo de campos consultados. Os desenvolvedores podem ver isso como parte do "contrato do seletor para o resto da organização".
  • Segurança: fornece meios para que o chamador aceite ou recuse (em cenários no nível do sistema) verificações de segurança que imponham compartilhamentos e permissões aplicadas ao contexto do usuário atual.
  • Simpatia de plataforma: otimiza as consultas ao máximo, expressando os critérios principalmente por meio de conjuntos, o que incentiva o chamador a ser massificado no código quando os métodos de seletor são chamados. Os seletores também devem poder equilibrar a necessidade de consistência dos dados dos campos consultados com a otimização dos dados dos campos em um contexto de conjuntos de dados maiores e quando o heap for uma preocupação.

Considerações sobre o design

Ao codificar uma classe de seletor, considere os seguintes aspectos de design:

Como usar uma classe de seletor

Como colocamos essas considerações de design em prática? Abaixo vemos um exemplo simples usado para iniciar uma instância da classe de domínio com registros. Esse seletor usa métodos não estáticos para fornecer sua funcionalidade, ou seja, uma instância é criada. Se o chamador retém a instância de seletor, há escopo para configuração e armazenamento em cache, se necessário.

List<Opportunity> opportunities =
   new OpportunitiesSelector().selectById(opportunityIds));

Veja um código de camada de serviço (talvez você o reconheça de uma unidade anterior) que agora está usando um seletor para consultar e transmitir registros para uma classe de domínio, que está executando lógica de domínio nos registros consultados. Assim, a lógica de SOQL embutido foi fatorada fora da lógica de serviço e de domínio.

List<Opportunity> opportunityRecords = new 
       OpportunitiesSelector().selectByIdWithProducts(opportunityIds);
Opportunities opportunities = new Opportunities(opportunityRecords);
opportunities.applyDiscount(discountPercentage, uow);

O exemplo mostra um seletor no método start do Apex em lote para acionar os registros processados no execute.

public Database.QueryLocator start(Database.BatchableContext bc) {
    return new InvoicesSelector().queryLocatorInvoicesToProcess();
}

Como testar classes de seletor

É claro que seu código da classe de seletor está coberto pelos testes da camada de domínio ou de serviço. No entanto, se você quiser fazer apenas testes de unidade, as classes de seletor funcionam basicamente como qualquer outra classe: crie os dados de teste, chame os métodos e avalie os resultados.

@IsTest
private static void whenQueryOpportuntiesGetProducts() {
    
    // Given
    Set<Id> testRecordIds = setupTestOpportunities();
    // When
    OpportunitiesSelector selector = new OpportunitiesSelector();
    List<Opportunity> opportunities =
      selector.selectByIdWithProducts(testRecordIds);
    // Then
    System.assertEquals(10, opportunities.size());
    System.assertEquals(5, opportunities[0].OpportunityLineItems.size());
}

Se você incluiu métodos QueryLocator em seu seletor, poderá chamar o método de iteração para obter os registros resultantes e avaliar os dados esperados no teste do seletor.

Database.QueryLocator queryLocator =
    new ProductsSelector().queryLocatorById(new Set<Id> {product.Id});
Database.QueryLocatorIterator productsIterator = queryLocator.iterator();
Product2 queriedProduct = (Product2) productsIterator.next();

Recursos

Continue a aprender de graça!
Inscreva-se em uma conta para continuar.
O que você ganha com isso?
  • Receba recomendações personalizadas para suas metas de carreira
  • Pratique suas habilidades com desafios práticos e testes
  • Monitore e compartilhe seu progresso com os empregadores
  • Conecte-se a orientação e oportunidades de carreira