Servidores HTTP persistindo dados no MongoDB
Jorge D. Ortiz-Fuentes5 min read • Published Sep 04, 2024 • Updated Sep 04, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
No artigo anterior e no vídeo correspondente, escrevemos um servidor HTTP básico do zero. Usamos Go 1.22 para lidar com diferentes verbos HTTP e desserializamos dados que foram enviados de um cliente HTTP.
A troca de dados é inútil se você esquecê-los imediatamente. Vamos persistir esses dados usando o MongoDB. Você precisará de um cluster do MongoDB Atlas. O gratuito é mais do que suficiente. Se você não tiver uma conta, poderá encontrar orientações sobre como isso é feito neste workshop ou noYouTube. Você não precisa fazer o laboratório inteiro, apenas as partes "Criar uma conta" e "Criar um cluster" na seção "MongoDB Atlas". Chame seu cluster de "NoteKeeper" em um clusterGRATUITO. Crie um nome de usuário e senha que você usará em um momento. Verifique se o seu endereço IP está incluído. Verifique se o endereço IP do seu servidor tem permissão de acesso. Se você usar o codespace, inclua o endereço 0.0.0.0 para indicar que o acesso é permitido a qualquer IP.
- Até agora, usamos pacotes da biblioteca padrão, mas queremos usar o driver do MongoDB para nos conectar ao Atlas cluster. Isso adiciona o driver MongoDB Go às dependências de nosso projeto, incluindo entradas em
go.mod
para ele e todas as suas dependências. Ele também mantém os hashes das dependências nogo.sum
para garantir a integridade e baixa todo o código para poder incluí-lo no programa.1 go get go.mongodb.org/mongo-driver/mongo - O MongoDB usa BSON para serializar e armazenar os dados. Ele é mais eficiente e oferece suporte a mais tipos do que o JSON (estamos olhando para vocês, datas, mas também BinData). E podemos usar a mesma técnica que usamos para desserializar JSON para converter para BSON, mas, nesse caso, a conversão será feita pelo driver. Vamos declarar uma variável global para manter a conexão com o MongoDB Atlas e usá-la nos manipuladores. Essa nãoé uma prática recomendada. Em vez disso, poderíamos definir um tipo que mantenha o cliente e quaisquer outras dependências e forneça métodos - que terão acesso às dependências - que possam ser usados como manipuladores de HTTP.
1 var mdbClient *mongo.Client - Se o seu editor tiver algum problema ao importar os pacotes do driver do MongoDB, você precisará ter esses dois em seu bloco de importação.
1 "go.mongodb.org/mongo-driver/mongo" 2 "go.mongodb.org/mongo-driver/mongo/options" - Na função
main
, inicializamos a conexão com o Atlas. Observe que esta função retorna duas coisas. Para o primeiro, estamos usando uma variável que já foi definida no escopo global. O segundo,err
, não está definido no escopo atual, portanto, poderíamos usar a declaração de variável curta aqui. No entanto, se fizermos isso, ele ignorará a variável global que criamos para o cliente (mdbClient
) e definirá uma local somente para esse escopo. Então, vamos usar uma atribuição regular e precisamos queerr
seja declarada para podermos atribuir um valor a ela.1 var err error 2 mdbClient, err = mongo.Connect(ARG1, ARG2) - O primeiro argumento dessa chamada
Connect()
é um contexto que permite o compartilhamento de dados e solicitações de cancelamento entre a função principal e o cliente. Vamos criar um contexto destinado a fazer trabalho em segundo plano. Você poderia adicionar um cronômetro de cancelamento a esse contexto, entre outras coisas.1 ctxBg := context.Background() - O segundo argumento é uma estrutura que contém as opções usadas para criar a conexão. O mínimo é ter um URI para o nosso cluster Atlas MongoDB. Obtemos esse URI na página do cluster clicando em "Obter connection string". Criamos uma constante com essa connection string. Não use este. Não vai funcionar. Obtenha-o do seu cluster. Ter o URI de conexão com o usuário e a senha como constante também não é uma prática recomendada. Você deve passar esses dados usando uma variável de ambiente.
1 const connStr string = "mongodb+srv://yourusername:yourpassword@notekeeper.xxxxxx.mongodb.net/?retryWrites=true&w=majority&appName=NoteKeeper" - Agora podemos usar essa constante para criar o segundo argumento em vigor.
1 var err error 2 mdbClient, err = mongo.Connect(ctxBg, options.Client().ApplyURI(connStr)) - Se não conseguirmos nos conectar ao Atlas, não adianta continuar, então registramos o erro e saímos.
log.Fatal()
lida com ambas as coisas.1 if err != nil { 2 log.Fatal(err) 3 } - Se a conexão foi bem-sucedida, a primeira coisa que queremos fazer é garantir que ela será fechada se sairmos dessa função. Usamos
defer
para isso. Tudo o que adiarmos será executado quando sairmos do escopo da função, mesmo que as coisas não corram bem e ocorra um pânico. Incluímos o trabalho em uma função anônima e a chamamos porque defer é uma instrução. Dessa forma, podemos usar o valor de retorno do métodoDisconnect()
e agir de acordo.1 defer func() { 2 if err = mdbClient.Disconnect(ctxBg); err != nil { 3 panic(err) 4 } 5 }()
- Em seguida, queremos utilizar a collection (mais ou menos equivalente a uma tabela em um relational database) que conterá nossas anotações no banco de dados
NoteKeeper
. A primeira vez que nos referimos a esta collection, ela é criada. E isso pode ser feito porque não há necessidade de definir o esquema dessa collection antes de adicionar dados a ela. Vamos acessar a collection de dentro do HTTP implementado noCreateNote()
pouco antes de escrevermos no gravador de resposta do código anterior.1 notesCollection := mdbClient.Database("NoteKeeper").Collection("Notes") - Na próxima linha, inserimos a nota obtida desserializando os dados na solicitação. Também a partir da solicitação HTTP, obtemos o contexto que foi usado com ele, para estender seu uso à solicitação do Atlas.
1 result, err := notesCollection.InsertOne(r.Context(), note) - Caso haja algum problema com a solicitação
InsertOne()
, o manipulador terá que retornar um erro e o status HTTP adequado. Isso deve ser feito antes que qualquer coisa seja gravada no escritor da resposta ou será ignorada. Não é uma prática recomendada retornar o erro do banco de dados ao usuário. Você pode estar revelando muitas informações.1 if err != nil { 2 http.Error(w, err.Error(), http.StatusInternalServerError) 3 return 4 } - E, se tudo correr bem, imprimimos o ID da nova entrada, na parte inferior do manipulador HTTP.
1 log.Printf("Id: %v", result.InsertedID) - Compile e execute. Em seguida, usamos a mesma solicitação que tínhamos antes, só que, dessa vez, os dados também serão persistidos no banco de dados na nuvem.
1 curl -iX POST -d '{ "title": "Master plan", "tags": ["ai","users"], "text": "ubiquitous AI", "scope": {"project": "world domination", "area":"strategy"} }' localhost:8081/notes
Neste artigo, abordamos:
- O uso de pacotes para incorporar facilmente funcionalidades adicionais (como o driver do MongoDB).
- A implementação do tratamento de erros em um manipulador HTTP.
- A persistência de dados moderadamente complexos em um serviço de cloud.
Essas ideias podem ser estendidas para implementar uma REST API com recursos completos, um servidor de back-end ou um microsserviço, então você pode considerar este seu primeiro passo para superpotências Go reais.
No próximo artigo desta série, prestaremos atenção a um detalhe complicado: a simultaneidade. Usaremos goroutines e canais para implementar um desligamento normal de nosso servidor. O repositório contém todo o código desta série e das próximas para que você possa acompanhar.
Mantenha-se atento. Hackeie seu código. Até a próxima!
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.