Tutorial

Comment utiliser Go avec MongoDB en utilisant le pilote MongoDB Go

MongoDBGoDatabases

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 :

  • export GO111MODULE="on"

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 :

  • mkdir tasker

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

  • cd tasker

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 :

  • go mod init

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 :

  • nano main.go

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 :

  • nano 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 :

  • go run main.go

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 :

  • nano 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 :

  • nano 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: %s\n", i+1, v.Text)
        } else {
            color.Yellow.Printf("%d: %s\n", 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 :

  • go run main.go 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 :

  • nano 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.

  • go run main.go all

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 :

  • nano 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 :

  • go run main.go

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 :

  • nano 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 :

  • go run main.go finished

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 :

  • nano main.go

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 :

  • go run main.go
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 :

  • go run main.go
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.

Creative Commons License