Skip to main content

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 Aplicações Corporativas, 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 Force.com 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 do Lightning ou 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 plataforma Force.com.

Maneiras de invocar a lógica do Apex na plataforma Force.com: controladores de interface de usuário do Apex, serviços da Web do Apex, serviços REST do Apex, métodos invocáveis, manipuladores de email recebido, Apex em lote, Apex agendado e que permite a execução em fila.

Nota

Nota

Os acionadores do Apex não estão entre eles porque a lógica pertence à camada de domínio do aplicativo, que está alinhada estritamente com os objetos, ou seja, com a manipulação dos registros no aplicativo. A lógica de domínio é chamada direta e indiretamente na camada de serviço e, claro, pela interface de usuário e pelas APIs da plataforma.

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 Force.com. 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ífica InvoiceService.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 Force.com é 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 repetidamente InvoiceService.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 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 modificador 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étodo Database.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.
Nota

Nota

No Apex, as transações de bancos de dados são confirmadas automaticamente se a solicitação for concluída sem erro, e revertida em caso de exceção não tratada. No entanto, permitir a conclusão de uma solicitação com geração de erros não é uma experiência de usuário desejável porque a plataforma que lida com essas exceções normalmente não é tão acessível (trabalhos do Apex em lote) ou esteticamente aprazível (página em branco, texto em preto) para usuários finais. Por isso, os desenvolvedores costumam pegar exceções e direcioná-las conforme o caso. Um possível efeito colateral dessa abordagem é que a plataforma vê isso como uma conclusão válida da solicitação e confirma registros que foram inseridos ou atualizados e levaram ao erro. Seguindo os princípios de design da camada de serviço acima em relação ao gerenciamento de transações e falta de estado, você pode contornar esse problema.

Como usar serviços no Apex

Vamos ver um pouco de código. Imagine que você tem um botão personalizado no layout de oportunidade que, quando pressionado, exibe uma página do Visualforce que pede uma porcentagem de desconto ao usuário para aplicar ao valor da oportunidade ou, se houver, aos itens de linha de oportunidade associados.

Vejamos como você pode usar o método OpportunitiesService.applyDiscounts em vários lugares. Visualforce, Apex em lote e JavaScript Remoting são todos mostrados abaixo. O exemplo a seguir lida com uma única oportunidade selecionada por meio de um StandardController. Observe que o tratamento de erros do controlador é feito pelo controlador, não pelo serviço, porque o Visualforce tem sua própria maneira de revelar erros.

public PageReference applyDiscount() {
    try {
        // Apply discount entered to the current Opportunity
        OpportunitiesService.applyDiscounts(
            new Set<ID> { standardController.getId() }, DiscountPercentage);
    } catch (Exception e) {
        ApexPages.addMessages(e);
    }          
    return ApexPages.hasMessages() ?null :standardController.view();
}

O exemplo a seguir lida com várias oportunidades por meio de um StandardSetController.

public PageReference applyDiscounts() {
    try {
        // Apply discount entered to the selected Opportunities
        OpportunitiesService.applyDiscounts(
           // Tip:Creating a Map from an SObject list gives easy access to the Ids (keys)
           new Map<Id,SObject>(standardSetController.getSelected()).keyValues(),
           DiscountPercentage
        );
    } catch (Exception e) {
        ApexPages.addMessages(e);
    }          
    return ApexPages.hasMessages() ?null :standardController.view();               
}

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 controlador do Visualforce 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) { }    
}

O exemplo agora encapsula o método Service e o expõe por meio do JavaScript Remoting. Aqui, as exceções não são pegas porque o JavaScript Remoting faz o marshalling interno das exceções quando geradas. Queremos aproveitar isso, transmitindo-as para o código JavaScript do cliente para pegar usando as instalações internas.

public class OpportunityController {
    @RemoteAction
    public static void applyDiscount(Id opportunityId, Decimal discountPercent) {
        // Call service
        OpportunitiesService.applyDiscounts(new Set<ID> { opportunityId }, discountPercent);
    }
}

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

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