Conhecer os princípios da camada de serviço
Objetivos de aprendizagem
Após concluir esta unidade, você estará apto a:
- Explicar as origens do padrão Service a partir dos padrões de Arquitetura de aplicativo corporativo, livro de Martin Fowler.
- Determinar qual código do Apex pertence à camada de serviço.
- Debater como a camada de serviço se encaixa na arquitetura do aplicativo e na plataforma.
- Criar uma camada de serviço para funcionar de acordo com as melhores práticas da 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 17:45 minutos, caso você queira retroceder e ver o início da etapa novamente.)
Introdução
A unidade anterior apresentou SOC como uma forma de concentrar as atenções dos arquitetos de software na lógica de aplicativos em camadas. Esta unidade aborda a definição e a utilização da camada de serviço como um ponto de entrada principal para outras camadas e consumidores (como uma API) de seu aplicativo.
A camada de serviço “define o limite de um aplicativo com uma camada de serviço que estabelece um conjunto de operações disponíveis e coordena a resposta do aplicativo em cada operação”. Martin Fowler/Randy Stafford, Padrões de Arquitetura de Aplicações Corporativas
A camada de serviço ajuda a formar um encapsulamento claro e restrito de código ao implementar tarefas comerciais, cálculos e processos. É importante assegurar que a camada de serviço esteja pronta para uso em diferentes contextos, como aplicativos móveis, formulários de interface de usuário, interfaces de usuário web avançadas e várias APIs. Ela deve permanecer pura e abstrata para resistir às mudanças e às demandas futuras. As seções a seguir definem as diretrizes para criação de uma implementação de camada de serviço no Apex seguindo as melhores práticas do Salesforce e os limites de administrador.
Quem usa a camada de serviço?
Talvez você queira responder “todo mundo que é alguém” usa camada de serviço, mas, tecnicamente, o consumidor de uma camada de serviço é chamado de “cliente”. Um cliente invoca o código da sua camada de serviço. Essas interações não são interações humanas com a camada de serviço e, sim, outras partes de código que interagem com o usuário ou sistema, como um controlador de interface de usuário ou o Apex em lote.
Um exemplo de um cliente é código criado com uma classe de controlador ou método @AuraEnabled
do Visualforce. No entanto, existem vários outros clientes (consumidores) do seu código de camada de serviço a serem considerados. Para criar uma lista de candidatos, pense em todas as maneiras de invocar a lógica do Apex na Salesforce Platform.
Como você pode imaginar, é muito fácil vazar lógica da camada de serviço no código do Apex criado para outras camadas e finalidades. O vazamento degrada o valor da implementação de uma camada de serviço porque dá ensejo a inconsistências que podem chegar à experiência de usuário final. Por exemplo, quando os usuários interagem com o aplicativo por meio de determinado recurso exposto por várias tecnologias do Salesforce. Ou determinado cálculo exposto por um componente do Lightning que você criou, assim como por um serviço REST do Apex. Em ambos os casos, o comportamento precisa ser consistente. Na próxima seção, discutiremos o design e as responsabilidades da camada de serviço, além das expectativas que o código que consome a camada de serviço deve ter.
Inovação e adaptabilidade da plataforma
As tecnologias indicadas acima foram introduzidas gradualmente como novos recursos na plataforma ao longo dos anos. Imagine se a codificação fosse feita de maneira vinculada a um recurso específico e você sempre tivesse que refatorar. Agora imagine como é mais fácil adotar e adaptar seu aplicativo a esses recursos e a recursos futuros quando não é necessário se preocupar com refatorar o código em uma das áreas anteriores. Ou pior, duplicar o código por medo de a refatoração desfigurar a funcionalidade existente. Afe.
Considerações sobre o design
-
Convenções de nomenclatura: a camada de serviço precisa ser abstrata o suficiente para ser relevante para vários clientes. Esse aspecto normalmente aparece nos verbos e substantivos usados nos nomes de classes, métodos e parâmetros. Faça com que eles sejam expressos em termos gerais do aplicativo ou da tarefa em vez de estarem relacionados a um chamador de cliente específico. Por exemplo, esse nome de método se baseia na operação comercial
InvoiceService.calculateTax(...)
enquanto esse nome de método se baseia em uma operação de uso do cliente específicaInvoiceService.handleTaxCodeForACME(...)
. O nome do segundo método deve causar algum desconforto.
-
Compatibilidade com plataforma/chamador: crie assinaturas de método que sejam compatíveis com as melhores práticas da plataforma, especialmente massificação. Uma das maiores preocupações com código no Salesforce é a massificação. Pense em serviços que podem ser chamados com listas em vez de conjuntos de parâmetros únicos. Por exemplo, os parâmetros deste método permitem a massificação
InvoiceService.calculateTax(List<TaxCalculation> taxCalculations)
enquanto esse método força os chamadores a chamar o método repetidamenteInvoiceService.calculateTax(Invoice invoice, TaxInfo taxCodeInfo)
. Mais uma vez: o segundo deve causar algum desconforto.
-
Considerações de SOC: o código da camada de serviço normalmente encapsula lógica de tarefa ou processo usando vários objetos no seu aplicativo. Pense nisso como um orquestrador. Ao contrário, o código relativo especificamente a validação, valores de campo ou cálculos, que ocorrem durante inserções de registro, atualizações e exclusões, é de responsabilidade do objeto relacionado. Esse código normalmente é escrito em acionadores do Apex e pode permanecer neles. Não se preocupe, apresentaremos o padrão Domain para esse tipo de código em breve.
-
Segurança: o código da camada de serviço e o código chamado por ele deve, por padrão, ser executado com a segurança do usuário aplicada. Para garantir isso, use o modificador
with sharing
(com compartilhamento) nas suas classes de serviço do Apex (particularmente importante se você estiver expondo esse código pelo modificador global). Se a lógica do Apex precisar acessar registros não incluídos na visibilidade do usuário, o código precisará elevar o contexto de execução explicitamente o mais rapidamente possível. Uma boa abordagem é usar uma classe do Apex interna privada com a aplicação do modificadorwithout sharing
(sem compartilhamento).
-
Marshalling: evite estipular como os aspectos da interação com a camada de serviço devem ser tratados porque é melhor que alguns aspectos sejam tratados pelos chamadores do serviço, por exemplo, semântica, como tratamento de erros e mensagens. Os chamadores costumam ter seus próprios meios de interpretar e tratar deles. Por exemplo, o Visualforce usa
<apex:pagemessages>
e os trabalhos de agenda provavelmente usarão emails, publicações do Chatter ou logs para comunicar erros. Assim, neste caso, é melhor aproveitar a semântica de tratamento de erros padrão do Apex gerando exceções. Como alternativa, seu serviço pode oferecer feedback de atualização de banco de dados parcial para o chamador. Nesse caso, conceba uma classe do Apex adequada e retorne uma lista desse tipo. O métodoDatabase.insert
do sistema é um bom exemplo desse tipo de assinatura de método.
-
Serviços compostos: embora os clientes possam executar várias chamadas de serviço em sequência, isso pode ser ineficiente e gerar problemas transacionais no banco de dados. É melhor criar serviços compostos que agrupam várias chamadas de serviço internamente em uma mesma chamada de serviço. Também é importante assegurar que a camada de serviço esteja o mais otimizada possível em relação ao uso de SOQL e DML. Isso não significa que serviços mais específicos não possam ser expostos; apenas que você deve dar aos chamadores a opção de usar um serviço único mais específico, se necessário.
-
Gerenciamento de transações e falta de estado: os clientes da camada de serviço costumam ter requisitos diferentes em relação à longevidade do processo realizado e às informações gerenciadas. Por exemplo, uma única solicitação ao servidor e várias solicitações divididas em escopos diferentes: o estado de gerenciamento (como o Apex em lote) ou uma interface de usuário complexa que mantenha seu próprio estado de página ao longo de várias solicitações. Considerando essas variações no gerenciamento de estados, é melhor encapsular operações de banco de dados e estado de serviço na chamada do método para a camada de serviço. Em outras palavras: tornar o serviço sem estado para dar aos contextos de chamada a flexibilidade de empregar suas próprias soluções de gerenciamento de estados. O escopo de uma transação com o banco de dados deve também estar contido em cada método de serviço para que o chamador não tenha que pensar nisso com seus próprios
SavePoints
, por exemplo.
-
Configuração: pode haver substituições comportamentais ou de configuração comuns em uma camada de serviço, por exemplo, o fornecimento de controle para permitir que o cliente instrua a camada de serviço a não confirmar alterações ou enviar emails. Esse cenário pode ser útil quando o cliente está implementando versões prévias ou funcionalidades do tipo “e se”. Pense em como implementar isso consistentemente, talvez como uma sobrecarga de método que usa um parâmetro Options compartilhado, semelhante aos métodos DML no Apex.
Como usar serviços no Apex
Vejamos como você pode usar o método OpportunitiesService.applyDiscounts
em vários lugares. Os componentes do Lightning e o Apex em lote são todos apresentados abaixo.
O exemplo a seguir lida com uma única Opportunity (Oportunidade) selecionada por meio de um componente do Lightning. Imagine que você tem um componente do Lightning que solicita ao usuário uma porcentagem de desconto a ser aplicada a um Opportunity Amount (Valor da oportunidade) selecionado. Observe que o tratamento dos erros é feito nesse estágio, e não no serviço, porque os componentes do Lightning têm sua própria maneira de exibir erros.
@AuraEnabled public void applyDiscount(Id opportunityId, Decimal discountPercentage) { try { // Apply discount entered to the current Opportunity OpportunitiesService.applyDiscounts( new Set<ID> { opportunityId }, discountPercentage); } catch(Exception e) { throw new AuraHandledException('Something went wrong: ' + e.getMessage()); } }
O exemplo a seguir lida com o processamento de blocos de registros por meio do método execute do Apex em lote. Se você examinar bem, verá que o tratamento de exceções é diferente do exemplo de componente do Lightning anterior.
public with sharing class OpportunityApplyDiscountJob implements Database.Batchable<SObject> { public Decimal DiscountPercentage {get;private set;} public OpportunityApplyDiscountJob(Decimal discountPercentage) { // Discount to apply in this job this.DiscountPercentage = discountPercentage; } public Database.QueryLocator start(Database.BatchableContext ctx) { // Opportunities to discount return Database.getQueryLocator( 'SELECT Id FROM Opportunity WHERE StageName = \'Negotiation/Review\''); } public void execute(Database.BatchableContext BC, List<sObject> scope) { try { // Call the service OpportunitiesService.applyDiscounts( new Map<Id,SObject>(scope).keySet(),DiscountPercentage); } catch (Exception e) { // Email error, log error, chatter error etc.. } } public void finish(Database.BatchableContext ctx) { } }
Em uma unidade posterior, veremos como expor um método Service por meio da API REST.
Outros benefícios e considerações de uma camada de serviço do Apex
Fora do escopo desta unidade está o tópico de implementar serviços para testes simulados e desenvolvimento paralelo. Os serviços podem usar o padrão factory junto com interfaces do Apex para resolver a implementação dinamicamente em vez de codificá-la diretamente nos métodos. Essa abordagem é útil para dar mais flexibilidade na engenharia do escopo de testes envolvendo serviços. No entanto, as factories exigem algum serviço de bastidores para criar interfaces, formas de registrar classes e outros itens divertidos em seu código. Saiba que seu uso agrega valor em termos de simulações e flexibilidade do tempo de execução com base nas configurações.
Além disso, a definição do design da camada de serviço previamente permite que os desenvolvedores ou equipes de desenvolvimento trabalhem melhor em conjunto ou paralelamente. Quem precisar chamar os serviços poderá usar implementações fictícias para retornar dados estáticos e quem precisar implementar os serviços poderá trabalhar no código sem causar impacto nos chamadores. Esse estilo de desenvolvimento costuma ser chamado de Programação por contrato (Design by contract - Dbc) e é lindo.
Resumo
Investir em uma camada de serviço para seu aplicativo oferece os benefícios de engenharia de maior reutilização e adaptabilidade, além de permitir uma maneira mais simples e com melhor custo-benefício de implementar uma API para seu aplicativo, algo essencial no mundo de hoje, integrado na nuvem. Ao observar as considerações de encapsulamento e design descritas acima, você começará a criar um núcleo durável para seu aplicativo que resistirá e permanecerá um investimento robusto nestes tempos inovadores e em constante mudança!
Recursos
- Separação de preocupações (Wikipedia)
- Padrão de camada de serviço de Martin Fowler
- Padrões de arquitetura empresarial de Martin Fowler