Back-End

29 out, 2018

Padrões de Web API – Parte 01: Documentação, Requests, Responses

Publicidade

Hoje em dia é muito comum a utilização de Web APIs como back-end para aplicações web e mobile. Porém, as tão faladas Web API’s hoje em dia já vão além de serem consumidas por simples aplicações web e mobile. Tornou-se comum ver sistemas embarcados de eletrônicos consumindo Web APIs, pois os pré requisitos são poucos, basta que o dispositivo eletrônico seja capaz de acessar a internet e fazer requisições HTTP para que tenha capacidade de se comunicar com uma Web API.

Afinal, uma Web API pode retornar outros tipos de formatos além do aclamado JSON, como por exemplo, o bom e velho binário.

Como mostrado acima, o crescimento da utilização de Web APIs está acontecendo de forma exponencial. Então, hoje venho trazer alguns padrões para desenvolver uma Web API com qualidade. Neste primeiro artigo da série falaremos de verbos e documentação.

Documentação

É muito comum cair no conforto de não documentar uma Web API, porém, é muito importante que este conforto seja deixado para trás, pois documentação também é uma das coisas que separaram os juniors dos seniors.

Utilizando algum framework, como o swagger, e efetuando a documentação corretamente, as coisas tendem a se tornar bem mais claras para as pessoas que irão utilizar a Web API – principalmente quando estamos falando de times diferentes trabalhando na mesma aplicação.

Por exemplo, imagine que tenha dois times atuando numa determinada aplicação web:um time desempenhando no back-end e outro no front-end (cenário partilhado por diversas empresas), neste caso a documentação até mesmo facilita a integração dos times. Veja abaixo algumas vantagens, quando o time de back-end documenta a Web API. Nestas situações o time de front-end, consegue:

  • Consultar o modelo do objeto a ser enviado na requisição.
  • Consultar o verbo da requisição a ser enviada.
  • Consultar o modelo do objeto que será retornado na requisição.
  • Saber quais tipos de erros esperar para realizar tratativa na interface.

Perceba que todas as ações acima são ações que caso não houvesse documentação, seria preciso que o time de front-end levantasse todas essas definições como perguntas para o time de back-end, utilizando o valioso tempo da sprint desnecessariamente.

Confira o código do exemplo aqui no repositório.

Documente sua aplicação. Não é só para você, é para quem vai consumir também. Veja como fica a documentação no código.

Verbos HTTP e HTTP Responses

Infelizmente ainda tem muito desenvolvedor que pensa que toda requisição que precisa enviar um body é POST e que todo retorno bem sucedido é Ok (200). Não é assim. Ser genérico nestas definições é a mesma coisa que negligenciar qualidade.

Veja um exemplo de implementação correta de verbos HTTP e HTTP responses, utilizando uma simples controller de produtos.

using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Patterns.WebAPI.Models;

namespace Patterns.WebAPI.Controllers
{
    [ApiController]
    [Route("api/v1/[controller]")]
    public class ProductController: ControllerBase
    {
        private static readonly IList<Product> ProductCollection = new List<Product>();
        
        /// <summary>
        /// Select all products
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [ProducesResponseType(200)]
        [ProducesResponseType(204)]
        public IActionResult GetAll()
        {
            if (ProductCollection.Any())
                return Ok(ProductCollection);
            
            return NoContent();
        }
        
        /// <summary>
        /// Select a single product
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("{id}", Name = "GetById")]
        [ProducesResponseType(200)]
        [ProducesResponseType(404)]
        public IActionResult GetById(string id)
        {
            //Avoiding invalid id to call database
            if (string.IsNullOrEmpty(id)) 
                return BadRequest();
            
            var product = ProductCollection.SingleOrDefault(pdt => pdt.Id == id);
            if (product != null)
                return Ok(product);

            return NotFound();
        }
        
        /// <summary>
        /// Creates a single product
        /// </summary>
        /// <param name="product"></param>
        /// <returns></returns>
        [HttpPost]
        [ProducesResponseType(201)]
        public IActionResult Register([FromBody] Product product)
        {
            ProductCollection.Add(product);
            return Created(nameof(GetById), product);
        }

        /// <summary>
        /// Updates a single product
        /// </summary>
        /// <param name="id"></param>
        /// <param name="product"></param>
        /// <returns></returns>
        [HttpPut]
        [ProducesResponseType(202)]
        [ProducesResponseType(404)]
        public IActionResult Update([FromRoute]string id, [FromBody] Product product)
        {
            //Avoiding invalid id to call database
            if (string.IsNullOrEmpty(product.Id)) 
                return BadRequest();
            
            if (ProductCollection.All(pdt => pdt.Id != id))
                return NotFound();

            var index = ProductCollection.IndexOf(product);
            ProductCollection[index] = product;
            return Accepted();
        }
        
                
        /// <summary>
        /// Removes a single product
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        [Route("{id}")]
        [ProducesResponseType(200)]
        [ProducesResponseType(404)]
        public IActionResult Delete([FromRoute]string id)
        {
            //Avoiding invalid id to call database
            if (string.IsNullOrEmpty(id)) 
                return BadRequest();
            
            var product = ProductCollection.SingleOrDefault(pdt => pdt.Id == id);
            if (product is null) 
                return NotFound();
            
            ProductCollection.Remove(product);
            return Ok();
        }
    }
}
  • 1. /api/v1/products => GetAll (MethodName). GET. Seleciona todos os produtos e retorna dentro de uma resposta com Http Status Code OK (200). Caso não existam produtos, é retornado o Http Status Code NoContent (204).
  • 2./api/v1/products/{id} => GetById (MethodName). GET. Seleciona um produto pelo seu identificador. Note que a recepção do id já é esperada na rota e não na query string, conforme recomendado no guia de web api patterns da Microsoft aqui. Perceba que existe uma data annotation na assinatura do método, no parâmetro id, [FromRoute]. Com essa anotação, estamos avisando explicitamente aonde o parâmetro será encontrado, poupando processamento de busca ao tentar procurá-lo automaticamente em todos os possíveis lugares (Query String, Rota, Header, Body). Prosseguindo, caso o produto seja encontrado é retornado uma response com Http Status Code OK (200), e caso não seja encontrado, assumindo que deveria, é retornado um ERRO, NotFound (404).

A título de curiosidade, são considerados requests bem sucedidos, requests com response de Http Status Code entre 200 e 299.

  • 3. /api/v1/products => Register (MethodName). POST. Adiciona um produto e retorna Http Status Code Created (201). Perceba novamente a marcação de onde o parâmetro virá na assinatura do método, [FromBody].
  • 4. /api/v1/products/{id} =>Update (MethodName). PUT. Atualiza um produto existente e em caso de sucesso retorna Http Status Code Accepted (202). Caso o produto a ser atualizado não seja encontrado, trata-se claramente de um erro, então o retorno será NotFound (404). Note que o id do produto não vem dentro do objeto [FromBody]; vem na rota [FromRoute]. Conforme documentado no guia de boas práticas para desenvolvimento de API da Microsoft aqui.
  • 5. /api/v1/products/{id} => Delete (MethodName). DELETE. Remove um produto e retorna Http Status Code OK (200), em caso de sucesso. Caso o produto não seja encontrado, retorna NotFound (404).

Pessoal, por hoje é isso! Espero que tenham gostado e que de alguma forma eu tenha te ajudado! Um grande abraço e até mais!

Quer saber mais sobre docker, docker compose, .net core, testes unitários e de integração? Baixe meu e-book free em https://kenerry.com.br