Operaciones con MongoDB en Go

MongoDB es una base de datos NoSQL popular que almcaena datos en un formato similar a JSON llamado BSON (Binary JSON). A diferencia de las bases de datos relacionales, MongoDB utiliza el concepto de colecciones (equivalente a tablas en SQL) y documentos (equivalente a filas en SQL).

Instalación y Configuración de MongoDB

Para instalar MongoDB en sistemas basados en RHEL/CentOS, primero configure el repositorio adecuado:

vim /etc/yum.repos.d/mongodb-org-4.2.repo

[mongodb-org-4.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-4.2.asc

Luego, instale el paquete y inicie el servicio:

sudo yum install -y mongodb-org
systemctl start mongod

Comandos Básicos de MongoDB

  • use <database_name>;: Cambiar a una base de datos (la crea si no existe).
  • db.createCollection("<collection_name>");: Crear una colección.
  • db.createUser({...});: Crear un usuario con roles.
  • mongo --port <port> -u "<username>" -p "<password>" --authenticationDatabase "<auth_db>": Iniciar sesión en MongoDB.
  • db.<collection_name>.createIndex({field: 1});: Crear un índice en un campo.
  • db.<collection_name>.insertOne({...});: Insertar un solo documento.
  • db.<collection_name>.find({...});: Buscar documentos.
  • db.<collection_name>.update({...}, {...});: Actualizar un documento.
  • db.<collection_name>.deleteOne({...});: Eliminar un solo documento.

Integración con Go

Instalación del Driver de Go para MongoDB

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

Conexión a la Base de Datos

La URI de conexión sigue el formato: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]].

El siguiente código demuestra cómo establecer una conexión segura con un tiempo de espera:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

// User representa la estructura de un usuario en la base de datos.
type User struct {
	ID     uint      `bson:"_id,omitempty"` // ID único del usuario, omitido si está vacío al insertar
	Name   string    `bson:"name"`          // Nombre del usuario
	Age    int       `bson:"age"`           // Edad del usuario
	Gender int8      `bson:"gender"`        // Género del usuario (ej: 1 para masculino, 2 para femenino)
	BirthDate time.Time `bson:"birth_date"`  // Fecha de nacimiento del usuario
}

var (
	userCollection *mongo.Collection // Referencia a la colección de usuarios
	ctx            context.Context   // Contexto para operaciones con tiempo de espera
	cancel         context.CancelFunc // Función para cancelar el contexto
)

func init() {
	// Configura un contexto con un tiempo de espera de 5 segundos para la conexión.
	ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)

	// URI de conexión a MongoDB.
	connectionURI := "mongodb://localhost:27017/" // Reemplaza con tu URI si es diferente

	// Configura las opciones del cliente, incluyendo la URI, el tiempo de espera y la autenticación.
	clientOptions := options.Client().ApplyURI(connectionURI).
		SetConnectTimeout(5 * time.Second). // Tiempo de espera para la conexión
		SetAuth(options.Credential{       // Credenciales de autenticación (si son necesarias)
			Username: "myuser",
			Password: "mypassword",
		})

	// Conecta al servidor de MongoDB.
	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal("Error al conectar a MongoDB:", err)
	}

	// Verifica que la conexión sea exitosa.
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal("Error al hacer ping a MongoDB:", err)
	}

	// Obtiene una referencia a la colección 'users' en la base de datos 'testdb'.
	userCollection = client.Database("testdb").Collection("users")
	log.Println("Conexión a MongoDB exitosa y colección lista.")

	// Nota: El cierre de la conexión (client.Disconnect) se maneja típicamente al final del programa principal
	// o en una función defer en main() si el cliente es global. Aquí, para el ejemplo de init,
	// asumimos que el programa principal gestionará el ciclo de vida del cliente.
}

func main() {
	// Asegúrate de que el contexto se cancele al finalizar la función principal.
	defer cancel()

	// Ejemplo de uso: Insertar un nuevo usuario.
	newUser := User{
		Name:   "Alice",
		Age:    30,
		Gender: 2, // Femenino
		BirthDate: time.Now().AddDate(-30, 0, 0), // Aproximadamente 30 años atrás
	}

	// Inserta un solo documento.
	insertResult, err := userCollection.InsertOne(ctx, newUser)
	if err != nil {
		log.Fatal("Error al insertar usuario:", err)
	}
	fmt.Printf("Usuario insertado con ID: %v\n", insertResult.InsertedID)

	// Ejemplo de búsqueda: Buscar al usuario recién insertado.
	var foundUser User
	filter := bson.D{{Key: "name", Value: "Alice"}} // Filtro por nombre
	err = userCollection.FindOne(ctx, filter).Decode(&foundUser)
	if err != nil {
		log.Fatal("Error al buscar usuario:", err)
	}
	fmt.Printf("Usuario encontrado: %+v\n", foundUser)

	// Ejemplo de actualización: Incrementar la edad del usuario.
	updateFilter := bson.D{{Key: "_id", Value: insertResult.InsertedID}} // Busca por el ID insertado
	update := bson.D{{Key: "$inc", Value: bson.D{{Key: "age", Value: 1}}}}{} // Incrementa la edad en 1
	updateResult, err := userCollection.UpdateOne(ctx, updateFilter, update)
	if err != nil {
		log.Fatal("Error al actualizar usuario:", err)
	}
	fmt.Printf("Documentos actualizados: %d\n", updateResult.ModifiedCount)

	// Ejemplo de eliminación: Eliminar al usuario.
	deleteResult, err := userCollection.DeleteOne(ctx, updateFilter)
	if err != nil {
		log.Fatal("Error al eliminar usuario:", err)
	}
	fmt.Printf("Documentos eliminados: %d\n", deleteResult.DeletedCount)
}

Operaciones Comunes

Creación de Documentos

Se pueden insertar uno o múltiples documentos a la vez.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

// User struct definition remains the same as in the connection example.
type User struct {
	ID        uint      `bson:"_id,omitempty"`
	Name      string    `bson:"name"`
	Age       int       `bson:"age"`
	Gender    int8      `bson:"gender"`
	BirthDate time.Time `bson:"birth_date"`
}

var (
	userCollection *mongo.Collection
	ctx            context.Context
	cancel         context.CancelFunc
)

// init function remains the same as in the connection example.
func init() {
	ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
	connectionURI := "mongodb://localhost:27017/"
	clientOptions := options.Client().ApplyURI(connectionURI).
		SetConnectTimeout(5 * time.Second).
		SetAuth(options.Credential{
			Username: "myuser",
			Password: "mypassword",
		})

	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal("Error al conectar a MongoDB:", err)
	}
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal("Error al hacer ping a MongoDB:", err)
	}
	userCollection = client.Database("testdb").Collection("users")
	log.Println("Conexión a MongoDB exitosa y colección lista.")
}


func main() {
	defer cancel()
	// Ensure client disconnect logic is handled appropriately in a real application,
	// potentially in main() using defer if client is declared globally or passed around.

	// Insertar un único usuario
	singleUser := User{
		ID:        1, // Note: _id is usually managed by MongoDB unless explicitly provided
		Name:      "Bob",
		Age:       25,
		Gender:    1, // Masculino
		BirthDate: time.Now().AddDate(-25, 0, 0),
	}

	insertResult, err := userCollection.InsertOne(ctx, singleUser)
	if err != nil {
		log.Fatal("Error al insertar un solo usuario:", err)
	}
	fmt.Println("Insertado un usuario con ID:", insertResult.InsertedID)

	// Insertar múltiples usuarios
	usersToInsert := []interface{}{
		User{Name: "Charlie", Age: 22, Gender: 1, BirthDate: time.Now().AddDate(-22, 0, 0)},
		User{Name: "Diana", Age: 28, Gender: 2, BirthDate: time.Now().AddDate(-28, 0, 0)},
		User{Name: "Ethan", Age: 35, Gender: 1, BirthDate: time.Now().AddDate(-35, 0, 0)},
	}

	insertManyResult, err := userCollection.InsertMany(ctx, usersToInsert)
	if err != nil {
		log.Fatal("Error al insertar múltiples usuarios:", err)
	}
	fmt.Println("Insertados múltiples usuarios con IDs:", insertManyResult.InsertedIDs)
}


Actualización de Documentos

Se pueden actualizar uno o todos los documentos que coincidan con el filtro.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

// User struct definition remains the same.
type User struct {
	ID        uint      `bson:"_id,omitempty"`
	Name      string    `bson:"name"`
	Age       int       `bson:"age"`
	Gender    int8      `bson:"gender"`
	BirthDate time.Time `bson:"birth_date"`
}

var (
	userCollection *mongo.Collection
	ctx            context.Context
	cancel         context.CancelFunc
)

// init function remains the same.
func init() {
	ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
	connectionURI := "mongodb://localhost:27017/"
	clientOptions := options.Client().ApplyURI(connectionURI).
		SetConnectTimeout(5 * time.Second).
		SetAuth(options.Credential{
			Username: "myuser",
			Password: "mypassword",
		})

	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal("Error al conectar a MongoDB:", err)
	}
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal("Error al hacer ping a MongoDB:", err)
	}
	userCollection = client.Database("testdb").Collection("users")
	log.Println("Conexión a MongoDB exitosa y colección lista.")
}

func main() {
	defer cancel()

	// Opciones para la actualización, permitiendo insertar si no se encuentra el documento.
	updateOpts := options.Update().SetUpsert(true)

	// Filtro para encontrar documentos con nombre "Bob".
	filter := bson.D{{Key: "name", Value: "Bob"}}

	// Operación de actualización: Establecer la edad a 666.
	// Otra opción común es "$inc" para incrementar valores:
	// update := bson.D{{Key: "$inc", Value: bson.D{{Key: "age", Value: 1}}}}
	update := bson.D{{Key: "$set", Value: bson.D{{Key: "age", Value: 666}}}}

	// Actualizar todos los documentos que coincidan con el filtro.
	updateResult, err := userCollection.UpdateMany(ctx, filter, update, updateOpts)
	if err != nil {
		log.Fatal("Error al actualizar documentos:", err)
	}

	// Muestra las estadísticas de la operación.
	fmt.Printf("Documentos coincidentes: %d, Documentos modificados: %d, Documentos insertados: %d, ID insertado: %v\n",
		updateResult.MatchedCount, updateResult.ModifiedCount, updateResult.UpsertedCount, updateResult.UpsertedID)
}

Consulta de Documentos

Permite filtrar, ordenar y paginar los resultados.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

// User struct definition remains the same.
type User struct {
	ID        uint      `bson:"_id,omitempty"`
	Name      string    `bson:"name"`
	Age       int       `bson:"age"`
	Gender    int8      `bson:"gender"`
	BirthDate time.Time `bson:"birth_date"`
}

var (
	userCollection *mongo.Collection
	ctx            context.Context
	cancel         context.CancelFunc
)

// init function remains the same.
func init() {
	ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
	connectionURI := "mongodb://localhost:27017/"
	clientOptions := options.Client().ApplyURI(connectionURI).
		SetConnectTimeout(5 * time.Second).
		SetAuth(options.Credential{
			Username: "myuser",
			Password: "mypassword",
		})

	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal("Error al conectar a MongoDB:", err)
	}
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal("Error al hacer ping a MongoDB:", err)
	}
	userCollection = client.Database("testdb").Collection("users")
	log.Println("Conexión a MongoDB exitosa y colección lista.")
}

func main() {
	defer cancel()

	// Definir criterios de ordenación: por edad descendente.
	sortOrder := bson.D{{Key: "age", Value: -1}}

	// Definir el filtro de búsqueda. Un filtro vacío ({}) busca todos los documentos.
	filter := bson.D{{}} // Equivalente a bson.M{}

	// Configurar las opciones de búsqueda: límite, omisión y ordenación.
	findOptions := options.Find().
		SetSort(sortOrder).      // Aplicar ordenación
		SetLimit(2).             // Limitar a 2 resultados
		SetSkip(0)               // Omitir los primeros 0 resultados (para paginación, se puede ajustar)

	// Ejecutar la consulta.
	cursor, err := userCollection.Find(ctx, filter, findOptions)
	if err != nil {
		log.Fatal("Error al ejecutar la consulta:", err)
	}
	defer cursor.Close(ctx) // Asegurarse de cerrar el cursor

	// Iterar sobre los resultados.
	fmt.Println("Resultados de la búsqueda:")
	for cursor.Next(ctx) {
		var user User
		if err := cursor.Decode(&user); err != nil {
			log.Fatal("Error al decodificar el documento:", err)
		}
		fmt.Printf("  %+v\n", user)
	}

	// Verificar si hubo errores durante la iteración.
	if err := cursor.Err(); err != nil {
		log.Fatal("Error en el cursor:", err)
	}
}

Eliminación de Documentos

Se pueden eliminar uno o todos los documentos que coincidan con el filtro.

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

// User struct definition remains the same.
type User struct {
	ID        uint      `bson:"_id,omitempty"`
	Name      string    `bson:"name"`
	Age       int       `bson:"age"`
	Gender    int8      `bson:"gender"`
	BirthDate time.Time `bson:"birth_date"`
}

var (
	userCollection *mongo.Collection
	ctx            context.Context
	cancel         context.CancelFunc
)

// init function remains the same.
func init() {
	ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
	connectionURI := "mongodb://localhost:27017/"
	clientOptions := options.Client().ApplyURI(connectionURI).
		SetConnectTimeout(5 * time.Second).
		SetAuth(options.Credential{
			Username: "myuser",
			Password: "mypassword",
		})

	client, err := mongo.Connect(ctx, clientOptions)
	if err != nil {
		log.Fatal("Error al conectar a MongoDB:", err)
	}
	err = client.Ping(ctx, nil)
	if err != nil {
		log.Fatal("Error al hacer ping a MongoDB:", err)
	}
	userCollection = client.Database("testdb").Collection("users")
	log.Println("Conexión a MongoDB exitosa y colección lista.")
}

func main() {
	defer cancel()

	// Filtro para eliminar documentos con nombre "Charlie".
	// Se puede usar bson.D o bson.M para definir filtros.
	// filter := bson.D{{Key: "name", Value: bson.D{{Key: "$eq", Value: "Charlie"}}}}
	filter := bson.M{"name": "Charlie"} // Forma más concisa

	// Eliminar un solo documento que coincida con el filtro.
	// deleteResult, err := userCollection.DeleteOne(ctx, filter)

	// Eliminar todos los documentos que coincidan con el filtro.
	deleteResult, err := userCollection.DeleteMany(ctx, filter)

	if err != nil {
		log.Fatal("Error al eliminar documentos:", err)
	}

	fmt.Printf("Número de documentos eliminados: %d\n", deleteResult.DeletedCount)
}

Tipos BSON en Go

El driver de Go para MongoDB proporciona varias estructuras para trabajar con BSON:

  • bson.M: Un mapa sin orden de pares clave-valor (map[string]interface{}). Ideal para condiciones de consulta y operaciones de actualización.
  • bson.D: Una lista ordenada de pares clave-valor ([]bson.E). Útil cuando el orden de los campos es importante, como en comandos de base de datos o pipelines de agregación.
  • bson.A: Un array BSON ([]interface{}). Para almacenar coleciones de valores.
  • bson.E: Un único par clave-valor, usado para construir bson.D.

Tabla Comparativa de Tipos BSON

Tipo Orden Uso Común Ejemplo
bson.M Sin ordenar Filtros de consulta, actualizaciones bson.M{"name": "Alice", "age": bson.M{"$gt": 20}}
bson.D Ordenado Comandos, pipelines de agregación bson.D{{"$match", bson.D{{"status", "active"}}}}
bson.A Ordenado Arrays, listas de valores bson.A{"red", "green", "blue"}
bson.E Ordenado Componente de bson.D bson.E{Key: "name", Value: "Alice"}

Etiquetas BSON en Estructuras Go

Las etiquetas BSON permiten mapear campos de estructuras Go a campos de documentos MongoDB y controlar cómo se serializan/deserializan.

  • `bson:"field_name"``: Mapea un campo a un nombre específico en MongoDB.
  • bson:"_id,omitempty"``: Define el campo como el identificador único _id, omitiéndolo si está vacío al insertar. Requiere usar primitive.ObjectID` como tipo.
  • `bson:"-"``: Ignora el campo, no se incluye en el documento MongoDB.
  • `bson:",inline"``: Embebe el contenido de la estructura o mapa del campo directamante en el documento MongoDB.
  • bson:",omitempty"``: Omite el campo si su valor es el "valor cero" (por ejemplo, 0, "", nil`).

Ejemplo de Estructura con Etiquetas BSON

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

type Product struct {
	ID          primitive.ObjectID `bson:"_id,omitempty"`
	Name        string             `bson:"productName"`
	Description string             `bson:"description,omitempty"`
	Price       float64            `bson:"price"`
	Stock       int                `bson:"stock,omitempty"`
	Details     map[string]string  `bson:"details,inline"` // Embebe el mapa 'Details'
	InternalCode string            `bson:"-"` // Este campo no se guardará en MongoDB
}

Usar las etiquetas BSON correctamente facilita la interacción entre tus modelos de Go y la estructura de tus documentos MongoDB.

Etiquetas: Go MongoDB NoSQL Database BSON

Publicado el 7-6 02:02