One place for hosting & domains

      MongoDB

      Cómo usar Go con MongoDB utilizando el controlador de Go de MongoDB


      El autor seleccionó la Free Software Foundation para recibir una donación como parte del programa Write for DOnations.

      Introducción

      Después de muchos años de basarse en soluciones desarrolladas por la comunidad, MongoDB anunció que estaban trabajando en un controlador oficial para Go.  Este nuevo controlador estuvo listo para la producción en marzo de 2019 con el lanzamiento de la versión v1.0.0 y, desde entonces, se ha estado actualizando de forma continua.

      Al igual que los demás controladores oficiales de MongoDB, el controlador de Go es característico del lenguaje de programación Go y ofrece una manera sencilla de utilizar MongoDB como solución de base de datos para programas de Go. Está completamente integrado con la API de MongoDB y presenta todas las funciones de consulta, indexación y agregación de la API, así como otras funciones avanzadas. A diferencia de las bibliotecas de terceros, los ingenieros de MongoDB lo respaldarán por completo, por lo que puede tener la seguridad de que se seguirá desarrollando y manteniendo.

      En este tutorial, empezará a utilizar el controlador de oficial de Go de MongoDB. Instalará el controlador, establecerá conexión con una base de datos de MongoDB y realizará varias operaciones CRUD. En el proceso, creará un programa de administración de tareas para gestionar tareas a través de la línea de comandos.

      Requisitos previos

      Para este tutorial, necesitará lo siguiente:

      • Go instalado en su máquina y un espacio de trabajo de Go configurado conforme a Cómo instalar Go y configurar un entorno de programación local. En este tutorial, el proyecto se denominará tasker. Necesitará tener instalada la versión 1.11 o superior de Go en su máquina y los módulos de Go habilitados.
      • MongoDB instalada para su sistema operativo conforme a Cómo instalar MongoDB. MongoDB 2.6 o superior, que es la versión mínima que admite el controlador de Go de MongoDB.

      Si utiliza Go v1.11 o 1.12, asegúrese de que los módulos de Go estén habilitados fijando la variable de entorno GO111MODULE en on como se indica a continuación:

      Para obtener más información sobre la implementación de variables de entorno, consulte el tutorial Cómo leer y establecer variables de entorno y shell.

      Los comandos y el código que se muestran en esta guía se probaron con Go v1.14.1 y MongoDB v3.6.3.

      Paso 1: Instalar el controlador de Go de MongoDB

      En este paso, instalará el paquete del controlador de Go para MongoDB y lo importará en su proyecto. También establecerá conexión con su base de datos de MongoDB y verificará el estado de la conexión.

      Proceda a crear un directorio nuevo para este tutorial en su sistema de archivos:

      Una vez que haya establecido el directorio de su proyecto, posiciónese en él con el siguiente comando:

      A continuación, inicie el proyecto de Go con un archivo go.mod. Este archivo define los requisitos del proyecto y bloquea las dependencias en sus versiones correctas:

      Si el directorio de su proyecto está fuera de $GOPATH, debe especificar la ruta de importación de su módulo de la siguiente manera:

      • go mod init github.com/<your_username>/tasker

      En este punto, su archivo go.mod tendrá el siguiente aspecto:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Agregue el controlador de Go de MongoDB como dependencia de su proyecto utilizando el siguiente comando:

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

      Verá un resultado como el siguiente:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      En este punto, su archivo go.mod tendrá el siguiente aspecto:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      A continuación, cree un archivo main.go en el root de su proyecto y ábralo en su editor de texto:

      Para comenzar a usar el controlador, importe los siguientes paquetes en su archivo main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Aquí, añade los paquetes mongo y options, que proporciona el controlador de Go de MongoDB.

      A continuación, después de realizar sus importaciones, cree un cliente de MongoDB nuevo y establezca conexión con su servidor de MongoDB en ejecución:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() acepta los objetos Context y options.ClientOptions, que se utiliza para establecer la cadena de conexión y otros ajustes del controlador. Puede consultar las opciones de configuración disponibles en la documentación del paquete de opciones.

      El contexto es como un tiempo de espera o un plazo que indica cuándo una operación debe dejar de funcionar y reanudarse. Ayuda a prevenir la degradación del rendimiento en los sistemas de producción cuando determinadas operaciones se ejecutan con lentitud. En este código, está pasando context.TODO() para indicar que no está seguro de qué contexto usar en este momento, pero que planea añadir uno en el futuro.

      A continuación, vamos a asegurarnos de que su servidor de MongoDB se haya encontrado y conectado con éxito utilizando el método Ping.  Añada el siguiente código en la función init:

      main.go

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

      Si hay algún error al establecer conexión con la base de datos, el programa debe bloquearse mientras intenta solucionar el problema, dado que no tiene sentido mantenerlo en ejecución sin una conexión activa con la base de datos.

      Añada el siguiente código para crear una base de datos:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      Creó una base de datos tasker y una colección task para almacenar las tareas que creará. También estableció collection como variable de nivel de paquete para poder reutilizar la conexión a la base de datos en todo el paquete.

      Guarde el archivo y ciérrelo.

      En este punto, todo main.go tiene la siguiente estructura:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Configuró su programa para establecer conexión con su servidor de MongoDB usando el controlador de Go. En el siguiente paso, procederá a crear su programa de administración de tareas.

      Paso 2: Crear un programa de CLI

      En este paso, instalará el famoso paquete cli para ayudar a desarrollar su programa de administración de tareas. Ofrece una interfaz que puede aprovechar para crear rápidamente herramientas de línea de comandos modernas. Por ejemplo, este paquete proporciona la capacidad de definir subcomandos para su programa con el fin de obtener una experiencia de línea de comandos más similar a git.

      Ejecute el siguiente comando para añadir el paquete como dependencia:

      • go get github.com/urfave/cli/v2

      Luego, vuelva a abrir su archivo main.go:

      Añada el siguiente código resaltado a su archivo main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Importó el paquete cli como se indicó. También importó el paquete os, que usará para pasar argumentos de línea de comandos a su programa:

      Añada el siguiente código después de la función init para crear su programa de CLI y hacer que su código se compile:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Este fragmento crea un programa de CLI denominado tasker y añade una descripción de uso breve que se imprimirá al ejecutar el programa. El segmento de Commands es donde agregará comandos para su programa. El comando Run redistribuye el segmento de argumentos al comando correspondiente.

      Guarde y cierre su archivo.

      Este es el comando que necesita para compilar y ejecutar el programa:

      Verá el siguiente resultado:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      El programa ejecuta y muestra texto de ayuda, que es útil para aprender sobre lo que puede hacer el programa y cómo usarlo.

      En los siguientes pasos, mejorará la utilidad de su programa añadiendo subcomandos para ayudar a gestionar sus tareas en MongoDB.

      Paso 3: Crear una tarea

      En este paso, agregará un subcomando a su programa de CLI utilizando el paquete cli. Al final de esta sección, podrá añadir una tarea nueva a la base de datos de MongoDB utilizando un comando add nuevo en su programa de CLI.

      Comience por abrir su archivo main.go:

      Luego, importe los paquetes go.mongodb.org/mongo-driver/bson/primitive, time y errors:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Luego, cree una nueva estructura para representar una sola tarea en la base de datos e insértela inmediatamente antes de la función main:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Utilizó el paquete primitive para establecer el tipo de ID de cada tarea, dado que MongoDB utiliza ObjectID para el campo _id por defecto. Otro comportamiento predeterminado de MongoDB es que el nombre de campo en minúsculas se utiliza como la clave de cada campo exportado cuando se está serializado, pero esto se puede modificar utilizando las etiquetas de estructura bson.

      A continuación, cree una función que reciba una instancia de Task y la guarde en la base de datos. Añada este fragmento después de la función main:

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      El método collection.InsertOne() inserta la tarea proporcionada en la colección de la base de datos y devuelve la ID del documento que se insertó. Como no necesita esta ID, la descarta al asignar al operador de guion bajo.

      El siguiente paso es añadir un comando nuevo a su programa de administración de tareas para crear tareas nuevas. Lo llamaremos add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Todos los comandos nuevos que se añaden a su programa de CLI se colocan dentro del segmento de Commands. Cada uno consta de un nombre, una descripción de uso y una acción. Este es el código que se ejecutará al ejecutar el comando.

      Con este código, se recoge el primer argumento en add y se utiliza para establecer la propiedad Text de una nueva instancia de Task a la vez que se asignan los valores predeterminados correspondientes de las demás propiedades. La tarea nueva se pasa posteriormente a createTask, que inserta la tarea en la base de datos y devuelve nil si todo está bien, lo que provoca que el comando se cierre.

      Guarde y cierre su archivo.

      Pruébelo al añadir algunas tareas utilizando el comando add. Si todo es correcto, no verá errores en la pantalla:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Ahora que puede añadir tareas correctamente, implementaremos una manera de mostrar todas las tareas que añadió a la base de datos.

      Paso 4: Enumerar todas las tareas

      Los documentos de una colección se pueden enumerar utilizando el método collection.Find(), que espera un filtro y un indicador a un valor en el que se pueda decodificar el resultado.  El valor que devuelve es un cursor, que proporciona un flujo de documentos que se pueden iterar y decodificar de a uno a la vez. El cursor se cierra una vez que se agota.

      Abra su archivo main.go:

      Asegúrese de importar el paquete bson:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Luego, cree las siguientes funciones inmediatamente después de createTask:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      BSON (JSON con codificado binario) es la forma en que los documentos se representan en una base de datos de MongoDB, y el paquete bson es lo que nos ayuda a trabajar con los objetos de BSON en Go. El tipo bson.D que se utiliza en la función getAll() representa un documento de BSON y se utiliza cuando el orden de las propiedades es importante. Al pasar bson.D{{}} como su filtro a filterTasks(), indica que desea hacer coincidir todos los documentos de la colección.

      En la función filterTasks(), itera sobre el cursor que devuelve el método collection.Find() y decodifica cada documento en una instancia de Task. Luego, cada Task se anexa al segmento de tareas creadas al principio de la función. Una vez que el cursor se agota, se cierra y se devuelve el segmento de tasks.

      Antes de crear un comando para enumerar todas las tareas, crearemos una función de ayuda que tome un segmento de tasks e imprima en la salida estándar. Utilizará el paquete color para dar color al resultado.

      Para poder utilizar este paquete, instálelo con lo siguiente:

      • go get gopkg.in/gookit/color.v1

      Verá el siguiente resultado:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      Impórtelo en su archivo main.go junto con el paquete fmt:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Luego, cree una función printTasks nueva después de su función main:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      Esta función printTasks toma un segmento de tasks, itera sobre cada una de ellas y las imprime en la salida estándar utilizando el color verde para indicar las tareas completadas y el amarillo para las tareas incompletas.

      Proceda a agregar las siguientes líneas resaltadas para crear un comando all nuevo en el segmento de Commands. Este comando imprimirá todas las tareas añadidas a la salida estándar:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      El comando all obtiene todas las tareas presentes en la base de datos y las imprime en la salida estándar. Si no hay tareas presentes, en su lugar, se imprime una línea de comandos para añadir una tarea nueva.

      Guarde y cierre su archivo.

      Compile y ejecute su programa con el comando all:

      Enumerará todas las tareas que añadió hasta ahora:

      Output

      1: Learn Go 2: Read a book

      Ahora que puede ver todas las tareas de la base de datos, añadiremos la capacidad de marcar una tarea como completa en el siguiente paso.

      Paso 5: Completar una tarea

      En este paso, creará un subcomando nuevo denominado done que le permitirá marcar una tarea existente en la base de datos como completada. Para marcar una tarea como completada, puede utilizar el método collection.FindOneAndUpdate(). Le permite localizar un documento en una colección y actualizar todas sus propiedades o algunas de ellas. Este método requiere un filtro para localizar el documento y un documento de actualización para describir la operación. Los dos se crean utilizando tipos bson.D.

      Comience por abrir su archivo main.go:

      A continuación, inserte el siguiente fragmento después de la función de filterTasks:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      La función coincide con el primer documento en el que la propiedad de texto es igual al parámetro text. El documento update especifica que la propiedad completed se establezca en true. Si hay un error en la operación FindOneAndUpdate(), se devolverá mediante completeTask(). De lo contrario, se devuelve nil.

      A continuación, añadiremos un comando done a su programa de CLI que marca una tarea como completada:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Utiliza el argumento que se pasó al comando done para encontrar el primer documento cuya propiedad text coincida. Si se encuentra, la propiedad completed del documento se establece en true.

      Guarde y cierre su archivo.

      Luego, ejecute su programa con el comando done:

      • go run main.go done "Learn Go"

      Si vuelve a utilizar el comando all, observará que la tarea que se marcó como completada, ahora, se imprime en color verde.

      Captura de pantalla del resultado de la terminal después de completar una tarea

      A veces, solo desea ver las tareas que todavía no se completaron. Añadiremos esa función a continuación.

      Paso 6: Mostrar únicamente tareas pendientes

      En este paso, incorporará código para obtener las tareas pendientes de la base de datos utilizando el controlador de MongoDB. Las tareas pendientes son las que tienen la propiedad completed establecida en false.

      Vamos a añadir una función nueva para obtener las tareas que todavía no se completaron. Abra su archivo main.go:

      Luego, añada este fragmento después de la función completeTask:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Creó un filtro utilizando los paquetes bson y primitive del controlador de MongoDB, que devolverá los documentos que tengan la propiedad completed establecida en false. Luego, el segmento de tareas pendientes se devuelve al autor de la llamada.

      En lugar de crear un comando nuevo para enumerar las tareas pendientes, vamos a hacer que sea la acción predeterminada cuando se ejecute el programa sin comandos. Para hacerlo, vamos a añadir una propiedad Action al programa de la siguiente manera:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      La propiedad Action realiza una acción predeterminada cuando el programa se ejecuta sin ningún subcomando. Aquí es donde se aplica la lógica para enumerar las tareas pendientes. Se invoca la función get getPending() y las tareas resultantes se imprimen en la salida estándar utilizando printTasks(). Si no hay tareas pendientes, se muestra un aviso en su lugar, que anima al usuario a añadir una nueva tarea usando el comando add.

      Guarde y cierre su archivo.

      Ahora, al ejecutar el programa sin añadir ningún comando, se enumerarán todas las tareas pendientes en la base de datos:

      Verá el siguiente resultado:

      Output

      1: Read a book

      Ahora que puede enumerar tareas incompletas, vamos a añadir otro comando que permite ver únicamente las tareas completadas.

      Paso 7: Mostrar tareas completadas

      En este paso, agregará un subcomando finished nuevo que obtiene las tareas completadas de la base de datos y las muestra en la pantalla. Esto incluye filtrar y devolver las tareas que tengan la propiedad completed establecida en true.

      Abra su archivo main.go:

      Luego, añada el siguiente código al final de su archivo:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      De forma similar a lo que hizo con la función getPending(), añadió una función getFinished() que devuelve un segmento de tareas completadas. En este caso, el filtro tiene la propiedad completed establecida en true, por lo tanto, solo se devolverán los documentos que coincidan con esta condición.

      A continuación, cree un comando finished que imprima todas las tareas completadas:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      El comando finished obtiene las tareas que tienen la propiedad completed establecida en true a través de la función getFinished() creada aquí. Luego, las pasa a la función printTasks para que se impriman en la salida estándar.

      Guarde y cierre su archivo.

      Ejecute el siguiente comando:

      Verá el siguiente resultado:

      Output

      1: Learn Go

      En el paso final, proporcionará a los usuarios la opción de eliminar tareas de la base de datos.

      Paso 8: Eliminar una tarea

      En este paso, agregará un subcomando delete para permitir que los usuarios puedan eliminar tareas de la base de datos. Para eliminar una tarea, utilizará el método collection.DeleteOne() del controlador de MongoDB. También utiliza un filtro para buscar el documento que se eliminará.

      Vuelva a abrir su archivo main.go una vez más:

      Añada esta función deleteTask para eliminar tareas de la base de datos directamente después de su función getFinished:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      Este método deleteTask toma un argumento de cadena que representa el elemento de tarea que se va a eliminar. Se crea un filtro para buscar el elemento de tarea que tenga la propiedad text establecida en el argumento de cadena. Pasa el filtro al método DeleteOne() que coincide con el elemento de la colección y lo elimina.

      Puede verificar la propiedad DeletedCount en el resultado del método DeleteOne para verificar si se eliminaron documentos. Si el filtro no encuentra un documento para eliminar, DeletedCount será cero y, en ese caso, puede devolver un error.

      Ahora, añada un comando rm nuevo como se indica en la sección resaltada:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Al igual que con todos los demás subcomandos añadidos anteriormente, el comando rm utiliza su primer argumento para buscar una tarea en la base de datos y la elimina.

      Guarde y cierre su archivo.

      Puede enumerar tareas pendientes ejecutando su programa sin pasar ningún subcomando de la siguiente manera:

      Output

      1: Read a book

      Al ejecutar el subcomando rm en la tarea de "Read a book", se eliminará de la base de datos:

      • go run main.go rm "Read a book"

      Si vuelve a enumerar todas las tareas pendientes, observará que la tarea "Read a book" ya no aparece y, en su lugar, se muestra un aviso para añadir una nueva tarea:

      Output

      Nothing to see here Run `add 'task'` to add a task

      En este paso, añadió una función para eliminar tareas de la base de datos.

      Conclusión

      Creó correctamente un programa de línea de comandos de administración de tareas y aprendió los conceptos básicos del uso del controlador de Go de MongoDB en el proceso.

      Asegúrese de consultar la documentación completa del controlador de Go de MongoDB en GoDoc para obtener más información sobre las funciones que proporciona su utilización. La documentación que describe el uso de agregaciones o transacciones puede resultarle particularmente interesante.

      Puede ver el código final de este tutorial en este repositorio de GitHub.



      Source link

      Использование Go с MongoDB с помощью драйвера Go MongoDB


      Автор выбрал фонд Free Software Foundation для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Много лет MongoDB зависела от решений, создаваемых общими усилиями, но затем ее разработчики объявили, что работают над официальным драйвером для Go. В марте 2019 года этот новый драйвер достиг уровня готовности к эксплуатации в версии 1.0.0, и с тех пор он регулярно обновляется.

      Как и другие официальные драйвера MongoDB, драйвер Go является неотъемлемой частью языка программирования Go и обеспечивает удобную возможность использования MongoDB в качестве решения для баз данных программы Go. Он полностью интегрирован с API MongoDB, а также имеет все функции запросов, индексирования и агрегирования API и другие продвинутые функции. В отличие от сторонних библиотек, он будет полностью поддерживаться инженерами MongoDB, поэтому вы сможете быть уверенными в его дальнейшей разработке и поддержке.

      В этом обучающем руководстве вы начнете использовать официальный драйвер MongoDB Go. Вы установите драйвер, подключитесь к базе данных MongoDB и выполните несколько операций CRUD. В процессе вы создадите программу диспетчера задач для управления задачами с помощью командной строки.

      Предварительные требования

      Для этого обучающего руководства вам потребуется следующее:

      • Go, установленный на вашем компьютере, и рабочее пространство Go, настроенное в соответствии с разделом Установка Go и настройка локальной среды программирования. В этом обучающем руководстве проект будет называться tasker. Вам потребуется Go v1.11 или выше, установленный на компьютере с активированными модулями Go.
      • MongoDB, установленный для вашей операционной системы в соответствии с разделом Установка MongoDB. MongoDB 2.6 или выше — минимальная версия, поддерживаемая драйвером MongoDB Go.

      Если вы используете Go v1.11 или 1.12, то убедитесь, что модули Go активированы, присвоив переменной среды GO111MODULE значение on — так, как показано ниже:

      Дополнительную информацию об активации переменных среды можно найти в этом обучающем руководстве: Чтение и установка переменных сред и переменных оболочки.

      Команды и код, указанные в этом руководстве, были протестированы в Go версии v1.14.1 и MongoDB версии v3.6.3.

      Шаг 1 — Установка драйвера MongoDB Go

      На этом шаге вы установите пакет драйвера Go для MongoDB и импортируете его в ваш проект. Также вы подключитесь к вашей базе данных MongoDB и проверите состояние подключения.

      Теперь создайте новый каталог для этого обучающего руководства в файловой системе:

      После настройки каталога проекта измените его с помощью следующей команды:

      Затем инициализируйте проект Go с файлом go.mod. Этот файл определяет требования проекта и блокирует зависимости от правильных версий:

      Если директория вашего проекта находится за пределами директории $GOPATH, то вам нужно указать путь импорта вашего модуля следующим образом:

      • go mod init github.com/<your_username>/tasker

      Теперь файл go.mod будет выглядеть следующим образом:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Добавьте драйвер MongoDB Go в качестве зависимости для вашего проекта, используя следующую команду:

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

      Результат будет выглядеть примерно следующим образом:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      Теперь файл go.mod будет выглядеть следующим образом:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Затем создайте файл main.go в корневом каталоге проекта и откройте его в текстовом редакторе:

      Для начала работы с драйвером импортируйте следующие пакеты в файл main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Здесь вы добавляете пакеты mongo и опций, которые предоставляет драйвер MongoDB Go.

      Затем, в зависимости от импорта, создайте новый клиент MongoDB и подключитесь к запущенному серверу MongoDB:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() принимает Context и объект options.ClientOptions, который используется для установки строки подключения и других параметров драйвера. Вы можете посмотреть документацию по опциям пакетов, чтобы узнать, какие варианты конфигурации доступны.

      Context — крайний срок, указывающий, когда операция должна остановиться и показать результат. Это помогает предотвращать снижение производительности работы в производственных системах, когда отдельные операции работают медленно. В этом коде вы передаете context.TODO(), чтобы указать, что вы не уверены, какой именно контекст нужно использовать сейчас, но планируете добавить контекст в дальнейшем.

      Затем убедимся, что ваш сервер MongoDB был обнаружен и подключен для успешного использования метода Ping. Добавьте следующий код под функцией init:

      main.go

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

      Если есть какие-либо ошибки при подключении к базе данных, то программа должна быть отключена, пока вы пытаетесь решить проблему, поскольку нет смысла поддерживать работу программы без активного подключения к базе данных.

      Добавьте следующий код для создания базы данных:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      Вы создаете базу данных tasker и набор задач для хранения задач, которые вы создадите. Также вы настроили команду collection в качестве переменной уровня пакетов, чтобы можно было использовать подключение к базе данных на разных этапах пакетов.

      Сохраните и закройте файл.

      На этот момент полный main.go выглядит следующим образом:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Вы выполнили настройку вашей программы для подключения к серверу MongoDB с помощью драйвера Go. На следующем шаге вы продолжите создание программы диспетчера задач.

      Шаг 2 — Создание программы интерфейса командной строки

      На этом шаге вы установите хорошо известный пакет cli (интерфейса командной строки) для оказания помощи в разработке программы диспетчера задач. Он предоставляет интерфейс, которым вы можете воспользоваться для быстрого создания современных инструментов командной строки. Например, этот пакет дает возможность задавать субкоманды для вашей программы, делающие работу с командной строкой более похожей на git.

      Запустите следующую команду для добавления пакета в качестве зависимости:

      • go get github.com/urfave/cli/v2

      Затем откройте файл main.go еще раз:

      Добавьте следующий выделенный код в файл main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Вы импортируете пакет cli, как упоминалось. Также вы импортируете пакет os, который вы будете использовать для передачи аргументов командной строки в вашу программу:

      Добавьте следующий код после функции init, чтобы создать программу интерфейса командной строки и начать компиляцию при помощи кода:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Этот фрагмент кода создает программу интерфейса командной строки под названием tasker и добавляет краткое описание использования, которое будет отображаться при запуске программы. Набор командной строки находится в том месте, где вы будете добавлять команды для вашей программы. Команда Run отображает список аргументов для подходящей команды.

      Сохраните и закройте файл.

      Вот команда, которая вам потребуется для создания и запуска программы:

      Вывод должен выглядеть так:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      Программа запускает и отображает текст справки, это полезно для изучения возможностей и способов использования программы.

      На следующих шагах вы будете повышать эффективность вашей программы, добавляя субкоманды для управления задачами в MongoDB.

      Шаг 3 — Создание задачи

      На этом шаге вы добавите субкоманду в вашу программу CLI при помощи пакета cli. В конце этого раздела вы сможете добавить новую задачу в базу данных MongoDB, используя новую команду add (добавить) в вашей программе CLI.

      Начнем с открытия файла main.go:

      Затем импортируем пакеты go.mongodb.org/mongo-driver/bson/primitive, time (времени) и errors (ошибок):

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Затем создадим новую структуру для представления одной задачи базе данных и ее вставки непосредственно перед функцией main:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Вы будете использовать пакет primitive для установки типа ID каждой задачи, поскольку MongoDB использует ObjectID для поля _idпо умолчанию. Еще одно действие MongoDB по умолчанию — имя поля из строчных букв используется в качестве ключа для каждого экспортированного поля, когда ему присваивают серию, но это можно изменять с использованием тегов структуры bson.

      Затем создадим функцию, которая получает экземпляр Task и сохраняет его в базе данных. Добавьте этот фрагмент кода после функции main:

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      Метод collection.InsertOne() добавляет выделенную задачу в набор баз данных и возвращает ID документа, который был добавлен. Поскольку вам не потребуется этот идентификатор, вы удаляете его, присваивая его оператору подчеркивания.

      Следующий шаг — добавление новой команды в программу диспетчера задач для создания новых задач. Назовем ее add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Каждая новая команда, добавляемая в вашу программу CLI, помещается в список командной строки. Каждый элемент включает имя, описание использования и действия. Это код, который будет запускаться при исполнении команд.

      В этом коде вы берете первый аргумент для add (добавления) и используете его для установки свойства Text новой задачи, а также присваиваете соответствующие значения по умолчанию другим свойствам. Новая задача передается далее в createTask, которая вносит задачу в базу данных и возвращает nil, если все идет хорошо, после чего команда прекращается.

      Сохраните и закройте файл.

      Протестируйте ее, добавив несколько задач с помощью команды add. В случае успешного выполнения на экране не будет ошибок:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Теперь, когда вы можете успешно добавлять задачи, давайте реализуем способ отображения всех задач, которые вы добавляли в базу данных.

      Шаг 4 — Перечисление всех задач

      Перечисление документов в коллекции можно сделать с помощью метода collection.Find(), который предполагает фильтр, а также указатель значения, в которое можно расшифровать результат. Его значение — Cursor, которое предоставляет поток документов, которые можно обрабатывать пошагово, расшифровывая отдельные документы по очереди. Затем Cursor закрывается после завершения его использования.

      Откройте файл main.go:

      Обязательно импортируйте пакет bson:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Затем создайте следующие функции сразу после выполнения createTask:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      BSON (JSON в двоичном коде) — метод представления документов в базе данных MongoDB, а пакет bson это то, что помогает нам работать с объектами BSON в Go. Тип bson.D, используемый в функции getAll(), представляет документ BSON, а также используется в тех случаях, когда порядок свойств важен. Передавая bson.D{}} в качестве фильтра в Tasks(), вы указываете, что хотите сопоставить все документы в коллекции.

      В функции filterTasks() вы пошагово выполняете Cursor, возвращаемый методом collection.Find(), и расшифровываете каждый документ в экземпляр Task (задачи). Затем каждая задача добавляется в список задач, созданных при запуске функции. После завершения использования Cursor будет закрыт, а задачи будут возвращены список задач.

      Прежде чем вы создадите команду для перечисления всех задач, создадим функцию helper (помощник), которая принимает на себя долю задач и отображает стандартный вывод. Вы будете использовать пакет color для придания цвета выводимым результатам.

      Чтобы начать использовать этот пакет, установите его с помощью следующей команды:

      • go get gopkg.in/gookit/color.v1

      Вывод должен выглядеть так:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      И импортируйте его в файл main.go вместе с пакетом fmt:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Затем создайте новую функцию printTasks после функции main:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      Функция printTasks берет на себя список задач, пошагово выполняет каждую задачу и отображает результат в стандартном выводе — зеленым цветом для обозначения завершенных задач и желтым — незавершенных задач.

      Теперь добавьте следующие выделенные строки для создания новой команды all в списке командной строки. Эта команда отобразит все дополнительные задачи в стандартном выводе:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Команда all извлекает все задачи, перечисленные в базе данных, и отображает их в стандартном выводе. Если задачи отсутствуют, вместо этого откроется подсказка с предложением добавить новую задачу.

      Сохраните и закройте файл.

      Создайте программу и запустите ее с помощью команды all:

      Она отобразит все задачи, которые вы добавили на данный момент:

      Output

      1: Learn Go 2: Read a book

      Теперь, когда вы можете просматривать все задачи базы данных, добавим возможность обозначать задачу как завершенную на следующем шаге.

      Шаг 5 — Завершение задачи

      На этом шаге вы создадите новую субкоманду с именем done, которая позволит обозначать существующую задачу в базе данных как завершенную. Для обозначения задачи как завершенной вы можете использовать метод collection.FindOneAndUpdate(). Это позволяет вам найти документ в коллекции и обновить некоторые или все его свойства. В этом методе требуется фильтр для определения местонахождения документа и обновления документа для описания операции. Оба созданы при помощи типов bson.D.

      Начнем с открытия файла main.go:

      Вставьте следующий фрагмент кода после функции filterTasks:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      Функция совпадает с первым документом, где текстовое свойство равняется параметру text. В документе update (обновления) указано, что свойству completed (завершено) можно присвоить значение true (истина). Если в операции FindOneAndUpdate() имеется ошибка, то она будет возвращена командой completeTask(). В противном случае будет возвращено nil.

      Затем добавим новую команду done в вашу программу CLI (интерфейса командной строки), обозначающую задачу как завершенную:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Вы используете аргумент, переданный команде done для нахождения первого документа, чье свойство text совпадает. В случае обнаружения свойству completed в документе будет привоено значение true.

      Сохраните и закройте файл.

      Затем запустите программу с помощью команды done:

      • go run main.go done "Learn Go"

      Если вы снова будете использовать команду all, то увидите, что задача, которая была обозначена как завершенная, теперь отображается зеленым.

      Скриншот вывода на терминал после завершения задачи

      Иногда вы захотите просматривать только те задачи, которые еще не выполнялись. На следующем шаге мы добавим эту функцию.

      Шаг 6 — Отображение только незавершенных задач

      На этом шаге вы будете включать код для извлечения незавершенных задач из базы данных с помощью драйвера MongoDB. Незавершенные задачи — те, чьему свойству completed присвоено значение false.

      Давайте добавим новую функцию, которая извлекает задачи, которые еще не выполнены. Откройте файл main.go:

      Добавьте этот фрагмент кода после функции completeTask:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Вы создаете фильтр при помощи пакетов bson и primitive из драйвера MongoDB, который будет сопоставлять документы, чьему свойству completed присвоено значение false. Затем вызывающему выдается список незавершенных задач.

      Вместо создания новой команды для перечисления незавершенных задач давайте сделаем это действием по умолчанию при запуске программы без каких-либо команд. Вы можете сделать это, добавив в программу свойство Action следующим образом:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      Свойство Action выполняет действие по умолчанию, когда программа выполняется без каких-либо субкоманд. Здесь отображается логика перечисления незавершенных задач. Вызывается функция getPending(), и конечные задачи отображаются в стандартном выводе с помощью printTasks(). Если незавершенных задач нет, отображается подсказка, предлагающая пользователю добавить новую задачу с помощью команды add.

      Сохраните и закройте файл.

      Запуск программы сейчас без добавления каких-либо команд отобразит все незавершенные задачи в базе данных:

      Вывод должен выглядеть так:

      Output

      1: Read a book

      Теперь, когда вы можете указывать незавершенные задачи, добавим другую команду, которая позволяет просматривать только завершенные задачи.

      Шаг 7 — Отображение завершенных задач

      На этом шаге вы добавите новую субкоманду finished, которая извлекает задачи из базы данных и отображает их на экране. Это подразумевает фильтрацию и возврат задач, чьему свойству completed присвоено значение true.

      Откройте файл main.go:

      Добавьте следующий код в конце файла:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Как и в случае с функцией getPending(), вы добавили функцию getFinished(), которая возвращает список завершенных задач. В данном случае свойству completed в фильтре присвоено значение true, поэтому будут выведены только те документы, которые соответствуют этому состоянию.

      Затем создайте команду finished, выводящую все завершенные задачи:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Команда finished извлекает задачи, чьему свойству completed присвоено значение true с помощью функции getFinished(). Затем она передает их в функцию printTasks, чтобы они отображались в стандартном выводе.

      Сохраните и закройте файл.

      Запустите следующую команду:

      Вывод должен выглядеть так:

      Output

      1: Learn Go

      На последнем шаге вы дадите пользователям возможность удалять задачи из базы данных.

      Шаг 8 — Удаление задачи

      На этом шаге вы добавите новую субкоманду delete (удаление), чтобы пользователи могли удалять задачу из базы данных. Для удаления одной задачи вы будете использовать метод collection.DeleteOne() из драйвера MongoDB. Также он использует фильтр для сопоставления удаляемого документа.

      Откройте файл main.go еще раз:

      Добавьте функцию deleteTask для удаления задач из базы данных непосредственно после функции getFinished:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      В этом методе deleteTask имеется строковый аргумент, представляющий удаляемую задачу. Создается фильтр для сопоставления задачи, чьему свойству text присвоен строковый аргумент. Вы передаете фильтр в метод DeleteOne(), который сопоставляет элемент коллекции и удаляет его.

      Вы можете проверить свойство DeletedCount по результату метода DeleteOne, чтобы подтвердить, были ли удалены какие-либо документы. Если фильтр не сможет сопоставить удаляемый документ, то DeletedCount будет равно нулю, и вы сможете вывести ошибку.

      Теперь добавьте новую команду rm, как выделено:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Как и в случае со всеми остальными ранее добавленными субкомандами, команда rm использует первый аргумент для сопоставления задачи в базе данных и ее удаления.

      Сохраните и закройте файл.

      Вы можете указывать незавершенные задачи, запустив программу без передачи субкоманд:

      Output

      1: Read a book

      Запуск субкоманды rm в задаче «Читать книгу» удалит ее из базы данных:

      • go run main.go rm "Read a book"

      Если вы снова укажете все незавершенные задачи, то увидите, что задача «Читать книгу» больше не отображается, а вместо этого отображается подсказка, предлагающая создать новую задачу:

      Output

      Nothing to see here Run `add 'task'` to add a task

      На этом шаге вы добавили функцию для удаления задач из базы данных.

      Заключение

      Вы успешно создали программу «диспетчер задач» интерфейса командной строки и заодно научились основам применения драйвера MongoDB Go.

      Обязательно посмотрите полную документацию по драйверу MongoDB Go в GoDoc, чтобы узнать больше о функциях этого драйвера. Документация, описывающая применение агрегирования или транзакций, может представлять особый интерес для вас.

      Окончательный код этого обучающего руководства можно найти в репозитории GitHub.



      Source link

      Comment utiliser Go avec MongoDB en utilisant le pilote MongoDB Go


      L’auteur a choisi la Free Software Foundation pour recevoir un don dans le cadre du programme Write for DOnations.

      Introduction

      Après s’être appuyée sur des solutions développées par la communauté pendant de nombreuses années, MongoDB a annoncé qu’elle travaillait sur un pilote officiel pour Go. En mars 2019, ce nouveau pilote a atteint un statut prêt pour la production avec la sortie de la v1.0.0 et a été continuellement mis à jour depuis lors.

      Comme les autres pilotes officiels de MongoDB, le pilote Go est idiomatique au langage de programmation Go et fournit un moyen facile d’utiliser MongoDB comme solution de base de données pour un programme Go. Il est entièrement intégré à l’API MongoDB, et expose toutes les fonctions d’interrogation, d’indexation et d’agrégation de l’API, ainsi que d’autres fonctions avancées. Contrairement aux bibliothèques tierces, il sera entièrement pris en charge par les ingénieurs de MongoDB afin que vous puissiez être assuré de son développement et de sa maintenance continus.

      Dans ce tutoriel, vous commencerez à utiliser le pilote officiel Go de MongoDB. Vous allez installer le pilote, vous connecter à une base de données MongoDB et effectuer plusieurs opérations CRUD. Dans le processus, vous créerez un programme de gestion des tâches pour gérer les tâches par la ligne de commande.

      Conditions préalables

      Pour ce tutoriel, vous aurez besoin des éléments suivants :

      Si vous utilisez Go v1.11 ou 1.12, assurez-vous que Go Modules est activé en définissant la variable d’environnement GO111MODULE sur on comme indiqué ci-dessous :

      Pour plus d’informations sur l’implémentation des variables d’environnement, lisez ce tutoriel sur Comment lire et configurer les variables d’environnements et les variables shell.

      Les commandes et le code présentés dans ce guide ont été testés avec Go v1.14.1 et MongoDB v3.6.3.

      Étape 1 — Installation du pilote Go de MongoDB

      Dans cette étape, vous allez installer le package Go Driver pour MongoDB et l’importer dans votre projet. Vous vous connecterez également à votre base de données MongoDB et vérifierez l’état de la connexion.

      Allez-y et créez un nouveau répertoire pour ce tutoriel dans votre système de fichiers :

      Une fois que votre répertoire de projet est configuré, modifiez-le avec la commande suivante :

      Ensuite, initialisez le projet Go avec un fichier go.mod. Ce fichier définit les exigences du projet et verrouille les dépendances à leurs versions correctes :

      Si votre répertoire de projet se trouve en dehors du $GOPATH, vous devez spécifier l’itinéraire d’importation de votre module comme suit :

      • go mod init github.com/<your_username>/tasker

      À ce stade, votre fichier go.mod ressemblera à ceci :

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Ajoutez le pilote MongoDB Go comme dépendance pour votre projet en utilisant la commande suivante :

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

      Vous verrez une sortie comme celle-ci :

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      À ce stade, votre fichier go.mod ressemblera à ceci :

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Ensuite, créez un fichier main.go dans votre projet root et ouvrez-le dans votre éditeur de texte :

      Pour commencer à utiliser le pilote, importez les packages suivants dans votre fichier main.go :

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Vous ajoutez ici les packages mongo et options, que le pilote Go de MongoDB fournit.

      Ensuite, après vos importations, créez un nouveau client MongoDB et connectez-vous à votre serveur MongoDB en cours d’exécution :

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() accepte un Context et un objet options.ClientOptions, qui est utilisé pour définir la chaîne de connexion et d’autres paramètres du pilote. Vous pouvez consulter la documentation du package options pour voir quelles sont les options de configuration disponibles.

      Context est comme un délai ou une date limite qui indique quand une opération doit s’arrêter et reprendre. Il contribue à prévenir la dégradation des performances des systèmes de production lorsque certaines opérations sont lentes. Dans ce code, vous passez context.TODO() pour indiquer que vous n’êtes pas sûr du contexte à utiliser pour le moment, mais que vous prévoyez d’en ajouter un à l’avenir.

      Ensuite, assurons-nous que votre serveur MongoDB a été trouvé et connecté avec succès en utilisant la méthode Ping. Ajoutez le code suivant à l’intérieur de la fonction init :

      main.go

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

      S’il y a des erreurs lors de la connexion à la base de données, le programme devrait planter pendant que vous essayez de résoudre le problème, car il ne sert à rien de laisser le programme fonctionner sans une connexion active à la base de données.

      Ajoutez le code suivant pour créer une base de données :

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

      Vous créez une base de données tasker et une collection task pour stocker les tâches que vous allez créer. Vous configurez également la collection comme une variable au niveau du package afin de pouvoir réutiliser la connexion à la base de données dans l’ensemble du package.

      Enregistrez et quittez le fichier.

      Le main.go complet à ce stade est le suivant :

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Vous avez configuré votre programme pour vous connecter à votre serveur MongoDB en utilisant le pilote Go. Dans l’étape suivante, vous procéderez à la création de votre programme de gestion des tâches.

      Étape 2 — Création d’un programme CLI

      Au cours de cette étape, vous installerez le célèbre package cli pour vous aider à développer votre programme de gestion des tâches. Il offre une interface dont vous pouvez tirer parti pour créer rapidement des outils modernes en ligne de commande. Par exemple, ce package donne la possibilité de définir des sous-commandes destinées à votre programme, pour une expérience en ligne de commande plus proche de celle de git.

      Exécutez la commande suivante pour ajouter le package en tant que dépendance :

      • go get github.com/urfave/cli/v2

      Ensuite, ouvrez à nouveau votre fichier main.go :

      Ajoutez le code surligné suivant à votre fichier main.go :

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Vous importez le package cli comme mentionné. Vous importez également le package os, que vous utiliserez pour passer les arguments de la ligne de commande à votre programme :

      Ajoutez le code suivant après votre fonction init pour créer votre programme CLI et faire en sorte que votre code se compile :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Ce snippet crée un programme CLI appelé tasker et ajoute une courte description d’utilisation qui sera imprimée lorsque vous exécuterez le programme. La slice Commands est le lieu où vous ajouterez des commandes pour votre programme. La commande Run décrit la slice arguments à la commande appropriée.

      Enregistrez et fermez votre fichier

      Voici la commande dont vous avez besoin pour construire et exécuter le programme :

      Vous verrez la sortie suivante :

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

      Le programme s’exécute et affiche un texte d’aide, qui est pratique pour apprendre ce que le programme peut faire, et comment l’utiliser.

      Dans les prochaines étapes, vous améliorerez l’utilité de votre programme en ajoutant des sous-commandes pour vous aider à gérer vos tâches dans MongoDB.

      Étape 3 — Création d’une tâche

      Au cours de cette étape, vous ajouterez une sous-commande à votre programme CLI en utilisant le package cli. À la fin de cette section, vous pourrez ajouter une nouvelle tâche à votre base de données MongoDB en utilisant une nouvelle commande add dans votre programme CLI.

      Commencez par ouvrir votre fichier main.go :

      Ensuite, importez les packages go.mongodb.org/mongo-driver/bson/primitive, time et errors :

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Puis, créez une nouvelle structure pour représenter une seule tâche dans la base de données et insérez-la immédiatement avant la fonction main :

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Vous utilisez le package primitive pour définir le type de l’ID de chaque tâche, puisque MongoDB utilise les ObjectID par défaut pour le champ _id. L’un des autres comportements par défaut de MongoDB est que le nom de champ en minuscules est utilisé comme clé pour chaque champ exporté lors de sa sérialisation, mais cela peut être modifié en utilisant les balises struct bson.

      Ensuite, créez une fonction qui reçoit une instance de Task et l’enregistre dans la base de données. Ajoutez ce snippet à la fonction main :

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      La méthode collection.InsertOne() insère la tâche fournie dans la collection de la base de données et renvoie l’identifiant du document qui a été inséré. Étant donné que vous n’avez pas besoin de cet identifiant, vous le supprimez en l’attribuant à l’opérateur de soulignement.

      L’étape suivante consiste à ajouter une nouvelle commande à votre programme de gestion des tâches pour créer de nouvelles tâches. Appelons-la add :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Toute nouvelle commande ajoutée à votre programme CLI est placée dans la slice Commands. Chacune d’entre elles se compose d’un nom, d’une description d’usage et d’une action. C’est le code qui sera exécuté lors de l’exécution de la commande.

      Dans ce code, vous collectez le premier argument à add et l’utilisez pour définir la propriété Text d’une nouvelle instance Task tout en attribuant les valeurs par défaut appropriées pour les autres propriétés. La nouvelle tâche est ensuite transmise à createTask, qui insère la tâche dans la base de données et renvoie nil si tout va bien, ce qui entraîne la sortie de la commande.

      Enregistrez et fermez votre fichier

      Testez ce processus en ajoutant quelques tâches à l’aide de la commande add. En cas de succès, vous ne verrez aucune erreur s’imprimer sur votre écran :

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

      Maintenant que vous pouvez ajouter des tâches avec succès, mettons en place un moyen d’afficher toutes les tâches que vous avez ajoutées à la base de données.

      Étape 4 — Liste de toutes les tâches

      Le listage des documents d’une collection peut être effectué à l’aide de la méthode collection.Find(), qui attend un filtre ainsi qu’un pointeur vers une valeur en laquelle le résultat peut être décodé. Sa valeur de retour est un Curseur, qui fournit un flux de documents qui peuvent être répétés et décodés un à la fois. Le Curseur est ensuite fermé une fois qu’il a été épuisé.

      Ouvrez votre fichier main.go :

      Veillez à importer le package bson :

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Ensuite, créez les fonctions suivantes immédiatement après createTask :

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      BSON (Binary-encoded JSON) est la façon dont les documents sont représentés dans une base de données MongoDB et le package bson est ce qui nous aide à travailler avec les objets BSON dans Go. Le type bson.D utilisé dans la fonction getAll() représente un document BSON et il est utilisé lorsque l’ordre des propriétés est important. En passant bson.D{{}} comme filtre à filterTasks(), vous indiquez que vous voulez faire correspondre tous les documents de la collection.

      Dans la fonction filterTasks(), vous itérez sur le Curseur renvoyé par la méthode collection.Find() et décodez chaque document en une instance de Task. Chaque Task est ensuite annexée à la slice de tâches créée au début de la fonction. Une fois que le Curseur est épuisé, il est fermé et la slice des tasks est retournée.

      Avant de créer une commande pour lister toutes les tâches, créons une fonction d’aide qui prend une slice de tasks et l’imprime sur la sortie standard. Vous utiliserez le package color pour colorer la sortie.

      Avant de pouvoir utiliser ce package, installez le avec :

      • go get gopkg.in/gookit/color.v1

      Vous verrez la sortie suivante :

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      Et importez-le dans votre fichier main.go en même temps que le package fmt :

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Ensuite, créez une nouvelle fonction printTasks à la suite de votre fonction main :

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

      Cette fonction printTasks prend une slice de tasks, l’itére sur chacune et l’imprime sur la sortie standard en utilisant la couleur verte pour indiquer les tâches terminées, et jaune pour les tâches incomplètes.

      Poursuivez en ajoutant les lignes surlignées suivantes pour créer une nouvelle commande all dans la slice Commands. Cette commande permet d’imprimer toutes les tâches ajoutées à la sortie standard :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      La commande all récupère toutes les tâches présentes dans la base de données et les imprime sur la sortie standard. Si aucune tâche n’est présente, une invite à ajouter une nouvelle tâche est imprimée à la place.

      Enregistrez et fermez votre fichier

      Construisez et exécutez votre programme avec la commande all :

      Il énumérera toutes les tâches que vous avez ajoutées jusqu’à présent :

      Output

      1: Learn Go 2: Read a book

      Maintenant que vous pouvez visualiser toutes les tâches dans la base de données, ajoutons la possibilité de marquer une tâche comme terminée à l’étape suivante.

      Étape 5 — Fin d’une tâche

      Dans cette étape, vous créerez une nouvelle sous-commande appelée done qui vous permettra de marquer une tâche existante dans la base de données comme étant terminée. Pour marquer une tâche comme terminée, vous pouvez utiliser la méthode collection.FindOneAndUpdate(). Il permet de localiser un document dans une collection et de mettre à jour toutes ou une partie de ses propriétés. Cette méthode nécessite un filtre pour localiser le document et un document de mise à jour pour décrire l’opération. Les deux sont construits en utilisant les types bson.D.

      Commencez par ouvrir votre fichier main.go :

      Insérez le snippet suivant à la suite de votre fonction filterTasks :

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      La fonction correspond au premier document où la propriété de texte est égale au paramètre text. Le document update précise que la propriété completed doit être réglée sur true. S’il y a une erreur dans l’opération FindOneAndUpdate(), elle sera renvoyée par completeTask(). Sinon, nil est renvoyé.

      Ensuite, ajoutons une nouvelle commande done à votre programme CLI qui marque une tâche comme étant terminée :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Vous utilisez l’argument passé à la commande done pour trouver le premier document dont la propriété text correspond. S’il est trouvé, la propriété completed sur le document est réglée sur true.

      Enregistrez et fermez votre fichier

      Ensuite, exécutez votre programme avec la commande done :

      • go run main.go done "Learn Go"

      Si vous utilisez à nouveau la commande all, vous remarquerez que la tâche qui était marquée comme terminée est maintenant imprimée en vert.

      Capture d'écran de la sortie du terminal après l'achèvement d'une tâche

      Parfois, vous voulez seulement voir les tâches qui n’ont pas encore été terminées. Nous ajouterons cet élément par la suite.

      Étape 6 — Affichage des tâches en attente uniquement

      Dans cette étape, vous allez incorporer du code pour récupérer les tâches en attente dans la base de données, en utilisant le pilote MongoDB. Les tâches en attente sont celles dont la propriété completed est réglée sur false.

      Ajoutons une nouvelle fonction qui récupère les tâches qui n’ont pas encore été terminées. Ouvrez votre fichier main.go :

      Ajoutez ensuite ce snippet à la suite de la fonction completeTask :

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Vous créez un filtre en utilisant les package bson et primitive du pilote MongoDB, qui correspondra aux documents dont la propriété completed est réglée sur false. La slice des tâches en attente est ensuite renvoyée à l’appelant.

      Au lieu de créer une nouvelle commande pour lister les tâches en attente, faisons en sorte que ce soit l’action par défaut lors de l’exécution du programme sans aucune commande. Vous pouvez effectuer ceci en ajoutant une propriété Action au programme comme suit :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      La propriété Action exécute une action par défaut lorsque le programme est exécuté sans aucune sous-commande. C’est là que se situe la logique de la liste des tâches en attente. La fonction getPending() est appelée et les tâches résultantes sont imprimées sur la sortie standard à l’aide de printTasks(). S’il n’y a pas de tâches en attente, une invite est affichée à la place, encourageant l’utilisateur à ajouter une nouvelle tâche à l’aide de la commande add.

      Enregistrez et fermez votre fichier

      Si vous exécutez le programme maintenant sans ajouter de commandes, toutes les tâches en attente seront listées dans la base de données :

      Vous verrez la sortie suivante :

      Output

      1: Read a book

      Maintenant que vous pouvez lister les tâches incomplètes, ajoutons une autre commande qui vous permet de ne voir que les tâches terminées.

      Étape 7 — Affichage des tâches terminées

      Dans cette étape, vous ajouterez une nouvelle sous-commande finished qui récupère les tâches terminées dans la base de données et les affiche à l’écran. Cela implique de filtrer et de renvoyer les tâches dont la propriété completed est réglée sur true.

      Ouvrez votre fichier main.go :

      Ajoutez ensuite le code suivant à la fin de votre dossier :

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Comme pour la fonction getPending(), vous avez ajouté une fonction getFinished() qui renvoie une slice de tâches terminées. Dans ce cas, le filtre a la propriété completed réglée sur true, de sorte que seuls les documents qui correspondent à cette condition seront retournés.

      Ensuite, créez une commande finished qui imprime toutes les tâches terminées :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      La commande finished récupère les tâches dont la propriété completed est réglée sur true via la fonction getFinished() créée ici. Il la transmet ensuite à la fonction printTasks afin qu’elle soit imprimée sur la sortie standard.

      Enregistrez et fermez votre fichier

      Exécutez la commande suivante :

      Vous verrez la sortie suivante :

      Output

      1: Learn Go

      Dans la dernière étape, vous donnerez aux utilisateurs la possibilité de supprimer des tâches de la base de données.

      Étape 8 — Suppression d’une tâche

      Dans cette étape, vous ajouterez une nouvelle sous-commande delete pour permettre aux utilisateurs de supprimer une tâche de la base de données. Pour supprimer une seule tâche, vous utiliserez la méthode collection.DeleteOne() du pilote MongoDB. Il s’appuie également sur un filtre pour faire correspondre le document à supprimer.

      Ouvrez votre fichier main.go une fois de plus :

      Ajoutez cette fonction deleteTask pour supprimer des tâches de la base de données juste après votre fonction getFinished :

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      Cette méthode deleteTask prend un argument de type chaîne de caractères qui représente l’élément de tâche à supprimer. Un filtre est construit pour correspondre à l’élément de tâche dont la propriété text est réglée sur l’argument de la chaîne de caractères. Vous passez le filtre à la méthode DeleteOne() qui correspond à l’article dans la collection et le supprime.

      Vous pouvez vérifier la propriété DeletedCount sur le résultat de la méthode DeleteOne pour confirmer si des documents ont été supprimés. Si le filtre ne parvient pas à faire correspondre un document à supprimer, le DeletedCount sera égal à zéro et vous pourrez renvoyer une erreur dans ce cas.

      Ajoutez maintenant une nouvelle commande rm comme surlignée :

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Comme pour toutes les autres sous-commandes ajoutées précédemment, la commande rm utilise son premier argument pour faire correspondre une tâche dans la base de données et la supprime.

      Enregistrez et fermez votre fichier

      Vous pouvez lister les tâches en attente en exécutant votre programme sans passer par des sous-commandes :

      Output

      1: Read a book

      L’exécution de la sous-commande rm sur la tâche "Read a book" la supprimera de la base de données :

      • go run main.go rm "Read a book"

      Si vous listez à nouveau toutes les tâches en attente, vous remarquerez que la tâche "Read a book" n’apparaît plus et qu’une invite à ajouter une nouvelle tâche est affichée à la place :

      Output

      Nothing to see here Run `add 'task'` to add a task

      Dans cette étape, vous avez ajouté une fonction permettant de supprimer des tâches de la base de données.

      Conclusion

      Vous avez créé avec succès un programme de ligne de commande de gestionnaire de tâches et appris les bases de l’utilisation du pilote MongoDB Go dans le processus.

      N’oubliez pas de consulter la documentation complète du pilote MongoDB Go sur GoDoc pour en savoir plus sur les fonctionnalités qu’offre l’utilisation du pilote. La documentation qui décrit l’utilisation des agrégats ou des transactions peut vous intéresser particulièrement.

      Le code final de ce tutoriel peut être consulté dans ce dépôt GitHub.



      Source link