Aplicar os princípios de camada de serviço no Apex
Objetivos de aprendizagem
Após concluir esta unidade, você estará apto a:
- Criar uma classe Service do Apex e usá-la efetivamente em seu aplicativo.
- Expor uma classe Service do Apex como uma API.
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 38:48 minutos, caso você queira retroceder e ver o início da etapa novamente.)
Como criar serviços
Vamos ver um pouco de código! Se você está usando um encapsulamento total do banco de dados e do gerenciamento de estados, uma das abordagens de implementação é usar uma classe adequadamente nomeada com métodos estáticos que representam as operações do serviço.
Os métodos na sua classe representam as operações de serviço, que acessam as informações necessárias por meio do ambiente e dos parâmetros transmitidos. A lógica no método atualiza o banco de dados ou retorna informações no tipo de retorno do método usando exceções personalizadas do Apex para indicar falha. O exemplo a seguir mostra um serviço aplicando determinado desconto a um conjunto de oportunidades (e itens de linha, se houver).
public with sharing class OpportunitiesService { public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) { // Validate parameters if(opportunityIds==null || opportunityIds.size()==0) throw new OpportunityServiceException('Opportunities not specified.'); if(discountPercentage<0 || discountPercentage>100) throw new OpportunityServiceException('Invalid discount to apply.'); // Query Opportunities and Lines (SOQL inlined for this example, see Selector pattern in later module) List<Opportunity> opportunities = [SELECT Amount, (SELECT UnitPrice FROM OpportunityLineItems) FROM Opportunity WHERE Id IN :opportunityIds]; // Update Opportunities and Lines (if present) List<Opportunity> oppsToUpdate = new List<Opportunity>(); List<OpportunityLineItem> oppLinesToUpdate = new List<OpportunityLineItem>(); Decimal factor = 1 - (discountPercentage==null ? 0 : discountPercentage / 100); for(Opportunity opportunity : opportunities) { // Apply to Opportunity Amount if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) { for(OpportunityLineItem oppLineItem : opportunity.OpportunityLineItems) { oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor; oppLinesToUpdate.add(oppLineItem); } } else { opportunity.Amount = opportunity.Amount * factor; oppsToUpdate.add(opportunity); } } // Update the database SavePoint sp = Database.setSavePoint(); try { update oppLinesToUpdate; update oppsToUpdate; } catch (Exception e) { // Rollback Database.rollback(sp); // Throw exception on to caller throw e; } } public class OpportunityServiceException extends Exception {} }
Se aplicarmos a consideração de design de configuração descrita anteriormente, podemos adicionar uma versão sobrecarregada do serviço acima com um parâmetro options, que permite ao chamador instruir o serviço para ignorar a confirmação do trabalho. O retorno dos valores descontados permite ao cliente implementar uma visualização dos descontos que seriam aplicados.
public static List<Decimal> applyDiscounts( List<Id> opportunityIds, Decimal discountPercentage, Options config)
A assinatura do método no exemplo completo acima usa uma lista de IDs, que também está de acordo com as considerações de design. No entanto, somente um parâmetro único para o desconto foi usado. O pressuposto é que o mesmo desconto se aplica a todas as oportunidades. No entanto, se for necessário permitir descontos diferentes para cada oportunidade, você pode usar uma classe parameter, como mostrado aqui.
public class OpportunityService { public class ApplyDiscountInfo { public Id OpportunityId; public Decimal DiscountPercentage; } public static void applyDiscounts(List<ApplyDiscountInfo> discInfos) { // Implementation... } }
Como expor serviços como APIs
Todo mundo ama APIs! É essencial expor sua lógica de serviço a terceiros por meio de uma API em seu aplicativo para desenvolver um ecossistema sólido de inovação e integrações de parceiros em torno de seus produtos.
Se você considerar sua camada de serviço como totalmente testada, sólida e aberta a qualquer cliente (e por que não estaria, já que você também está usando, não é?), a maneira mais simples de expô-la aos desenvolvedores do Apex é alterar os modificadores de método e classe de public
para global
. Bingo!
global class OpportunityService { global class ApplyDiscountInfo { global Id OpportunityId; global Decimal DiscountPercentage; } global static void applyDiscounts(List<ApplyDiscountInfo> discInfos) { // Implementation... } }
No entanto, nada na vida é tão simples quanto parece. Se você está criando um pacote do AppExchange, o uso de global tem implicações na hora de mudar assinaturas de método entre versões. Entenda bem essas implicações.
Também convém considerar expor sua API a chamadores fora da plataforma, como dispositivos móveis ou Internet das Coisas. Uma forma de fazer isso é por meio do protocolo REST. Veja uma implementação da API REST do Apex personalizada.
@RestResource(urlMapping='/opportunity/*/applydiscount') global with sharing class OpportunityApplyDiscountResource { @HttpPost global static void applyDiscount(Decimal discountPercentage) { // Parse context RestRequest req = RestContext.request; String[] uriParts = req.requestURI.split('/'); Id opportunityId = uriParts[2]; // Call the service OpportunitiesService.applyDiscounts( new Set<Id> { opportunityId }, discountPercentage); } }
A ID da oportunidade é tirada do URI e a porcentagem de desconto é tirada da informação publicada. Assim como no exemplo de JavaScript Remoting na unidade anterior, o tratamento de exceções é deixado a cargo do chamador. A plataforma faz marshalling de exceções na resposta JSON ou XML pertinente.
Pro Tip: considere expor alguns métodos invocáveis para que os usuários do Flow Builder possam acessar sua funcionalidade da camada de serviço sem ter que escrever código. Plataformas são incríveis, não é?
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