Construindo Sua Primeira API em Go do Zero - Inicializando o projeto TODO

Buenas cara pessoa que está lendo este artigo, hoje decidi trazer um artigo diferente, um tutorial para ser mais específico, então sem muita enrolação e como você já deve ter lido no título vamos começar a criar a sua primeira API juntos! Pode até que não seja a sua primeira API em Go, mas vou trazer algumas curiosidades sobre APIs em Go enquanto construímos a nossa.
Não sei o tamanho da série ainda, pois pelo que planejo seria bem evolutivo esse projeto a medida que vou trazendo explicações sobre práticas que aprendi e utilizo no dia-a-dia como desenvolvedor Go. Mas já digo que nesse primeiro momento, mesmo o projeto parecendo ser simples eu pretendo “complicá-lo” à medida que vamos evoluindo, então ele vai começar simples e com poucas boas práticas para manter a didática e vamos evoluindo ele. Vamos começar?
Inicializando o projeto TODO
O primeiro de tudo é ter o Go instalado na sua máquina, caso ainda não o tenha pode obter aqui no site oficial e escolher de acordo com sua máquina (arquitetura da CPU e o sistema operacional que está usando). Agora vamos criar a pasta do nosso projeto e inicializar o projeto em Go. Eu vou criar uma pasta chamada todo-go-app e depois vou entrar dentro dela, a forma como você cria fica a seu critério, seja visual ou por linha de comando, vou deixar aqui o como a linha de comando que pode usar para criar caso queira:
mkdir todo-go-app && cd todo-go-app
Agora vem a parte interessante, talvez alguns já saibam que para iniciar um projeto em Go basta utilizar o comando go mod init nome-do-projeto entretanto aqui já vai uma dica, quando criar um novo projeto crie utilizando o endereço do repositório Git do seu projeto!
Mas agora você pode estar se perguntando, mas por quê isso Augusto? Bem, para explicar isso vamos entender que Go é uma linguagem que trabalha com a lógica de pacotes (packages em inglês), então quer dizer que todo diretório do projeto, incluindo o diretório raiz é um pacote em Go, tanto que quando criamos o projeto com o comando acima, ele cria um arquivo chamado go.mod contendo o seguinte:
module nome-do-projeto
go {sua.versão.do.go.instalada}
Você pode ficar em dúvida porque tem o nome module e não package mas é porque os módulos em Go são pacotes ainda, só que vistos de uma forma diferente, posso depois escrever um artigo explicando melhor, mas por hora vamos pensar dessa forma ok? Tá agora que você aceitou isso, preciso explicar o porque que temos que colocar caminho do repositório como nome do projeto quando estou criando-o, certo?
Pois bem, Go tem diversos pacotes hoje para serem usados para nos auxiliar no desenvolvimento, seriam as bibliotecas ou frameworks, pegando o caso do chi que é um pacote para ajudar a criar uma aplicação web, entretanto se tivessem 2 pacotes chi criado por duas pessoas diferentes, quando fossemos executar o go get chi como o Go saberia qual pacote pegar? Então para poder diferenciar, criamos os nossos projetos com o caminho do repositório Git como nome, no caso do go-chi o nome é github.com/go-chi/chi/v5, que em particular tem o /v5 ao final para indicar que é a versão 5, mas o repositório em si é apenas github.com/go-chi/chi. Logo quando formos instalar o pacote no nosso projeto vamos usar o go get github.com/go-chi/chi/v5 e o Go saberá o pacote correto para pegar.
Tudo bem que nosso projeto será uma aplicação inteira e não apenas um pacote, mas para manter o padrão que já estamos acostumados a ver no ecossistema Go, vamos iniciar o nosso projeto como:
go mod init github.com/{meu-usuario}/todo-go-app-tutorial
No caso do nosso tutorial o nosso repositório remoto é o Github, por isso usei a URL do github.com para isso já que nosso repositório ficará por lá. Bem, iniciamos nosso projeto e já aprendemos uma coisa nova, que geralmente não vemos em outros tutoriais que é explicação do porquê projetos sérios o nome do projeto é o link do repositório, ou até mesmo outro link, e não somente um nome simples. Vamos seguir para estruturar nosso projeto em pastas e em seguida criar nossa primeira rota?
Montando a estrutura de pastas e escolhendo nossa primeira arquitetura
Bem, chegou a hora de entrarmos num ponto que gera bastante discussão dentro da comunidade Go e pra ser sincero, dependendo de qual comunidade em si você está imerso, vão te indicar leves diferenças no padrão. Mas, como falei inicialmente, vamos começar com as coisas bem didáticas e nesse primeiro momento vamos utilizar uma releitura minha do padrão MVC.
Para você que não sabe, o padrão MVC é um padrão que foi, e ainda é, utilizando em diversos frameworks e diversas linguagens como Ruby, Python, PHP e por aí vai, mas não fica tão bem aplicado em uma API Go (minha opnião). Resumindo, o MVC é um padrão de 3 camadas:
- Model: camada de modelos, modelos são a nossa representação dos objetos que vamos trabalhar e tem a regra de conexão com nossa base de dados.
- View: camada de visualização, nesse caso como nossa visualização é apenas o endpoint, não temos muita formatação para fazer aqui, como renderizar a página web no backend como o PHP e Python faz
- Controller: camada de controle, aqui os controladores irão fazer a conexão entre os modelos e visualizações, contendo regra de negócio como validações de campos e etc.
Resumidamente o MVC é isso. E para o nosso caso vamos trabalhar o MSH! Mas o que é isso? Como eu falei, uma releitura minha para o MVC aplicado ao Go e vou explicar o que cada camada é e faz.
- Model: aqui não muda muita coisa, vamos continuar da mesma forma.
- Handler: em Go trocamos os controladores (controllers) pelos manipuladores (handlers),sendo os manipuladores a nomenclatura mais usada em Go, mas nessa camada não teremos lógica de negócio iguais os controladores do MVC, apenas vai servir de interface para criar nossos endpoints e servir o JSON de resposta.
- Service: aqui já temos os serviços (services), se você já ouviu isso antes provavelmente reconhece de outra arquitetura (e não entrarei nos detalhes aqui para não alongar e complicar), mas voltando aos serviços, é nessa camada que teremos nossa regra de negócio e ela que irá conectar a camada de modelos com a camada de manipuladores
Didaticamente mantemos a mesma simplicidade, assim eu espero, e agora que temos nossa arquitetura formada, vamos adaptá-la ao padrão de pastas recomendada pela linguagem Go. Sendo assim teremos algo como:
todo-api/
│
├── cmd/
│ └── api/
│ └── main.go # main.go aqui (ponto de entrada da aplicação)
├── internal/
│ ├── todo/ # Domínio TODO
│ │ ├── model.go # Modelos e acesso a dados
│ │ ├── handler.go # Handlers/Controllers (HTTP handlers)
│ │ └── service.go # Lógica de negócio
│ └── ... # Outros domínios no futuro
│
├── pkg/ # Código reutilizável, se necessário, como acesso ao bd
│
├── go.mod
└── go.sum
Vou explicar um pouco dessa organização aqui para você que está lendo para entender um pouco
- cmd: essa pasta é a pasta de comando (command) que geralmente contém os arquivos de entrada da aplicação, já que uma aplicação pode ter diversos pontos como uma api, ou cli, pois é partir desses arquivos de entrada, os famosos main.go, que nossa aplicação executa os comandos.
- internal: essa pasta é apenas para indicar ao Go que todos os arquivos dessa pasta podem ser importados apenas até o mesmo nível que ela existe, sendo assim útil para separar lógica que a nossa aplicação precisa para funcionar mas que não deveria estar exposto a outras aplicações.
- Aqui eu sei que complica, mas imaginemos que além de aplicação o nosso projeto também é um framework que pode ser importado via go get mas queremos bloquear acesso de import de alguns pacotes, então nossos arquivos de todo não estariam disponíveis para serem usados, somente os arquivos da pasta cmd e pkg.
- Logo posso ter inclusive uma pasta internal dentro da pasta todo, caso eu queira criar arquivos/pacotes exclusivos de todo mas que outra parte do projeto não precise.
- pkg: essa pasta é uma pasta que colocamos pacotes que costumamos reutilizar em diversos ponto do código como por exemplo o código para acessar o banco de dados, que vai ser utilizado em diversos pontos, pode ficar aqui, por exemplo como pkg/database.go.
- Os arquivos go.mod e go.sum são criados automaticamente pelo go quando iniciamos o projeto e adicionamos dependências.
Agora que temos nossa estrutura de pastas e arquitetura definidas, vamos para o primeiro código?
Escrevendo nossa aplicação
Nesse primeiro momento, para simplificar o projeto e o tamanho do artigo que já está longo, o nosso projeto terá apenas um endpoint que servirá para extrair a lista de todos armazenados em slice (que é um array dinâmico da linguagem Go). Como os arquivos são simples, deixarei o código aqui para que possa pegá-los.
// model.go
package todo
import "context"
// Todo é a nossa estrutura de tarefas a fazer, por hora ela vai ter apenas ID inteiro, titulo e se está completa
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
// esse slice é para representar nossa base de forma simplificada, em outro artigo iremos conectar com uma base dados
// mas por hora vamos deixar assim para ficar mais simples
var todos = []Todo{
{
ID: 1,
Title: "Meu primeiro todo!",
Completed: true,
},
{
ID: 2,
Title: "Da minha primeira api Go!",
Completed: false,
},
}
// com a visão de pacotes do Go, podemos criar uma função sem precisar de um objeto (struct) para ter o método
// não é considerado uma boa prática na visão de POO, mas quis trazer que também é possível, diferente da service e do
// handler
func GetAllTodos(ctx context.Context) []Todo {
return todos
}
// service.go
package todo
import "context"
// A service aqui que criamos vai conter a logica, ela é um objet (struct) pois mais para frente iremos injetar a dependencia
// nela, calma que vai fazer sentido em outro artigo
type Service struct {
}
func NewService() Service {
return Service{}
}
// Dessa forma aque fizemos, basta que o método da service chame a função da model para que list todos os todos.
func (s Service) ListTodos(ctx context.Context) []Todo {
return GetAllTodos(ctx)
}
// handler.go
package todo
import (
"encoding/json"
"net/http"
)
type Handler struct {
service Service
}
func NewHandler(service Service) Handler {
return Handler{service: service}
}
// ListTodos lista todos todos que temos através da service injetada como dependencia
// de novo, calma que vai fazer sentido mais para frente.
func (h Handler) ListTodos(w http.ResponseWriter, r *http.Request) {
// aqui ele chama a service que foi injetada como dependencia
// (depois veremos melhor essa parte de injeção e como fazer de uma forma mais própria para isso e os seus benefícios)
allTodos := h.service.ListTodos(r.Context())
// com o resultado em mãos já podemos dizer que vamos retornar uma resposta do tipo json
w.Header().Set("Content-Type", "application/json")
// com status http 200 (OK)
w.WriteHeader(http.StatusOK)
// e por final pegamos nosso slice e convertemos em um json para ser respondido.
_ = json.NewEncoder(w).Encode(allTodos)
}
// main.go
package main
import (
"fmt"
"net/http"
"github.com/augustoasilva/todo-go-app-tutorial/internal/todo"
)
func main() {
service := todo.NewService()
handler := todo.NewHandler(service)
http.HandleFunc("/todos", handler.ListTodos)
// Aqui depois vamos adicionar nossar outras rotas
fmt.Println("Servidor iniciado em local host na porta 8080")
_ = http.ListenAndServe(":8080", nil)
}
Agora que temos o nosso projeto pronto, vamos executá-lo com o comando via cli abaixo, mas se quiser pode executar utilizando seu editor de texto ou IDE também, como por exemplo o VS Code tem um botão de “Play” assim como os IntelliJ e Goland da Jetbrains também tem.
E o resultado da nossa aplicação fica assim no nosso primeiro endpoint!
Próximos passos
Bem, agora encerramos aqui o nosso artigo de hoje com sua primeira api, mesmo de apenas um endpoint, está funcionando sem nenhuma dependência externa, viva! Muito bem por ter chegado até aqui.
Estarei deixando o link do repositório aqui para que possa acompanhar mais do projeto (e queira spoilers antes dos artigos) e deixarei o link da branch correspondente a este artigo aqui também para que veja todos os arquivos iniciais juntos.
No próximo artigo da série estaremos escrevendo os demais endpoints, explicando um pouco mais de como funciona o ciclo de requisição dentro do servidor Go entre outras coisinhas mais. Aviso que o próximo artigo não será continuação deste, será da série de Orientação a Objetos que também estou escrevendo, a ideia é alternar por enquanto estes dois temas. Então te espero no próximo artigo, tudo bem?