Camada de Integração

Introdução

A integração entre frontend e backend ocorre via API (Application Programming Interface) através de endpoints webservice.

O consumo de webservices é implementado por meio de funções JavaScript, aqui chamadas de “Remotes”, descritas a seguir.

Estrutura de Arquivos

As funções de serviços remotos são organizadas por diretórios conforme o exemplo a seguir. Cada arquivo de módulo contém 1 (uma) função remota, relativa a determinado endpoint do backend.

remotes/
   arquetipo/                                     <-- nome do microsserviço
      apoio/                                      <-- assunto ou agrupador
         pesquisarMotivosCancelamentoRemote.js
         pesquisarSituacoesOsRemote.js
         pesquisarTiposExecutoresRemote.js
      executor/
         cadastrarExecutorRemote.js
         inativarExecutorRemote.js
         pesquisarExecutoresRemote.js
         reativarExecutorRemote.js
      ordemServico/
         atestarOrdemServicoRemote.js
         cancelarOrdemServicoRemote.js
         encaminharOrdemServicoRemote.js
         executarOrdemServicoRemote.js
         gerarOrdemServicoRemote.js
         glosarOrdemServicoRemote.js
         pesquisarOrdensServicoRemote.js

Remotes

A seguir são definidas as diretrizes para funções de serviços remotos.

  • Retornam Promise que resolve o objeto do JSON ou undefined para webservices que retornam corpo HTTP vazio.

  • Consomem somente 1 (um) endpoint remoto.

  • Tem nome igual ao nome da operação remota, sem o sufixo da versão do endpoint. Exemplo: a função pesquisarExecutores() consome o serviço pesquisarExecutores.vN.

  • São implementadas no arquivo de módulo JavaScript que recebe o mesmo nome da função mais o sufixo Remote. Exemplo: pesquisarExecutoresRemote.js.

  • São exportadas de maneira nomeada (Named Exports). Exemplo: export { pesquisarExecutores }.

  • O módulo JavaScript correspondente é localizado na pasta app/remotes/<nomeMicrosservicoRemovo>/<grupo>.

  • Não implementam regras de negócio.

  • Tratam erros de conexão.

  • Tratam erros HTTP, por exemplo, códigos 4xx e 5xx.

  • Utilizam o fetch nativo ou preferencialmente o componente da arquitetura Networking.fetch.

    • O Networking.fetch é uma extensão do fetch nativo que permite a injeção automática de parâmetros comuns, como o token de autenticação JWT e parâmetros adicionais na URL que identificação o nome e versão da aplicação de frontend.

Exemplo 1 (GET)

  • Função remota que pesquisa dados (GET) e retorna um DTO

  • Arquivo app/remotes/arquetipo/ordemServico/pesquisarOrdensServicoRemote.js

  • Endpoint do backend <baseUrl>/pesquisarOrdensServico.v3?descricao=<valor>&situacaoId=<valor>...

  • No fluxo de sucesso, o backend retorna código 200 e um JSON

Código-fonte:

import { validateResponse } from '@/app/util/remoteUtils';
import { globalConfig } from '@/app/globals/config';
import { Networking } from '@bernardo-dias/react-native-cloudsupport';

// Vide Swagger do backend
// Parâmetros: descricao, situacaoId, de, qtd, ordem
// Retorno: lista, de, qtd, total
const pesquisarOrdensServico = (params = {}) => {

    // Prepara os parâmetros 
    const urlParams = new URLSearchParams(params); 

    // Prepara a URL
    const url = `${globalConfig.wsArquetipo}/pesquisarOrdensServico.v3?${urlParams}${globalConfig.platformParam}`;

    // Executa a requisição
    return Networking.fetchWithTimeout(url, null, globalConfig.connectionTimeout)

    // Erro http
    .then(validateResponse)
    
    // Converte json para objeto
    .then(response => response.json());
};

export { pesquisarOrdensServico }

A função validateResponse realiza o tratamento de erros de conexão e de HTTP, vide exemplo ao final da seção.

Note que há return em Networking.fetchWithTimeout, para garantir que a camada de apresentação possa manter o encadeamento de Promises.

Exemplo 2 (POST)

  • Função remota que altera dados (POST) e retorna undefined

  • Arquivo app/remotes/arquetipo/ordemServico/encaminharOrdensServicoRemote.js

  • Endpoint do backend <baseUrl>/encaminharOrdemServico.v1?protocolo=<valor>&executorUid=<valor>...

  • No fluxo de sucesso, o backend retorna código 200 e corpo HTTP vazio

Código-fonte:

import { validateResponse } from '@/app/util/remoteUtils';
import { globalConfig } from '@/app/globals/config';
import { Networking } from '@bernardo-dias/react-native-cloudsupport';

// Vide Swagger do backend
// Parâmetros: protocolo, executorUid, observacao
// Retorno: (não se aplica)
const encaminharOrdemServico = (params = {}) => {

    // Prepara os parâmetros
    const postParams = JSON.stringify(params); 

    // Prepara a URL
    const url = `${globalConfig.wsArquetipo}/encaminharOrdemServico.v1?${globalConfig.platformParam}`;

    // Prepara options do fetch
    const options = {
        method: 'POST',
        body: postParams,
        headers: {'Content-Type': 'application/json'},
    };

    // Executa a requisição
    return Networking.fetchWithTimeout(url, options, globalConfig.connectionTimeout)

    // Erro http
    .then(validateResponse)
    
    // Sem retorno
    .then(_ => {});
};

export { encaminharOrdemServico }

A função validateResponse realiza o tratamento de erros de conexão e de HTTP, vide exemplo ao final da seção.

A estrutura _ => {} é apenas uma boa prática que evita que o objeto response seja exposto para a camada de apresentação, tal que a função remota retorná undefined, vez que o corpo da resposta HTTP, neste exemplo, é vazio.

Consulte a documentação oficial do JavaScript para conhecer todas as configurações do options do fetch.

Exemplo 3 (validações)

Exemplo de implementação da função validateResponse para um backend em Cloudsupport:

const _isStringNotEmpty = attr => typeof attr === 'string' && attr.length;

// Esse método trata os códigos padrões de erro de webservice conforme convenções
// do Cloudsupport. Não se aplica ao Websupport.
const validateResponse = response => {

    // Erros de controle de acesso
    if (response.status == 401) {
        throw Error('É necessário realizar autenticação');
    } 
    if (response.status == 403) {
        throw Error('O acesso não foi autorizado');
    } 

    // Erro de endpoint inexistente
    if (response.status == 404) {
        throw Error('O serviço solicitado não existe');
    } 

    // Erro de negócio
    if (response.status == 422) {
        return response.json()
        .catch(_ => { throw Error('O servidor retornou formato inválido') })
        .then(err => { 

            // Erro de business
            let mensagem = err.message ?? '';

            // Erro de validacao
            if (Array.isArray(err.fieldMessages) && err.fieldMessages.length > 0) {
                if (_isStringNotEmpty(mensagem))
                    mensagem += ': ';
                mensagem += err.fieldMessages.map(item => item.message).join("; ");
            }
 
            // Mensagem padrão
            if (mensagem.length == 0)
                mensagem = 'Ocorreu um problema ao processar a requisição';

            // Lanca o erro, com atributo adicional
            throw Object.assign(new Error(mensagem), {fieldMessages: err.fieldMessages});
        });
    } 

    // Outros erros de frontend
    if (response.status >= 400 && response.status < 500) {            
        return response.json()
        .catch(_ => { throw Error('O servidor retornou formato inválido') })
        .then(err => { throw Error(`A requisição enviada é inválida: ${err.tracking}`) });
    } 

    // Erros de backend
    if (response.status >= 500 && response.status < 600) {            
        return response.json()
        .catch(_ => { throw Error('O servidor retornou formato inválido') })
        .then(err => { throw Error(`Ocorreu um erro ao processar a requisição: ${err.tracking}`) });
    }
    return response;
};

export { validateResponse }

A função considera convenções da arquitetura sobre retorno de webservices do backend. Em síntese:

  • Em caso de erro de regra de negócio (BusinessException) é retornado o código HTTP 400 e o campo message é incluído no JSON de retorno, para que seja exibido para o usuário final.

  • Em caso de erro inesperado no backend, o Cloudsupport omite o campo message, inclui um código de rastreamento no campo tracking e retorna HTTP 500.

Consulte a documentação de Webservices para mais informações sobre os padrões de webservices da arquitetura no que tange o backend.

Próximos Passos

A próxima leitura sugerida é a seção Negócio, que detalha a camada de serviços.

A seção Documentação da API contém a referência completa de todos os módulos e componentes da arquitetura.