english

Fabio Vedovelli

Neste momento da vida, puramente Javascript!

Chamadas HTTP em aplicações front end

February 21, 2020

Neste artigo demonstrarei como separo minhas chamadas AJAX em serviços, que por sua vez são consumidos pela store Vuex.

Disclaimer: apesar do artigo usar Vue.js para demonstração, esta técnica serve perfeitamente para um projeto React com Redux ou um projeto Svelte.


O código-fonte deste artigo está lhe esperando neste repositório: https://github.com/vedovelli/article-service-layer

Service Layer

Foto por unsplash-logoGalen Crout

Quando comecei a aprender a desenvolver interfaces complexas e interativas eu fazia tudo a partir do componente. O componente precisa de uma lista de objetos? Importava o axios, fazia a chamada AJAX e armazenava os dados retornados no state local do componente. A chamada retornou um erro? Também era responsabilidade do componente lidar com isso.

Este fluxo me atendeu bem por um tempo porém logo esbarrei no problema de repetição de código. Por que lidar com erros vindos do servidor várias e várias vezes se eu sabia ser possível fazer de forma centralizada? Também esbarrei na limitação de compartilhar, entre os componentes, informações que já haviam sido solicitadas.

Após um tempo de pesquisa e experimentação, cheguei no modelo que chamo de Service Layer.

Compreendendo a Service Layer

O fluxo completo

Ela consiste em criar uma pasta services em sua pasta src e lá criar métodos que fazem as chamadas AJAX. Estes métodos sempre retornarão uma Promise, permitindo que componentes tomam ações após sua resolução, caso seja necessário.

Estrutura de Pastas

src/services/index.js

import axios from 'axios';
import * as userService from './users';

/*
 * Aqui se pode configurar o Axios para customizar seu comportamento
 * Ver mais em https://github.com/axios/axios
 */
const http = axios.create({
  baseURL: '/api',
});

// Exporta o objeto Axios para ser usado pelos services
export default http;

// Exporta os services para serem usados pelas actions Vuex
export { userService };

src/services/users.js

import http from '@/service';

export const users = () => http.get('users');

Acontece que, seguindo adiante com as boas práticas, os serviços são utilizados EXCLUSIVAMENTE pela store Vuex, que usa os dados retornados para abastecer seu state e assim tornar os dados disponíveis para todos os componentes que tem interesse.

O resumo do fluxo é:

Componente (executa uma Action) > Action (executa um ou mais métodos do Service) > Service (faz chamada AJAX) > Action (recebe os dados na resolução Promise e os armazena no state da Store).

E as mensagens de erro?

Na Service Layer eu adiciono interceptors (eu uso Axios) que me permitem interagir com as chamadas antes e após sua execução. Para o momento ANTES eu faço ações complementam os dados da chamada, como adicionar um header com JWT, por exemplo. Já no momento DEPOIS eu verifico se um erro aconteceu e caso afirmativo, executo uma action que guardará a mensagem de erro na store Vuex. A mensagem então estará a disposição de todos os componentes que possuem interesse nela.

src/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

import { userService } from '@/service';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    users: [],
    ui: {
      error: '',
    },
  },
  mutations: {
    ['SET_USERS'](state, { users }) {
      state.users = users;
    },
    ['SET_ERROR_MESSAGE'](state, { error }) {
      state.ui.error = error;
    },
  },
  actions: {
    async fetchUsers({ commit }) {
      const {
        data: { users },
      } = await userService.users(); // <<< usa a Sercive Layer p/ chamar a API

      commit('SET_USERS', { users });
    },
    setErrorMessage({ commit }, { error }) {
      commit('SET_ERROR_MESSAGE', { error });
    },
  },
});

E então no componente eu faço apenas:

import { mapActions, mapState } from 'vuex';

const methods = mapActions(['fetchUsers']);

const computed = mapState(['users']);

export default {
  name: 'ServiceLayer',
  computed,
  methods,
  mounted() {
    this.fetchUsers();
  },
};

O que mais eu faço na Service Layer?

Digamos que eu precise formatar um payload de dados antes de enviar para o servidor. Isso é feito na Service Layer. Algo muito importante: sanitizar os dados antes de enviar ao server. Faço ali também (tanto nos métodos como nos Axios interceptors). Ou então que eu precise fazer o parse de dados vindos de uma API, para coloca-los num shape mais fácil de trabalhar. Isso também é feito pela Service Layer.

src/services/index.js

import axios from 'axios';
import store from '@/store';
import * as userService from './users';

/*
 * Aqui se pode configurar o Axios para customizar seu comportamento
 * Ver mais em https://github.com/axios/axios
 */
const http = axios.create({
  baseURL: '/api',
});

http.interceptors.request.use(
  config => {
    /*
     * aqui se pode por exemplo anexar um header contendo o JWT,
     * por exemplo. É um ponto central, todas as chamadas terão o header.
     */
    return config;
  },
  error => Promise.reject(error),
);

http.interceptors.response.use(
  response => response,
  error => {
    /*
     * aqui se pode ter um gerenciamento de erros centralizado. No exemplo abaixo estou
     * executando uma action na Vuex store para guardar a mensagem de erro no state e
     * então a mensagem poderá ser consumida por qualquer componente.
     */
    store.dispatch('setErrorMessage', { error: error.response.data });

    return Promise.reject(error);
  },
);

// Exporta o objeto Axios para ser usado pelos services
export default http;

// Exporta os services para serem usados pelas actions Vuex
export { userService };

src/services/users.js

import xss from 'xss';
import { cloneDeep } from 'lodash';
import http from '@/service';

export const users = () => http.get('users');

export const postUser = data => {
  /*
   * É na Service Layer que eu trato os dados antes de enviar a API.
   * Medida de segurança para evitar ataques XSS é um bom exemplo.
   */
  const safeData = xss(cloneDeep(data));

  return http.post('users', safeData);
};

Vantagens

  • Mantém seus componentes enxutos, retirando responsabilidades que não são meramente apresentar dados ao usuário da aplicação;
  • Separa sua regra de negócio do código do framework;
  • Facilita testes ao separar responsabilidades em seus devidos arquivos;
  • Diminui a superfície para aparecimento de bugs pois elimina a duplicidade de código.

Conclusão

Este fluxo de trabalho se mostrou escalável e fácil de implementar, compreender e ensinar.

Compartilhe este post