Go + Chi

Criando uma API REST simples — Artigo 1

John Fercher
5 min readJun 19, 2023

Postman collection, código do artigo.

Sumário: Desenvolvimento de APIs em Go

Próximo Artigo: Go + Project Layout — Organizando a Casa

Introdução

O go-chi/chi é um framework leve e de alto desempenho para o desenvolvimento de aplicativos web em Go. Ele foi projetado para ser minimalista e flexível, fornecendo apenas o conjunto de recursos essenciais para criar aplicativos web eficientes e escaláveis. Por ser minimalista é uma lib que nos faz ter que criar algumas coisas na mão, mas ele possui tudo que precisamos para criar nossa API que irá possuir um CRUD simples.

Existem outros frameworks que permitem a construção de APIs em Go, porém não iremos entrar em comparações entre eles. Na série, vamos utilizar o go-chi/chi, pois o objetivo não é focar em um framework específico, mas sim em outros aspectos do desenvolvimento de APIs. É importante ressaltar que outros frameworks também poderiam ser utilizados com sucesso.

A Entidade de Domínio

Como uma das utilidades do software é resolver problemas do mundo real, precisamos definir um problema para ser resolvido. Nesse sentido, pensei em desenvolver um sistema de gerenciamento de estoque. Para isso, teremos nossa entidade de domínio principal, que será Product.

https://github.com/johnfercher/medium/blob/ep1-go-chi/entities.go

Nosso Product irá possuir: id, name, type e quantity. E iremos manipular esses valores conforme as regras de negócio que definirmos melhor no futuro.

O Banco de Dados

Nesse episódio, não faremos integração com nenhum banco de dados. No entanto, para armazenar o estado necessário para o CRUD, criei um mapa global que simulará um banco de dados em memória.

Defini alguns products iniciais em homenagem a alguns amigos.

https://github.com/johnfercher/medium/blob/ep1-go-chi/db.go

Criando a API

Para enfocar exclusivamente no desenvolvimento de uma API básica e deixar tópicos para os próximos artigos, vamos manter todo o código da API na função principal (main) neste momento inicial. O código da main está presente no trecho abaixo. Na linha 2, criamos o “banco de dados” mencionado anteriormente. Na linha 3, criamos o objeto router do chi, responsável pelo tratamento das requisições. Na linha 4, há algo importante a ser mencionado: a utilização de um middleware de logging. Na última linha do exemplo, temos a chamada ao método do chi que efetivamente inicia a API. Essa chamada é bloqueante e permanecerá em execução enquanto a API estiver em funcionamento.

https://github.com/johnfercher/medium/blob/ep1-go-chi/main.go

Os middlewares no chi são códigos que encapsulam os requests e é possível criar middlewares customizados. Como o exemplo a baixo que adiciona um “user:123” ao contexto da request.

Exemplo de um middleware customizado.

Com os middlewares, é possível adicionar comportamentos que são executados antes e depois de cada requisição. Isso é útil para realizar validações mais gerais de permissões e até mesmo extrair métricas mais abrangentes da aplicação.

Entre as linhas 6 e 10 do código apresentado em artigo1-routes.go, temos a definição dos endpoints de nossa API: 2 Gets, 1 Post, 1 Put e 1 Delete

Definimos que os endpoints seguem um padrão de endereço uniforme, iniciando com /products, pois esses endpoints serão responsáveis por tratar as operações relacionadas à entidade Product previamente definida. Dessa forma, temos uma estrutura clara e consistente para lidar com as diferentes ações e interações com os produtos em nossa API.

É possível notar que os endpoints GetProductByID, UpdateProduct e DeleteProduct possuem um endereço diferente de CreateProduct, carregando o wildcard {id}. É possível obter o valor enviado no wildcard durante o lifecycle da requisição, e esse wildcard também serve para possibilitar que a API roteie somente as requisições que enviem um valor para a rota. Por exemplo:

  • GET: products/123
  • PUT: products/152
  • DELETE: products/656

Na definição dos endpoints, também é possível notar que cada um deles recebe uma variável com o sufixo “Handler” (CreateProductHandler, UpdateProductHandler, etc.). Esses handlers são funcões responsáveis por conter o código que será executado para realizar uma determinada operação durante uma requisição. Ao examinarmos a implementação interna do pacote chi por trás da definição do método Get, por exemplo, veremos que ele espera um padrão de string e um “HandlerFunc”, que basicamente é qualquer função que siga a assinatura func(ResponseWriter, *Request). Em essência, todos os endpoints devem respeitar essa assinatura para serem corretamente processados pela API.

Definição de Get do chi.

A leitura dos valores enviados na requisição, seja por URI, query string ou corpo da requisição, é feita por meio do objeto *Request. Por sua vez, a API responde utilizando o objeto ResponseWriter.

Endpoint Get Product By ID

Como os endpoints devem seguir a definição exigida pelo chi, criamos nossos handlers baseados nessa estrutura. Abaixo está a definição do GetProductByIDHandler:

O código presente nesse handler realiza uma série de operações em sequência:

  1. Lê o valor enviado no wildcard {id}.
  2. Utiliza o id enviado para obter o Product do “banco de dados”.
  3. Retorna o valor obtido.

Para modularizar essas operações, foram criadas as funções DecodeStringIDFromURI e WriteJsonResponse. A função DecodeStringIDFromURI tem como objetivo ler o valor passado no wildcard, como pode ser visto no código a seguir:

Já a função WriteJsonResponse realiza alguns tratamentos para retornar um objeto serializável em formato JSON. Ela cuida de realizar as etapas necessárias para serializar o objeto e configurar a resposta HTTP adequada. Veja o exemplo a seguir:

A função recebe o parâmetro ResponseWriter, que é utilizado pelo chi, um objeto como interface{} e o status HTTP que deve ser retornado. Ela utiliza o pacote JSON para serializar o objeto, que será escrito no corpo da resposta. Além disso, adiciona o cabeçalho “Content-Type: application/json” e define o status informado na função.

Endpoint Search Product

O endpoint de search tem como objetivo permitir a realização de buscas por outros valores da nossa entidade de domínio, além do ID. Neste caso, vamos implementar apenas a busca por type.

Nota: existe uma bomba nesse código que será tratada nos próximos artigos.

O código acima obtém o type enviado na requisição, itera sobre os dados do banco, filtra os dados que possuem o tipo correspondente e retorna os resultados. Basicamente, isso permite que a API receba uma solicitação GET como /products?type=clothing e retorne a Camisa do Grêmio e a Bandana do Dazaranha.

Endpoint Create Product

Para criar um product, os bytes enviados no corpo da requisição são lidos e deserializados na estrutura do Go. Em seguida, essa estrutura do Go é adicionada ao banco de dados e é retornada uma resposta de sucesso.

Não vamos abordar os endpoints de UpdateProduct e DeleteProduct, pois são muito parecidos com o CreateProduct e GetById.

Conclusões e Próximas Etapas

Neste artigo, foi demonstrado a construção de uma API com um CRUD simples utilizando o framework chi em aproximadamente 200 linhas de código. Embora não seja tão simples como poderia ser, já trabalhei em dezenas de APIs que seguem a mesma estrutura e lidam muito bem com alto throughput e sem problemas.

Existem muitas áreas que podem ser aprimoradas no que foi apresentado até agora. No próximo artigo, começaremos a organizar melhor o projeto, aplicando a estrutura do projeto (project-layout) e separando alguns pacotes para aumentar a coesão do nosso projeto.

Referências

--

--

John Fercher
John Fercher

Written by John Fercher

Tech lead at @MercadoLibre, gamer, master of science and open source contributor. More about: johnfercher.com