Skip to main content

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.
Nota

Nota

Deseja aprender em português (Brasil)? Comece o desafio em um Trailhead Playground de português (Brasil) e use as traduções fornecidas entre parênteses para navegar. Copie e cole somente os valores em inglês porque as validações dos desafios dependem de dados em inglês. Se você não passar no desafio em sua organização de português (Brasil), recomendamos que (1) mude o local para os Estados Unidos, (2) mude o idioma para inglês, seguindo as instruções aqui, e (3) clique novamente no botão “Validar o desafio”.

Consulte o emblema Trailhead no seu idioma para saber mais sobre como aproveitar a experiência de Trailhead em outros idiomas.

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

Compartilhe seu feedback do Trailhead usando a Ajuda do Salesforce.

Queremos saber sobre sua experiência com o Trailhead. Agora você pode acessar o novo formulário de feedback, a qualquer momento, no site Ajuda do Salesforce.

Saiba mais Continue compartilhando feedback