Golang: um dev Node fazendo seu primeiro webserver

Christian Benseler
7 min readJun 5, 2020

--

MongoDB + Golang -> REST API

Um dos métodos que uso para estudar uma linguagem nova é fazer algo que já domino em uma linguagem/tecnologia e que tem alguma aplicação prática, por diversas razões:

  • não preciso pensar num problema imaginário
  • tem alguma aderência com uma necessidade real
  • já sei qual o resultado esperado
  • consigo ter alguma base de comparação

Algumas semanas atrás comecei a olhar muito material sobre Golang e me interessei. Tenho trabalhado faz alguns anos majoritariamente com javascript, tanto no backend quanto no frontend (muito Angular e React e com aquele carinho que todo web developer deveria ter por jQuery, sem falar em ES6 e APIs novas que os browser suportam). No frontend, consegui experimentar muitas coisas, como React Native e Flutter, além de diversas abordagens e técnicas diferentes para programação voltada a web (como acessibilidade, PWAs, formas de organizar CSS, browser support, etc… ) mas no backend, tem sido só Node faz tempo e as tarefas se repetindo(e claro, dei uma olhada no Deno, mas não seria uma nova tecnologia em si).

Pensei em (re)ver Ruby, com a qual trabalhei muitos anos, mas a gente que está na área é tão bombardeado por novas stacks que resolvi testar Golang: por quê não aprender mais uma tecnologia?

Então o que foi a tarefa mais rotineira nos últimos projetos que fiz em Node, ou pelo menos o que tinha em quase todos? API Rest (aquele clássico CRUD de livros) persistindo os dados num MongoDB.

Já fui metendo as caras em fazer código Golang depois de fazer um breve tutorial da linguagem, mais para entender a sintaxe, operadores, os comandos básicos, etc… (PS: inclusive em breve farei um post contando qual processo costumo seguir para aprender uma nova tecnologia).

Vou então fazer uma comparação entre ambas tecnologias, é claro que nem de perto conheço o poder do Go, mas conversei com alguns colegas que trabalham com Node e olharam essa linguagem também e compartilhamos de muitas dessas visões, então acho que vai ajudar quem trabalha com Node em ter uma comparação.

O código-fonte está no Github!

Pacotes externos

Enquanto no Node a gente usa e abusa (até demais) do npm ou yarn, com Go os módulos externos não estão centralizados em um registry. Você dá o caminho de um repositório (parecido com o Deno, né?).
E você não precisa de um gerenciador externo(npm/yarn) para instalar a dependência, o próprio cli do go tem o comando get para isso:

go get "github.com/gin-gonic/gin"

A dependência então é instalada globalmente, no GOPATH (caminho no OS de instalação).

Quer saber mais sobre pacotes no Go? Leia esse texto!

Ah, uma coisa: o ecossistema Go é muito menor do que o Node, então terão menos opções e variedades de pacotes. Por isso então penei para encontrar pacotes que me ajudariam a fazer a implementação. O Go tem um pacote nativo de http e o time do Mongo provê drivers mas como eu queria algo parecido com o que eu já trabalho (como um Express e um Mongoose) procurei e encontrei o bongo que é um ORM para Mongo e o Gin, um webserver.

Linguagem tipada

Golang é uma linguagem tipada então se você é um javascriptzeiro puro (e que torce o nariz pra Typescript) ou algo do tipo, ai está uma boa diferença entra as duas. Então, no meu projeto, eu fui implementar uma função que recebe um id e retorna um livro, a declaração dela é (simplifiquei para esse exemplo):

func FindBookByID(id string) (Book) {}

É necessário tipar os argumentos e o retorno. Numa aplicação Node normal, eu escreveria:

const findBookById = id => {}

Ah, e não tem como fazer algo como o any do Typescript. Existe toda uma discussão sobre isso e Generics que pode ser acompanhada em links como:

Funções de primeira classe

Olhem esse código em Go

booksRoutes.PUT("/:id", updateBookEndpoint)

Para quem é do Node, está claro o que ele faz, não? E acertou, existe um router (booksRouter) que responde para o método PUT a url com pattern :id e a função updateBookEndpoint é passada como parâmetro. Então todos aqueles conceitos de high order functions podem ser aplicados em Go ❤ e isso tornou tão natural e simples pra mim (e assim creio que acontece com todo mundo que conhece programação funcional) escrever código em Go.

Não tem Exceptions

Sabe tudo aquilo que a gente faz em Node de jogar uma exception e tratá-la? Nananinanão, em Golang não é assim. E como se costuma fazer… bem, ai tem uma característica da linguagem que eu não lembro de ter visto antes: uma função pode ter mais do que um valor de retorno.

Lembra daquela função FindBookByID? Veja a implementação final dela:

func FindBookByID(id string) (models.Book, error) {
book := &models.Book{}
err := connection.Collection("books").FindById(bson.ObjectIdHex(id), book)
return *book, err
}

A assinatura dela é: recebe uma string e retorna um tipo Book e um tipo error

E a função que o router de recuperar um book pelo id (seria o nosso controller) ficou assim:

booksRoutes.GET("/:id", getBookEndpoint)
...
func getBookEndpoint(c *gin.Context) {
id := c.Param("id")
book, err := FindBookByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err})
return
}
c.JSON(http.StatusOK, book)
}

Conceitualmente, a forma de tratar erro é: trate o erro em todo lugar. Diferente da abordagem que temos utilizado em Node, que é de gerar uma Exception e pegar em algum lugar. Um código equivalente com Express + Mongoose seria

const findBookById = async function(id) {
const book = await Book.findById(id)
if(!book) {
throw new Error('No nbook found')
}
return book
}
const getBookEndpoint = async (req, res) => {
try {
const id = req.params['id']
const book = await findBookById(id)
res.json(book)
} catch(e) {
res.status(404).json({ e })
}
}

Esse post é dedicado a tratamento de erros em Go, vale a pena ler.

Linguagem compilada

Em Go você faz build de um executável. Veja no meu projeto

go build api.go

cria um arquivo (no meu caso por ser Windows) api.exe
Enquanto que a runtime do Node não faz isso, se você for rodar em produção você precisa de um gerenciador de processo (como o pm2) ou rodar a própria runtime.

Em tempo de desenvolvimento, você usa o comando run para rodar o seu source e ai ele fica como um processo

go run api.go

Ainda não encontrei uma forma de fazer livereload, como costumamos fazer em desenvolvimento com o nodemon no Node.

Fortemente opinada

Eu não conheço uma palavra que exprima em português o termo opiniated, então vamos lá: Golang te fala como as coisas devem ser — ele tem opinião. Exemplos?

  • styleguide definido (se usa ponto-e-vírgula ou não, se usa tabs ou espaços, se é camelcase ou snakecase, etc…) então não existe divergências entre equipes
  • declarar uma variável e não usar acusa erro
  • comandos (como já mostrei alguns) out-of-the-box para o dia-a-dia: testar, instalar dependências, formatar códigos, etc…
  • a forma como se organiza os arquivos, nomeia-se variáveis, etc…

Enquanto isso, no nosso mundo Node/javascript há uma incrível flexibilidade e infinitas preferências, para todos os gostos.

Pequena lista de comandos

Go tem uma variedade pequena de instruções, como if/else para condicional (não existe ternário), apenas um tipo de laço de repetição (for), e por aí vai. A idéia é que a linguagem seja simples e as implementações mais padronizadas, pelo que entendi do conceito; foco em resolver algo e não em como.

Structs

Para armazenar informação, você usa uma struct. A princípio (e de forma simplista) seria como uma interface, pois você a define e usa assim:

type Book struct {
ID string
Title string
Year int
}
book := Book{"Title": "teste", "Year": 2002, "ID": "somerandomonid"}

Eu não cheguei a implementar, já que o que fiz era bem básico, mas como funções são high order, você pode armazenar em um dos campos uma:

type function_name func()
type strcut_name struct{
var_name function_name
}

Essa forma de escrever me pareceu muito parecida com como se tem escrito código em Node atualmente sem orientação a objetos, fazendo composições e usando o poder de high order funcions.

Algo equivalente seria

const book = {
id: "someid",
title: "some title",
year: 2002,
someFunction: () => {}
}

Pointers

Golang tem muitas coisas de C e uma delas são os ponteiros, e acho (impressão minha) que atualmente ela não é uma linguagem que todos estudaram — quando eu comecei a minha formação como desenvolvedor, C era uma das linguagens que mais se usava e o conceito (e benefícios e preocupações ao se usar) não foi uma dor de cabeça para mim. Mas entendo que para quem nunca trabalhou com ponteiros precisa dar uma parada e lida a respeito, tanto em uma parte mais conceitual quanto mais mão na massa para lidar com ele.

Meu conselho: já comece pensando em ponteiros e relacione com (i)mutabilidade.

Conclusões

Nem de longe dá para eu, um newbie em Golang, cravar na pedra algo a respeito. Mas, no que diz respeito a uma primeira visão dessa linguagem para quem tem muita familiaridade com Node, é: vale a pena experimentar.
Tem muita coisa diferente mas muita coisa igual, dá pra sair jogando rapidamente, a documentação é boa, tem o fato de você precisar fazer menos escolhas (e também ter menos opções).
É claro que eu fiz o básico do básico com a tecnologia (Golang é usado para escrever Kubernetes) e que já fui atropelando muita coisa, mas no meu caso, em menos de um dia deu pra fazer uns tutoriais simples sobre sintaxe, conceitos, tools, etc… e já escrever essa API Rest.

E como se deve pensar sempre: qual o próximo passo? Estou pensando ainda… hehe! Talvez escrever uma API Rest similar em Node e comparar performance!

PS: pessoal com mais experiência em Go, por favor, se sintam à vontade para dar pitacos nessas minhas comparações e corrigir se eu tiver falado alguma besteira!

--

--