Conectar ao Salesforce com os controladores do lado do servidor

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:
  • Criar métodos do Apex que podem ser chamados remotamente a partir do código dos componentes do Aura.
  • Fazer chamadas de componentes do Aura para métodos remotos.
  • Tratar de respostas do servidor de forma assíncrona usando funções de retorno de chamada.
  • Meta ambiciosa: explicar a diferença entre “c.”, “c:” e “c.”.

Conceitos de controlador do lado do servidor

Até agora, tudo o que fizemos tem sido estritamente no lado do cliente. Ainda não salvamos nossas despesas no Salesforce. Criamos algumas despesas, recarregamos e o que acontece? É isso, todas as despesas desaparecem. Oba, dinheiro de graça!

Só que o departamento de contabilidade ligou e, bem, eles não estão muito felizes com esse tipo de coisa. E, na verdade, não queremos ser reembolsados em relação a essas despesas, já que estão saindo do nosso bolso...? Ui! Salvar nossos dados no Salesforce é com certeza uma prioridade!

Falando sério agora, finalmente, chegou a hora de adicionar controladores do lado do servidor ao nosso aplicativo. Temos evitado falar disso enquanto víamos as noções básicas. Agora que você está pronto, vamos nessa!

E “nessa” significa ver algumas fotos. Precisamos ter certeza de que sabemos para onde estamos indo e que nosso tanque está cheio antes de pegar a estrada.

Primeiro, vamos revisitar o primeiro diagrama que vimos nesse módulo, uma visão de (muito) alto nível sobre a arquitetura dos aplicativos de componentes do Lightning.

Uma arquitetura de muito alto nível dos componentes do Lightning: controlador e modo de exibição do cliente, controlador do Apex de servidor e banco de dados.

Até agora, tudo o que vimos estava no lado do cliente nessa imagem. (Observe que simplificamos ao mesclar controladores e auxiliares aqui.) Embora tenhamos referenciado um tipo de objeto personalizado, Expense_c, que é definido no lado do servidor, nunca tocamos no servidor diretamente.

Lembra de quando falamos sobre conectar diferentes elementos para criar um circuito completo? O formulário de despesas que criamos na última unidade fica mais ou menos assim.

Lado do cliente no fluxo

O circuito começa com o botão Criar, que está conectado ao manipulador de ação clickCreate (1). Quando o manipulador de ação é executado, ele recebe valores de fora dos campos do formulário (2) e adiciona uma nova despesa à matriz expenses (3). Quando a matriz é atualizada com set, ela aciona a renderização automática da lista de despesas (4), concluindo o circuito. Simples, não é?

Bem, quando conectamos o acesso do lado do servidor, o diagrama fica um pouco mais complicado. Mais setas, mais cores, mais números! (Não vamos explicar tudo isso no momento.)

Fluxo completo: lado do cliente e lado do servidor

E mais: esse circuito não tem o mesmo fluxo de controle suave e síncrono. As chamadas do servidor são caras e podem demorar. Milissegundos, quando tudo está bem, e longos segundos, quando a rede está congestionada. Você não quer que os aplicativos fiquem travados aguardando respostas do servidor.

A solução para permanecer responsivo enquanto aguarda é ter as respostas do servidor tratadas de forma assíncrona. Isso significa que, ao clicar no botão Criar despesa, o controlador do lado do cliente acionará uma solicitação ao servidor e continuará o processamento. Não só ele não aguarda o servidor, como sequer se lembra que fez a solicitação!

Em seguida, quando a resposta volta do servidor, o código que estava empacotado com a solicitação, chamado de função de retorno de chamada, é executado e trata da resposta, inclusive atualizando dados do lado do cliente e a interface de usuário.

Se você é um programador de JavaScript experiente, sabe certamente usar as funções de retorno de chamada e execução assíncrona. Se você nunca trabalhou com elas, as coisas serão novas e, talvez, bem diferentes. E também muito incríveis.

Consultar dados no Salesforce

Vamos começar com a leitura de dados do Salesforce, para carregar a lista de despesas existentes quando o aplicativo Despesas (Expenses) for iniciado.

Nota

Nota

Se você ainda não criou alguns registros reais de despesas no Salesforce, agora é uma boa hora. Do contrário, ao implementar o que se segue, você precisará perder tempo depurando para saber por que nada está carregando, quando, na verdade, não há nada a carregar. Este autor cai nessa, O... Tempo... Inteiro.

A primeira etapa é criar seu controlador do Apex. Os controladores do Apex contêm métodos remotos que podem ser chamados pelos componentes do Lightning. Nesse caso, para consultar e receber dados de despesas do Salesforce.

Vamos dar uma olhada em uma versão simplificada do código. No Console do desenvolvedor, crie uma nova classe do Apex chamada “ExpensesController” e cole-a no código a seguir.

public with sharing class ExpensesController {
    // STERN LECTURE ABOUT WHAT'S MISSING HERE COMING SOON
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
}

Vamos ver os controladores do Apex em mais detalhe na próxima seção, mas, por enquanto, este é um método do Apex extremamente direto. Ele executa uma consulta SOQL e retorna os resultados. Existem somente duas coisas específicas que disponibilizam esse método para seu código dos componentes do Lightning.

  • A anotação @AuraEnabled antes da declaração do método. “Aura” é o nome da estrutura que é a chave dos componentes do Lightning. Você já viu esse nome sendo usado no namespace de algumas marcas essenciais, como <aura:component>. Agora, você sabe de onde isso vem.
  • A palavra-chave static. Todos os métodos do controlador @AuraEnabled precisam ser métodos estáticos e com escopo public ou global.

Se esses requisitos lembram você de métodos remotos para o recurso remoto de JavaScript do Visualforce, isso não é uma coincidência. Os requisitos são os mesmos porque a arquitetura é muito semelhante em pontos-chave.

Outra observação importante é que o método não faz nada de especial para empacotar os dados para os componentes do Lightning. Ele apenas retorna os resultados da consulta SOQL diretamente. A estrutura dos componentes do Lightning lida com todo o trabalho de manobra envolvido na maioria dessas situações. Legal!

Carregar dados do Salesforce

A próxima etapa é conectar o componente expenses ao controlador do Apex do lado do servidor. Isso é tão fácil que você vai ficar tonto. Mude a marca de abertura <aura:component> do componente expenses para apontar para o controlador do Apex, assim:

<aura:component controller="ExpensesController">

A nova parte fica destacada em negrito e, sim, é mesmo muito simples.

No entanto, apontar para o controlador do Apex não faz com que os dados sejam efetivamente carregados ou o método remoto seja chamado. Assim como a conexão automática entre o componente e o controlador (do lado do cliente), apontar simplesmente faz com que essas duas partes “saibam da existência” uma da outra. Esse “saber da existência” tem o mesmo formato, outro provedor de valor, que veremos daqui a pouco. Mas a conexão automática é limitada. Continua sendo nossa responsabilidade concluir o circuito.

Nesse caso, a conclusão do circuito significa o seguinte:

  1. Quando o componente expenses é carregado,
  2. Consultar o Salesforce em relação a registros de despesa existentes e
  3. Adicionar os registros ao atributo do componente expenses.

Faremos uma coisa de cada vez. O primeiro item, acionar um comportamento quando o componente expenses é carregado pela primeira vez, exige a criação de um manipulador init. Este é apenas um termo que designa um manipulador de ação que está conectado a um evento init do componente, que ocorre quando o componente é criado pela primeira vez.

A conexão necessária exige uma única linha de marcação. Adicione o que se segue ao componente expenses, logo abaixo das definições de atributo do componente.

<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>

A marca <aura:handler> é como você diz que um componente pode tratar de um evento específico. Nesse caso, estamos dizendo que trataremos do evento init e que trataremos dele com o manipulador de ação doInit em nosso controlador. (Definir value="{!this}" irá marcar isso como um “valor de evento”. O que isso significa é muito complexo para explicar aqui. Saiba apenas que você sempre deve adicionar esse par atributo/valor a um evento init.)

Chamar métodos do controlador do lado do servidor

Uma etapa já foi; faltam duas. As etapas restantes são realizadas no manipulador de ação doInit e, portanto, vamos dar uma olhada nele. Adicione o código a seguir ao controlador do componente expense.

    // Load expenses from Salesforce
    doInit: function(component, event, helper) {
        // Create the action
        let action = component.get("c.getExpenses");
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            let state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.expenses", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
        // Send action off to be executed
        $A.enqueueAction(action);
    },

Antes de se sentir perdido com todas essas novidades, saiba que esse é só mais um manipulador de ação. A formatação é a mesma e a assinatura de função é a mesma. Estamos em território conhecido.

Dito isso, todas as linhas de código depois da assinatura de função são novas. Vamos vê-las daqui a pouco, mas veja um resumo do que esse código faz:

  1. Cria uma chamada de método remoto.
  2. Configura o que deve acontecer quando o método remoto retorna a chamada.
  3. Enfileira a chamada do método remoto.

Parece bem simples, não é? Talvez a estrutura ou as especificidades do código sejam novas, mas os requisitos básicos do que precisa acontecer são conhecidos.

Isso soa a encorajamento? Como se estivéssemos tentando ajudar você a passar por um obstáculo complicado? Bom, temos algo a dizer. O problema aparece na primeira linha do código na função.

        let action = component.get("c.getExpenses");

Essa linha de código cria nossa chamada do método remoto ou ação remota. E, a princípio, a parte component.get() parece familiar. Já fizemos isso várias vezes.

A diferença é que... antes obtínhamos “v.algo”, em que v era o provedor de valor para o modo de exibição. Aqui, temos “c” e, sim, c é outro provedor de valor. E já vimos um provedor de valor c antes, em expressões como press="{!c.clickCreate}" e action="{!c.doInit}".

Essas expressões estavam em uma marcação de componente, no modo de exibição. Aqui, no controlador, o provedor de valor c representa algo diferente. Ele representa o controlador do Apex remoto.

“Um momento... Você está me dizendo que temos um c para controlador do lado do cliente, c para namespace padrão e c para controlador do lado do servidor, todos nos componentes do Aura?”

Bem, resumindo: sim. Respire fundo.

Vamos ser sinceros com você. Se pudéssemos fazer tudo de novo, teríamos feito algumas escolhas diferentes. Embora as escolhas feitas não tenham sido por acidente, três “c” definitivamente abrem espaço para confusão. Também ficamos confusos!

Mas, como dizem, as coisas são o que são. Você não está sendo pego de surpresa. Agora, você sabe.

Identificador
Contexto
Significado
c.
Marcação do componente
Controlador do lado do cliente
c.
Código do controlador
Controlador do lado do servidor
c:
Marcação
Namespace padrão

OK, voltemos ao código. Antes de abrirmos um parêntese, estávamos examinando esta linha.

        let action = component.get("c.getExpenses");

Em que, em um código anterior, component.get("v.algo") nos retornava uma referência a um componente secundário no modo de exibição (marcação do componente); component.get("c.oquequerqueseja") retorna uma referência a uma ação disponível no controlador. Nesse caso, ele retorna uma chamada de um método remoto a nosso controlador do Apex. É assim que se cria uma chamada para um método @AuraEnabled.

A “linha” a seguir, action.setCallback(...), é um bloco de código que será executado quando a chamada do método remoto retornar. Como isso acontece “mais tarde”, vamos deixar isso de lado por enquanto.

A próxima linha que é realmente executada é esta.

        $A.enqueueAction(action);

Já vimos $A rapidamente antes, mas não nos aprofundamos. Ela é uma variável global de estrutura que fornece várias funções e serviços importantes. $A.enqueueAction(action) adiciona a chamada ao servidor que acabamos de configurar à fila de solicitações de estrutura dos componentes do Aura. Ela, junto com outras solicitações de servidor pendentes, será enviada ao servidor no ciclo de solicitações seguinte.

Isso soa meio vago. Os detalhes completos são interessantes e importantes para o uso avançado dos componentes do Aura. Mas, no momento, eis tudo o que você precisa saber sobre $A.enqueueAction (action).

  • Ela enfileira a solicitação ao servidor.
  • Em relação à sua ação do controlador, isso é tudo.
  • Você não sabe quando ou se receberá resposta.

É aqui que entra aquele bloco de código que separamos antes. Mas, antes de entrarmos nesse assunto, um pouco de cultura pop.

Chamadas ao servidor, execução assíncrona e funções de retorno de chamada

A música da Carly Rae Jepsen, “Call Me Maybe”, foi lançada em 2011 e alcançou o topo das paradas em mais de uma dezena de países. Até hoje, ela vendeu mais de 18 milhões de cópias no mundo todo e, aparentemente, é uma das músicas mais vendidas em formato digital de todos os tempos. A linha mais lembrada, do refrão, é “Here’s my number. So call me maybe.” (Aqui está o meu número. Qualquer coisa, me liga.) Além de ser uma música animada e que gruda na cabeça, serve como metáfora de como os componentes do Aura lidam com chamadas ao servidor.

Preste atenção. Vejamos nosso manipulador de ação em pseudocódigo.

    doInit: function(component, event, helper) {
        // Load expenses from Salesforce
        let action = component.get("c.getExpenses");
        action.setCallback(
            // Here’s my number,
            // Call me maybe
        );
        $A.enqueueAction(action);
    },

Hum. Talvez devêssemos explicar os parâmetros de action.setCallback() com mais detalhes. No verdadeiro código do manipulador de ação, nós o chamamos assim:

        action.setCallback(this, function(response) { ... });

this é o escopo no qual o retorno de chamada será executado; aqui, this é a própria função do manipulador de ação. Pense nele como um endereço ou... talvez um número. A função é o que é chamado quando a resposta do servidor retorna. Sendo assim:

        action.setCallback(scope, callbackFunction);

Aqui está o meu número. Qualquer coisa, me liga.

O efeito geral é criar a solicitação, empacotar o código para o que fazer quando a solicitação for concluída e enviá-la para execução. Aqui, o próprio manipulador de ação para de funcionar.

Existe outra maneira de pensar nisso. Você pode agasalhar seus filhos para irem ao colégio e entregar uma lista de tarefas que deseja que façam quando voltarem de lá. Você os deixa no colégio e vai para o trabalho. Enquanto você está no trabalho, está fazendo seu trabalho com a certeza de que seus filhos, sendo boas crianças, farão o trabalho atribuído a eles quando voltarem do colégio. Você não faz esse trabalho e não sabe exatamente quando ele será feito. Mas ele é feito.

Uma última forma de ver isso, novamente em pseudocódigo. Essa versão “desencapsula” a função de retorno de chamada para mostrar uma versão mais linear do manipulador de ação.

    // Not real code! Do not cut-and-paste!
    doInit: function(component, event, helper) {
        // Create server request
        let action = component.get("c.getExpenses");
        // Send server request
        $A.enqueueAction(action);
        // ... time passes ...
        // ...
        // ... Jeopardy theme plays ...
        // ...
        // ... at some point in the indeterminate future ...
        // Handle server response
        let state = action.response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", action.response.getReturnValue());
        }
    },

Dizemos novamente: as funções de retorno de chamada e execução assíncrona são, normalmente, para programadores em JavaScript, mas se você tem outro tipo de experiência, elas não serão tão conhecidas. Esperamos que você já as tenha entendido aqui, pois são fundamentais para desenvolver aplicativos com os componentes do Lightning.

Lidar com a resposta do servidor

Agora que entendemos a estrutura para criar uma solicitação de servidor, vamos dar uma olhada nos detalhes de como nossa função de retorno de chamada realmente lida com a resposta. Vejamos somente a função de retorno de chamada.

    function(response) {
        let state = response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", response.getReturnValue());
        }
    }

As funções de retorno de chamada usam um único parâmetro, response, que é um objeto opaco que fornece os dados retornados, se houver, e vários detalhes sobre o status da solicitação.

Nessa função específica de retorno de chamada, fazemos o seguinte:

  1. Obtemos o estado da resposta.
  2. Se o estado for SUCESSO, ou seja, se nossa solicitação foi concluída da maneira esperada, então:
  3. Definimos o atributo expenses do componente como o valor dos dados de resposta.

Você provavelmente tem algumas questões, como:

  • O que acontece se o estado da resposta não é SUCESSO?
  • O que acontece se a resposta nunca chega? (Qualquer coisa, me liga.)
  • Como podemos atribuir os dados de resposta ao atributo do componente?

A resposta às duas primeiras perguntas é que, infelizmente, não vamos abordar essas possibilidades neste módulo. Elas são certamente coisas que você precisa saber e considerar nos aplicativos da vida real, mas não temos tempo.

A última pergunta é a mais relevante aqui, mas também é a mais fácil de responder. Definimos um tipo de dados para o atributo expenses.

<aura:attribute name="expenses" type="Expense__c[]"/>

E nossa ação do controlador do lado do servidor tem uma assinatura de método que define seu tipo de dados de retorno.

public static List<Expense__c> getExpenses() { ... }

Os tipos correspondem e, portanto, basta atribuir um ao outro. Os componentes do Aura tratam de todos os detalhes. Você pode certamente fazer seu próprio processamento dos resultados e transformá-los em outros dados em seu aplicativo. Mas, se projetar as ações do lado do servidor corretamente, você não precisará fazer isso.

OK, existem várias maneiras diferentes de olhar para várias linhas de código. A questão é: você já experimentou a sua versão de nosso aplicativo dessa maneira? Porque já concluímos a parte de carregar despesas do Salesforce. Recarregue o aplicativo e veja se as despesas inseridas no Salesforce aparecem!

Controladores do Apex para componentes do Aura

Antes de irmos para a próxima etapa no desenvolvimento do aplicativo, vamos nos aprofundar mais um pouco no controlador do Apex. Vejamos a versão seguinte, da qual precisaremos para tratar da criação de novos registros e da atualização da caixa de seleção Reembolsado? nos registros existentes.

public with sharing class ExpensesController {
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Perform isAccessible() checking first, then
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
    @AuraEnabled
    public static Expense__c saveExpense(Expense__c expense) {
        // Perform isUpdateable() checking first, then
        upsert expense;
        return expense;
    }
}

A versão anterior prometia um discurso sério e isso vai acontecer. Mas, primeiro, vamos nos focar nos detalhes desta versão mínima.

Primeiro, adicionamos somente um novo método @AuraEnabled, saveExpense(). Ele usa um objeto de despesa (Expense__c) e o insere/atualiza (upsert). Isso nos permite usá-lo para criar novos registros e atualizar registros existentes.

Em seguida, observe que criamos a classe com as palavras-chave with sharing. Isso aplica automaticamente as regras de compartilhamento da sua organização aos registros que estão disponíveis por esses métodos. Por exemplo, os usuários normalmente só veriam seus próprios registros de despesas. O Salesforce lida com todas as regras complicadas de SOQL para você, automaticamente, nos bastidores.

O uso das palavras-chave with sharing é uma das medidas essenciais de segurança que você precisa tomar ao escrever um código para controlador do lado do servidor. No entanto, é uma medida necessária, mas insuficiente. Você vê os comentários sobre como executar verificações IsAccessible() e IsUpdateable()? with sharing só vai até aí. Mais especificamente, você precisa implementar a segurança em nível de objeto e de campo (que vê frequentemente abreviada como FLS) por conta própria.

Por exemplo, eis uma versão de nosso método getExpenses() com essa segurança implementada minimamente.

    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Check to make sure all fields are accessible to this user
        String[] fieldsToCheck = new String[] {
            'Id', 'Name', 'Amount__c', 'Client__c', 'Date__c',
            'Reimbursed__c', 'CreatedDate'
        };
        Map<String,Schema.SObjectField> fieldDescribeTokens =
            Schema.SObjectType.Expense__c.fields.getMap();
        for(String field : fieldsToCheck) {
            if( ! fieldDescribeTokens.get(field).getDescribe().isAccessible()) {
                throw new System.NoAccessException();
            }
        }
        // OK, they're cool, let 'em through
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }

É uma grande expansão, comparando com nosso código original de uma linha, mas é igualmente adequado. Além disso, as chamadas describe são caras. Se seu aplicativo chama esse método frequentemente, você deve encontrar uma maneira de otimizar ou armazenar as verificações de acesso em cache por usuário.

Como ocorre com SLDS, não temos tempo para ensinar todos os detalhes da codificação segura em Apex. E, diferentemente de SLDS, assumir a responsabilidade pela segurança do código que você escreve não é opcional. Se você ainda não leu os artigos sobre práticas seguras de codificação em Recursos, coloque-os na lista.

OK, </discurso-sério>.

Salvar dados no Salesforce

Antes de implementarmos o formulário Adicionar despesas de verdade, sem trapaça, vamos dar uma olhada em como a criação de um novo registro é um desafio diferente de ler registros existentes. Com doInit(), simplesmente lemos alguns dados e atualizamos a interface de usuário do aplicativo. Assim, bem direto, mesmo tendo dado o exemplo de Carly Rae para explicar melhor.

A criação de um novo registro é mais complexa. Vamos ler valores do formulário, criar um novo registro de despesa localmente, enviar esse registro para ser salvo no servidor e, quando o servidor nos informar que ele está salvo, atualizar a interface de usuário usando o registro retornado do servidor.

Parece que vai ser bem complicado? Que vamos precisar dos Rolling Stones e um álbum inteiro de músicas para nos ajudar durante a próxima explicação?

Vamos dar uma olhada em um código e você terá a resposta.

Primeiro, salve a versão atualizada do controlador do Apex, a versão anterior que inclui o método saveExpense().

Lembra-se de quando mostramos como lidar com o envio de formulários? Um campo inválido já é suficiente para que uma mensagem de erro seja exibida e o formulário não seja enviado. A mensagem de erro desaparece quando todos os campos forem válidos.

Como colocamos todos os detalhes (e as trapaças) da criação de uma nova despesa na função auxiliar createExpense(), não precisamos fazer outras alterações no controlador. Por enquanto, tudo fácil?

Então, tudo o que precisamos fazer é mudar a função createExpense() no auxiliar para fazer todas aquelas coisas complicadas que mencionamos anteriormente. É este o código.

    createExpense: function(component, expense) {
        let action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        action.setCallback(this, function(response){
            let state = response.getState();
            if (state === "SUCCESS") {
                let expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
        $A.enqueueAction(action);
    },

Ele é tão complicado quanto você esperava? Tantas linhas assim? Esperamos que não!

Na verdade, só existe uma nova coisa nesse manipulador de ação, e isso é fácil de entender. Vamos percorrer o código.

Vamos começar criando a ação, com component.get("c.saveExpense") obtendo o novo método do controlador do Apex. Bem familiar.

Em seguida, anexamos uma carga de dados à ação. Isso é novo. Precisamos enviar os dados da nova despesa para o servidor. Mas veja como é fácil! Basta usar action.setParams() e fornecer a um objeto do estilo JSON com pares de valor nome-parâmetro do parâmetro. Outro truque, e isso é importante, é que o nome do parâmetro precisa corresponder ao nome do parâmetro usado em sua declaração do método do Apex.

Em seguida, definimos o retorno de chamada para a solicitação. Mais uma vez, isso é o que acontecerá quando o servidor retornar uma resposta. Ao comparar essa função de retorno de chamada com nossa função auxiliar createExpense original, verá que elas são praticamente idênticas (menos a embrulhada lamentável).

Assim, como na versão anterior, usamos o método get() para obter o atributo expenses, enviamos um valor por push() para ele e usamos o método set() nele. A única diferença real é que, em vez de enviar por push() nossa versão local da nova experiência para a matriz, enviamos por push() a resposta do servidor!

Por que isso funciona? Porque o método no lado do servidor insere/atualiza o (nesse caso, novo) registro, que carimba uma ID nele e retorna o registro resultante. Mais uma vez, os tipos de dados no lado do cliente e no lado do servidor correspondem e, portanto, não precisamos fazer mais nada.

Bem, é isso. Nem precisamos dos Rolling Stones!

Cuidados a se tomar

Embora tenhamos visto todas as noções básicas para conectar o código dos componentes do Aura do lado do cliente com o código do Apex do lado do servidor, existem algumas coisas que merecem ser destacadas antes que elas venham te assombrar.

A primeira questão é a diferenciação de maiúsculas e minúsculas e, resumindo, Apex e Salesforce não diferenciam maiúsculas e minúsculas, em geral, mas o JavaScript diferencia. Ou seja, “Nome” e “nome” são a mesma coisa no Apex, mas são diferentes em JavaScript.

Isso pode causar erros absolutamente enlouquecedores que ficam totalmente invisíveis para você, mesmo quando estão na frente dos seus olhos. Principalmente, se você trabalhou com código de não componentes do Lightning no Salesforce por algum tempo, o costume é não pensar mais nas maiúsculas e minúsculas dos nomes de objeto e de campo, métodos e afins.

Assim, veja esta melhor prática: Sempre use o nome da API exato de cada objeto, campo, tipo, classe, método, entidade, elemento, elefante ou o que for. Sempre, em qualquer lugar, mesmo quando não fizer a menor diferença. Assim, você não terá problemas. Ou, pelo menos, não terá esse problema.

A outra questão que queríamos levantar é a natureza de “obrigatório”. Não podemos resistir em usar a famosa citação: “Você continua usando essa palavra. Não acho que signifique o que você acha que ela significa.”

No código que escrevemos até agora, vimos, pelo menos, dois tipos diferentes de “obrigatório” (required). Na marcação do formulário Adicionar despesas, você vê a palavra usada de duas maneiras. Por exemplo, no campo de nome da despesa.

<lightning:input aura:id="expenseform"
                 label="Expense Name"
                 name="expensename"
                 value="{!v.newExpense.Name}"
                 required="true"/> 

A marca <lightning:input> tem seu atributo required definido como true. Ambos ilustram somente um significado de obrigatório, que é “definir a interface de usuário desse elemento para indicar que o campo é obrigatório”. Em outras palavras, isso é superficial. Não existe proteção à qualidade dos seus dados aqui.

Outro significado da palavra “obrigatório” está ilustrado na lógica de validação que escrevemos para o mesmo campo.

let validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
    // Displays error messages for invalid fields
    inputCmp.showHelpMessageIfInvalid();
    return validSoFar && inputCmp.get('v.validity').valid;
}, true);

A palavra “obrigatório” não está em lugar nenhum, mas é o que a lógica de validação impõe. Você precisa definir um valor para o campo de nome da despesa.

E, até certo ponto, isso é ótimo. O seu formulário de despesas não enviará uma nova despesa com um nome em branco. A não ser que exista um erro. Ou talvez outro widget use o mesmo controlador do lado do servidor, mas não faz a sua validação de formulário com tanto cuidado. E assim por diante. Bem, isso é uma forma de proteção da qualidade dos dados, mas não é perfeita.

Como você impõe, e frisamos o impõe, uma regra de integridade de dados sobre o nome da despesa, neste exemplo? Fazendo isso no lado do servidor. E não é em qualquer lugar no lado do servidor. A regra é colocada na definição do campo ou é codificada como acionador. Ou, se você é um engenheiro que tem de fazer tudo certinho, como todos os bons engenheiros, você faz as duas coisas.

Para uma integridade de dados de verdade, quando “obrigatório” significar obrigatório, imponha-a no nível mais baixo possível.