Construindo uma Lista de Tarefas com MongoDB e Golang

Bom dia! Muitas pessoas questionam-se sobre como funcionam as simples planilhas de tarefas ou aplicações que oferecem essa funcionalidade. Neste artigo, eu convido você a considerar como você pode escrever seu pequeno serviço em Go em poucos horas e colocar tudo em um banco de dados.

Vamos começar nossa viagem com o Golang e o MongoDB.

Por que Golang?

 

Eu quero mostrar as chaves:

  • Design minimalista e compilação rápida
  • Modelo de concorrência forte com Goroutines e canais
  • Ecosistema enorme
  • Plataforma cruzada na caixa

Outro fator é não gastar muito tempo estudando bibliotecas ou soluções open-source. Em meu caso, eu quero criar um microserviço que funcionará quase de uma das embalagens. O idioma de programação Golang tem tudo para isso.

Contudo, notei que o idioma já érico em projetos muito fixes que resolvam muitos problemas para o desenvolvedor. Projetos como:

  1. Gin: Framework HTTP de alto desempenho
  2. Viper: Solução de configuração (JSON, propriedades, arquivos YML) para aplicações Go
  3. GORM: Biblioteca ORM
  4. Protocol Buffers (Protobuf): Melhor maneira de serializar dados estruturados.

Nós não as revisaremos dentro do framework do artigo, mas talvez eu escreva algo sobre elas depois. Com Protobuf, eu já escrevi um artigo interessante, “From JSON to FlatBuffers: Enhancing Performance in Data Serialization.”

Instalação

Instalando Linguagem Go

Visite golang.org e faça o download. Em seguida, vá para o terminal e verifique.

Shell

 

go version

IDE

Apenas instale VS Code (é grátis).

E depois adicione a extensão Go:

Digite seu primeiro código:

Go

 

package main

import "fmt"

func main() {
   fmt.Println("Hello, World!")
}

E execute-o:

Shell

 

go run main.go

É assim! O próximo passo é escolher a melhor opção para coletar nossos dados.

Nossa Estrutura de Tarefa

Acho que nossa primeira estrutura deve ser mesmo simples. Vamos começar com 3 campos:

  1. Título (texto)
  2. Descrição (texto)
  3. Status (bool)

 Arquivo JSON como referência:

JSON

 

{     
  "title": "Go to the groceries",     
  "description": "Purchase milk, eggs, and bread",     
  "completed": false 
}

Por que MongoDB?

Nós precisamos coletar dados para nossas tarefas e ser flexíveis. Não precisamos criar um esquema ou relação entre as coisas.

  1. Estrutura Flexível
  2. Escalabilidade: Ele suporta escalonamento horizontal.
  3. Linguagem de Consulta Rica

Para noss serviço pequeno, podemos executá-lo com docker-compose.

YAML

 

 # Use root/example como credenciais de usuário/senha 
version: '3.1'

services:

 mongo:
   image: mongo
   ports:
     - "27017:27017"
   environment:
     MONGO_INITDB_ROOT_USERNAME: root
     MONGO_INITDB_ROOT_PASSWORD: example

Eu prefiro o Compass GUI para trabalhar com nossos dados (coleções, bancos de dados, etc.). Baixe aqui.

Execute-o e configure suas credenciais em “Opções Avançadas“. Funciona perfeitamente e pode ajudá-lo a encontrar problemas e otimizar solicitações se necessário.

Design de Sistema (Muito Pequeno)

Para noss serviço, claro, a melhor opção é criar métodos CRUD (Criar, Ler, Atualizar, Excluir), algo como isto (sem excluir):

Go

 

http.HandleFunc("/api/v1/add", todoHandler.AddTask)
http.HandleFunc("/api/v1/get-all", todoHandler.GetAllTasks)
http.HandleFunc("/api/v1/update", todoHandler.UpdateTask)
http.HandleFunc("/api/v1/complete", todoHandler.CompleteTask)

Eu quero usar a maneira de separar todas as responsabilidades por pastas.

  • Handler – Camada HTTP
  • Model – Estruturas para dados
  • Casos de Uso – Camadas de negócio com serviço e repositório

A estrutura do projeto pode ser assim:

Plain Text

 

todo-list/
│
├── cmd/
│   └── main.go
├── pkg/
│   └── handler
│      └── add_task.go
│      └── http_handler.go
│      └── complite_task.go
│      └── get_all_task.go
│      └── update_task.go
│   └── mapper
│       └── task.go
│   └── model
│        └── task.go
│   └── usecase
│        └── task
│           └── repository
│               └── add_task.go
│               └── complite_task.go
│               └── get_all_task.go
│               └── mongo_repositiry.go
│               └── repository.go
│               └── update_task.go
│           └── service
│               └── add_task.go
│               └── complite_task.go
│               └── get_all_task.go
│               └── service.go
│               └── update_task.go
└── go.mod

Como podemos ver, temos um arquivo “go.mod”, mas o que é ele? É um gerenciador de pacotes ou gerenciador de dependências. Podemos instalar e adicionar bibliotecas externas e usá-las também. Para o nosso exemplo, precisamos de algumas instruções usando “go mod”.

  1. Inicie sua aplicação -> go mod init todo-service . Vamos iniciar tudo; o gerenciador de dependências criará arquivos e adicionará tudo o que precisamos.
  2. Adicione dependência extra usando go mod add "link" .

Você pode ler mais na Página de Referência de Módulos Go .

Então, vamos focar em apenas um método – adicionar tarefas. Para explorar mais e ver exemplos de código completos, visite o repositório GitHub Golang Workshop .

Conexão com o MongoDB

Usando apenas duas dependências, podemos criar uma conexão:

Go

 

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

Claro, precisamos adicioná-lo ao nosso serviço. Use os comandos:

go get "go.mongodb.org/mongo-driver/mongo"

e

go get "go.mongodb.org/mongo-driver/mongo/options"

Depois, escreva um pedaço de código:

Go

 

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Defina as opções do cliente do MongoDB
	clientOptions := options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{
		Username: "root",
		Password: "example",
	})

	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal(err)
	}

	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Connected to MongoDB!")
}

Estruturas de dados para nossa aplicação:

Go

 

package model

import "go.mongodb.org/mongo-driver/bson/primitive"

type Task struct {
    ID         string `json:"id"`
    Title      string `json:"title"`
    Desciption string `json:"description"`
    Completed  bool   `json:"completed"`
}

type MongoTask struct {
    ID         primitive.ObjectID `json:"id" bson:"_id"`
    Title      string             `json:"title"`
    Desciption string             `json:"description"`
    Completed  bool               `json:"completed"`
}

  • Tarefa – para solicitação HTTP, MongoTask – para camada MongoDB: Usar duas estruturas é fácil porque, às vezes, não precisamos enviar dados adicionais para nossos usuários. Por exemplo, podemos ter um campo secreto, como um nome de usuário, que deve ser oculto.

1. Camada de repositório:

Go

 

type Repository interface {
    AddTask(ctx context.Context, task model.MongoTask) error
}

func (r *MongoRepository) AddTask(ctx context.Context, task model.MongoTask) error {
	task.ID = primitive.NewObjectID()


	_, err := r.collection.InsertOne(ctx, task)
}

2. Camada de serviço:

Go

 

type TodoService interface {
	AddTask(ctx context.Context, task model.Task) error
}

func (s *Service) AddTask(ctx context.Context, task model.Task) error {
	return s.Repo.AddTask(ctx, mapper.MapToDto(task))
}

3. Camada de manipulador (para processar solicitações HTTP):

Go

 

func (h *Handler) AddTask(w http.ResponseWriter, r *http.Request) {
	ctx := context.Background()

	var task model.Task
	err := json.NewDecoder(r.Body).Decode(&task)
	if err != nil {
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	err = h.Service.AddTask(ctx, task)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusCreated)
}

Agora, precisamos instalar a conexão com o banco de dados e inicializar a dependência “layers” no arquivo main.go.

Go

 

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Configurar opções do cliente MongoDB
	clientOptions := options.Client().ApplyURI("mongodb://localhost:27017").SetAuth(options.Credential{
		Username: "root",
		Password: "example",
	})

	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal(err)
	}

	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Connected to MongoDB!")

	// Inicializar repositório, serviço e manipulador
	todoRepo := repository.NewMongoRepository(client)
	todoService := service.NewService(todoRepo)
	todoHandler := handler.NewHandler(todoService)

	// Configurar rotas
	http.HandleFunc("/api/v1/add", todoHandler.AddTask)

	// Criar um servidor
	srv := &http.Server{
		Addr:    ":8080",
		Handler: nil,
	}
  // tarefa pendente: executar serviço e encerrar
}

E testá-lo com a solicitação:

HTTP

 

# curl -X POST http://localhost:8080/add
#-H "Content-Type: application/json"
#-d '{
#    "id": 1,
#    "title": "Comprar alimentos",
#    "completed": false
#}'
POST http://localhost:8080/api/v1/add
Content-Type: application/json

{
 "title": "Add description to the structure",
 "description": "your desc here..."
}

Essa é tudo. Então, devemos adicionar e implementar novos métodos, e o nosso serviço estará pronto para funcionar.

Conclusão

Criamos um pequeno, mas robusto serviço de gerenciamento de tarefas usando Golang e MongoDB.

Como podemos ver, se precisarmos construir um pequeno serviço, fazemos isso muito rápido sem muitos obstáculos. Em meu caso, eu realmente gostaria de usar MongoDB como banco de dados principal se eu tivesse documentos. É muito fácil de gerenciar.

Pode-se também notar que não seria pior em outras linguagens de programação. Em algumas partes, até é ainda mais rápido. Por exemplo, se você usar Python e FastAPI – e aqui eu possivelmente discordo de você. O idioma Golang ainda se baseia em paradigmas de linguagens de programação como C++ e Java, onde existe OOP. Tais códigos e metodologias permitem manter o código tão claro e limpo quanto possível.

Para começar, vai ser bom consolidar tais fatores como base, o que ajudará você a entender TDD e outras metodologias. Eu também notei que, para evitar sobrecarregar o artigo com métricas e texto, eu omiti a comparação com outras linguagens. Notei que o Golang não tem problemas com isso, e tendo escrito a função principal, você já a executa em sua thread. Você pode facilmente encontrar comparações na Internet, incluindo benchmarks.

Para explorar mais a fim e ver exemplos de código completos, visite o repositório GitHub (vinculado anteriormente).

Agradecimentos e cuide-se!

Source:
https://dzone.com/articles/build-a-to-do-list-with-mongodb-and-golang