Criando um Serviço de Busca de CEP Simples com Go
Published: 2023-08-31 13:33:38


 
Neste artigo, faremos uma imersão em um serviço básico escrito em Go que pode servir para buscar informações de um CEP (Código de Endereçamento Postal) no Brasil. 

Vamos usar a [API ViaCep](https://viacep.com.br/) para busca de CEPs brasileiros.

### Estrutura do Código
O código é dividido em três partes principais:

1. A definição do tipo ViaCep, que é uma estrutura (struct) que espelha os dados retornados pela API ViaCep.

2. A função main, que inicia um servidor HTTP e define o roteamento.

3. As funções BuscaCepHandle e BuscaCep que lidam com a lógica principal da aplicação.

### Definindo o tipo ViaCep
Vamos definir o tipo ViaCep como uma estrutura com vários campos do tipo string. Esses campos refletem as propriedades do objeto JSON que recebemos da API ViaCep.

```
type ViaCep struct {
	Cep         string `json:"cep"`
	Logradouro  string `json:"logradouro"`
	Complemento string `json:"complemento"`
	Bairro      string `json:"bairro"`
	Localidade  string `json:"localidade"`
	Uf          string `json:"uf"`
	Ibge        string `json:"ibge"`
	Gia         string `json:"gia"`
	Ddd         string `json:"ddd"`
	Siafi       string `json:"siafi"`
}
```
>As tags json:"..." anexadas a cada campo da struct fazem o rolê de instruir o pacote encoding/json sobre como fazer o mapeamento do JSON para a struct em Go.

### Função Main
Na função main começamos escutando na porta `8080` e configuramos um roteamento de URL simples, onde todas as requisições para a rota `/` são tratadas pela função `BuscaCepHandle`.

```
func main() {
	http.HandleFunc("/", BuscaCepHandle)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
	return
	}
}
```
Se um erro ocorrer durante `http.ListenAndServe`, nós simplesmente retornamos e encerramos a execução do programa.

### Funções BuscaCepHandle e BuscaCep
A função `BuscaCepHandle` é nossa manipuladora de requisições HTTP. Ela lida com todas as solicitações que chegam à raíz ("/") da nossa aplicação.

```
func BuscaCepHandle(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	cepParam := r.URL.Query().Get("cep")
	if cepParam == "" || len(cepParam) != 8 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	// ... (código truncado por brevidade)
}
```
Esta função verifica se o **parâmetro do cep** foi passado na requisição e se ele possui 8 caracteres. Se o CEP não for válido, devolvemos um status HTTP 400.

A função `BuscaCep` é uma função auxiliar usada por `BuscaCepHandle` para fazer a solicitação real para a API ViaCep e processar os dados de resposta.

```
func BuscaCep(cep string) (*ViaCep, error) {
	url := "https://viacep.com.br/ws/" + cep + "/json/"
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var viaCep ViaCep
	err = json.Unmarshal(body, &viaCep)
	if err != nil {
		return nil, err
	}
	return &viaCep, nil
}
```
Esta função faz uma requisição para a API ViaCep, lê a resposta e a deserializa em um objeto ViaCep. 

A função retorna um ponteiro para um objeto ViaCep ou um erro, caso algo dê errado durante a solicitação HTTP ou a deserialização do JSON da resposta.

Abaixo eu deixo um exemplo do código completo para você analisar:

```
package main

import (
	"encoding/json"
	"io"
	"net/http"
)

type ViaCep struct {
	Cep         string `json:"cep"`
	Logradouro  string `json:"logradouro"`
	Complemento string `json:"complemento"`
	Bairro      string `json:"bairro"`
	Localidade  string `json:"localidade"`
	Uf          string `json:"uf"`
	Ibge        string `json:"ibge"`
	Gia         string `json:"gia"`
	Ddd         string `json:"ddd"`
	Siafi       string `json:"siafi"`
}

func main() {
	http.HandleFunc("/", BuscaCepHandle)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		return
	}
}

func BuscaCepHandle(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	cepParam := r.URL.Query().Get("cep")
	if cepParam == "" || len(cepParam) != 8 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	viaCep, err := BuscaCep(cepParam)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	err = json.NewEncoder(w).Encode(viaCep)
	if err != nil {
		return
	}

}

func BuscaCep(cep string) (*ViaCep, error) {
	url := "https://viacep.com.br/ws/" + cep + "/json/"
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var viaCep ViaCep
	err = json.Unmarshal(body, &viaCep)
	if err != nil {
		return nil, err
	}
	return &viaCep, nil
}

```

---
Pronto! 
Isso é o suficiente para criar um microsserviço simples de busca de CEP usando Go.

Se você quiser ter acesso ao código completo, pode acessar o [repositório do projeto no GitHub](https://github.com/marcelomatz/buscaCep).

Embora seja uma aplicação bastante simples, ela forma a base de como um serviço da vida real pode ser construído utilizando Go para buscar e fornecer informações do CEP a partir de uma API.

## Melhorias
Apesar de este ser um código que funciona e retorna o dado que esperamos, existem algumas coisas que podem ser melhoradas neste código.

**Tratamento de erros**: Quando um erro ocorrer, pode ser interessante retornar a mensagem de erro real ao invés de apenas um código de status HTTP para ajudar na depuração.

**Lembre-se de que esta não é uma boa prática para um ambiente de produção**, pois pode expor detalhes do sistema a usuários indesejados. 

Em um ambiente de produção, uma coisa que você pode fazer é logar o erro e retornar um ID de log para o cliente como uma referência para ele usar na abertura de um atendimento de suporte, por exemplo.

**Endpoint do servidor**: Atualmente o serviço está configurado para escutar todas as requisições na raiz ("/"). 

Poderia ser mais inteligente e útil ter um endpoint específico para essa funcionalidade como "/ceps" ou "/buscarCep". O exemplo não considerou isso justamente por ser um exemplo!

**Validação do CEP**: Poderia ser interessante adicionar uma validação mais forte para o campo 'cep'. 

Atualmente, o código apenas verifica se tem 8 caracteres, mas pode existir um cenário onde a entrada tenha 8 caracteres mas não seja um CEP válido. 🤡

**Testes**: Incluir testes de unidade e integração para garantir que o serviço está funcionando conforme esperado.

Na verdade tudo isso deveria ter começado a partir da escrita de testes. _My bad_

**Goroutine no BuscaCep**: A função do `BuscaCep` que chama um serviço externo poderia considerar utilizar uma goroutine para executar essa tarefa assincronamente.

**Verificações HTTP**: A resposta da função http.Get(url) pode vir com `statusCode não 200`. Seria inteligente fazer essa verificação antes de ler o corpo da resposta.

---
Agora sim, pronto!

Talvez quando você ler este post, os arquivos no repositório já tenham sido alterados e algumas dessas (ou todas) melhorias implementadas.

E se você tiver sugestões que colaborem com o aprendizado de outras pessoas sobre este mesmo assunto, fique a vontade para fazer um pull-request no GitHub.

Até a próxima 🤘