Tutorial

So verwenden Sie Go mit MongoDB mithilfe des MongoDB Go-Treibers

MongoDBGoDatabases

Der Autor wählte die Free Software Foundation, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

Einführung

Nachdem MongoDB sich viele Jahre lang auf von der Community entwickelte Lösungen verlassen hatte, gab MongoDB bekannt, dass sie an einem offiziellen Treiber für Go arbeiten. Im März 2019 erreichte dieser neue Treiber mit der Veröffentlichung von v1.0.0 den Status „Produktionsbereit“ und wurde seitdem kontinuierlich aktualisiert.

Wie die anderen offiziellen MongoDB-Treiber ist der Go-Treiber für die Go-Programmiersprache typisch und bietet eine einfache Möglichkeit, MongoDB als Datenbanklösung für ein Go-Programm zu verwenden. Er ist vollständig in die MongoDB-API integriert und stellt alle Abfrage-, Indexierungs- und Aggregationsfunktionen der API sowie andere erweiterte Funktionen zur Verfügung. Im Gegensatz zu Bibliotheken von Drittanbietern wird er von MongoDB-Ingenieuren vollständig unterstützt, sodass Sie sicher sein können, dass er weiterentwickelt und gewartet wird.

In diesem Tutorial lernen Sie den offiziellen MongoDB Go-Treiber kennen. Sie installieren den Treiber, stellen eine Verbindung zu einer MongoDB-Datenbank her und führen mehrere CRUD-Vorgänge aus. Dabei erstellen Sie ein Task-Manager-Programm zum Verwalten von Aufgaben über die Befehlszeile.

Voraussetzungen

Für dieses Tutorial benötigen Sie Folgendes:

  • Go auf Ihrem Computer installiert und einen Go-Arbeitsbereich, der wie folgt konfiguriert wird: Installieren von Go und Einrichten einer lokalen Programmierumgebung. In diesem Tutorial wird das Projekt als tasker bezeichnet. Sie müssen Go v1.11 oder höher auf Ihrem Computer mit aktivierten Go-Modulen installiert haben.
  • MongoDB für Ihr Betriebssystem gemäß der Installation von MongoDB installiert. MongoDB 2.6 oder höher ist die Mindestversion, die vom MongoDB Go-Treiber unterstützt wird.

Wenn Sie Go v1.11 oder 1.12 verwenden, stellen Sie sicher, dass Go Modules aktiviert ist, indem Sie die Umgebungsvariable GO111MODULE wie folgt auf on setzen:

  • export GO111MODULE="on"

Weitere Informationen zum Implementieren von Umgebungsvariablen finden Sie in diesem Tutorial zum Lesen und Festlegen von Umgebungs- und Shell-Variablen.

Die in diesem Leitfaden gezeigten Befehle und Codes wurden mit Go v1.14.1 und MongoDB v3.6.3 getestet.

Schritt 1 — Installieren des MongoDB Go-Treibers

In diesem Schritt installieren Sie das Paket Go Driver für MongoDB und importieren es in Ihr Projekt. Außerdem stellen Sie eine Verbindung zu Ihrer MongoDB-Datenbank her und überprüfen den Status der Verbindung.

Fahren Sie fort und erstellen Sie ein neues Verzeichnis für dieses Tutorial in Ihrem Dateisystem:

  • mkdir tasker

Sobald Ihr Projektverzeichnis eingerichtet ist, ändern Sie es mit dem folgenden Befehl:

  • cd tasker

Initialisieren Sie als Nächstes das Go-Projekt mit einer go.mod-Datei. Diese Datei definiert die Projektanforderungen und die Abhängigkeiten ihrer richtigen Versionen:

  • go mod init

Wenn Ihr Projektverzeichnis außerhalb von $GOPATH ist, müssen Sie den Importpfad für Ihr Modul wie folgt angeben:

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

Nun sieht Ihre Datei go.mod wie folgt aus:

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

go 1.14

Fügen Sie mit dem folgenden Befehl den MongoDB Go-Treiber als eine Abhängigkeit für Ihr Projekt hinzu:

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

Sie sehen eine Ausgabe wie die folgende:

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

Nun sieht Ihre Datei go.mod wie folgt aus:

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

go 1.14

require go.mongodb.org/mongo-driver v1.3.1 // indirect

Erstellen Sie als Nächstes eine main.go-Datei in Ihrem Projektstamm und öffnen Sie sie in Ihrem Texteditor:

  • nano main.go

Importieren Sie die folgenden Pakete in Ihre main.go-Datei, um mit dem Treiber zu beginnen:

main.go
package main

import (
    "context"
    "log"

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

Hier fügen Sie die Pakete mongo und options hinzu, die der MongoDB Go-Treiber bereitstellt.

Erstellen Sie nach Ihren Importen einen neuen MongoDB-Client und stellen Sie eine Verbindung zu Ihrem laufenden MongoDB-Server her:

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() akzeptiert ein Objekt Context und ein options.ClientOptions-Objekt, mit denen die Verbindungszeichenfolge und andere Treibereinstellungen festgelegt werden. In der Dokumentation zum Optionspaket finden Sie Informationen zu den verfügbaren Konfigurationsoptionen.

Context ist wie eine Zeitüberschreitung oder eine Frist, die angibt, wann ein Vorgang nicht mehr ausgeführt und zurückgegeben werden soll. Dies hilft, Leistungseinbußen auf Produktionssystemen zu vermeiden, wenn bestimmte Vorgänge langsam ausgeführt werden. In diesem Code übergeben Sie context.TODO(), um anzuzeigen, dass Sie nicht sicher sind, welchen Kontext Sie derzeit verwenden sollen, aber Sie planen, in Zukunft einen hinzuzufügen.

Stellen Sie als Nächstes sicher, dass Ihr MongoDB-Server mithilfe der Ping-Methode gefunden und erfolgreich verbunden wurde. Fügen Sie den folgenden Code in die init-Funktion ein:

main.go
. . .
    log.Fatal(err)
  }

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

Wenn beim Herstellen einer Verbindung zur Datenbank Fehler auftreten, sollte das Programm abstürzen, während Sie versuchen, das Problem zu beheben, da es keinen Sinn macht, das Programm ohne aktive Datenbankverbindung auszuführen.

Fügen Sie den folgenden Code hinzu, um eine Datenbank zu erstellen:

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

  collection = client.Database("tasker").Collection("tasks")
}

Sie erstellen eine tasker-Datenbank und eine task-Sammlung, um die zu erstellenden Aufgaben zu speichern. Sie richten collection auch als Variable auf Paketebene ein, damit Sie die Datenbankverbindung im gesamten Paket wiederverwenden können.

Speichern und schließen Sie die Datei.

Das vollständige main.go ist nun wie folgt:

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")
}

Sie haben Ihr Programm eingerichtet, um über den Go-Treiber eine Verbindung zu Ihrem MongoDB-Server zu erhalten. Im nächsten Schritt erstellen Sie Ihr Task-Manager-Programm.

Schritt 2 — Erstellen eines CLI-Programms

In diesem Schritt installieren Sie das bekannte cli-Paket, um die Entwicklung Ihres Task-Manager-Programms zu unterstützen. Es bietet eine Schnittstelle, über die Sie schnell moderne Befehlszeilentools erstellen können. Dieses Paket bietet beispielsweise die Möglichkeit, Unterbefehle für Ihr Programm zu definieren, um eine git-ähnliche Befehlszeilenerfahrung zu erzielen.

Führen Sie den folgenden Befehl aus, um das Paket als Abhängigkeit hinzuzufügen:

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

Öffnen Sie als Nächstes Ihre main.go-Datei erneut:

  • nano main.go

Fügen Sie Ihrer main.go-Datei den folgenden hervorgehobenen Code hinzu:

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"
)
. . .

Sie importieren das cli-Paket wie erwähnt. Außerdem importieren Sie das Paket os, das Sie verwenden, um Befehlszeilenargumente an Ihr Programm zu übergeben:

Fügen Sie nach Ihrer init-Funktion den folgenden Code hinzu, um Ihr CLI-Programm zu erstellen und den Code zu kompilieren:

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)
    }
}

Dieses Snippet erstellt ein CLI-Programm namens tasker und fügt eine kurze Verwendungsbeschreibung hinzu, die beim Ausführen des Programms ausgedruckt wird. Im Befehlsfenster fügen Sie Befehle für Ihr Programm hinzu. Der Befehl Run analysiert die Argumente auf den entsprechenden Befehl.

Speichern und schließen Sie Ihre Datei.

Hier ist der Befehl, den Sie zum Erstellen und Ausführen des Programms benötigen:

  • go run main.go

Sie sehen die folgende Ausgabe:

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)

Das Programm wird ausgeführt und zeigt einen Hilfetext an, in dem Sie erfahren, was das Programm kann und wie es verwendet wird.

In den nächsten Schritten verbessern Sie die Nützlichkeit Ihres Programms, indem Sie Unterbefehle hinzufügen, um Ihre Aufgaben in MongoDB zu verwalten.

Schritt 3 — Erstellen einer Aufgabe

In diesem Schritt fügen Sie Ihrem CLI-Programm mithilfe des cli-Pakets einen Unterbefehl hinzu. Am Ende dieses Abschnitts können Sie Ihrer MongoDB-Datenbank eine neue Aufgabe hinzufügen, indem Sie einen neuen add-Befehl in Ihrem CLI-Programm verwenden.

Öffnen Sie zunächst Ihre main.go-Datei:

  • nano main.go

Importieren Sie als Nächstes die Pakete go.mongodb.org/mongo-driver/bson/primitive, time und 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"
)
. . .

Erstellen Sie dann eine neue Struktur, um eine einzelne Aufgabe in der Datenbank darzustellen, und fügen Sie sie unmittelbar vor der Hauptfunktion ein:

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"`
}
. . .

Sie verwenden das primitive Paket, um den Typ der ID jeder Aufgabe festzulegen, da MongoDB standardmäßig ObjectIDs für das Feld _id verwendet. Ein weiteres Standardverhalten von MongoDB besteht darin, dass der Feldname in Kleinbuchstaben als Schlüssel für jedes exportierte Feld verwendet wird, wenn es serialisiert wird. Dies kann jedoch mithilfe von bson struct-Tags geändert werden.

Erstellen Sie als Nächstes eine Funktion, die eine Instanz der Aufgabe empfängt und in der Datenbank speichert. Fügen Sie dieses Snippet nach der Hauptfunktion hinzu:

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

Die Methode collection.InsertOne() fügt die bereitgestellte Aufgabe in die Datenbanksammlung ein und gibt die ID des eingefügten Dokuments zurück. Da Sie diese ID nicht benötigen, verwerfen Sie sie, indem Sie sie dem Unterstrichoperator zuweisen.

Der nächste Schritt besteht darin, Ihrem Task-Manager-Programm einen neuen Befehl zum Erstellen neuer Aufgaben hinzuzufügen. Nennen wir ihn 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)
    }
}

Jeder neue Befehl, der Ihrem CLI-Programm hinzugefügt wird, wird im Fenster Befehle platziert. Jeder besteht aus einem Namen, einer Verwendungsbeschreibung und einer Aktion. Dies ist der Code, der bei der Befehlsausführung ausgeführt wird.

In diesem Code sammeln Sie das erste add-Argument und verwenden es, um die Texteigenschaft einer neuen Aufgabeninstanz festzulegen, während Sie die entsprechenden Standardeinstellungen für die anderen Eigenschaften zuweisen. Die neue Aufgabe wird anschließend an createTask weitergeleitet, die die Aufgabe in die Datenbank einfügt und nil zurückgibt, wenn alles gut geht und der Befehl beendet wird.

Speichern und schließen Sie Ihre Datei.

Testen Sie es, indem Sie mit dem Befehl add einige Aufgaben hinzufügen. Bei Erfolg werden keine Fehler auf Ihrem Bildschirm angezeigt:

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

Nachdem Sie nun erfolgreich Aufgaben hinzufügen können, implementieren wir eine Möglichkeit, alle Aufgaben anzuzeigen, die Sie der Datenbank hinzugefügt haben.

Schritt 4 — Auflisten aller Aufgaben

Das Auflisten der Dokumente in einer Sammlung kann mit der Methode collection.Find() erfolgen, die einen Filter sowie einen Zeiger auf einen Wert erwartet, in den das Ergebnis dekodiert werden kann. Der Rückgabewert ist ein Cursor, der eine Reihe an Dokumenten bereitstellt, die einzeln durchlaufen und dekodiert werden können. Der Cursor wird dann geschlossen, sobald er erschöpft ist.

Öffnen Sie Ihre main.go-Datei:

  • nano main.go

Stellen Sie sicher, dass das Paket bson importiert wird:

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"
)
. . .

Erstellen Sie dann unmittelbar nach createTask die folgenden Funktionen:

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
}

Mit BSON (Binary-coded JSON) werden Dokumente in einer MongoDB-Datenbank dargestellt, und das bson-Paket hilft uns bei der Arbeit mit BSON-Objekten in Go. Der in der Funktion getAll() verwendete Typ bson.D stellt ein BSON-Dokument dar und wird dort verwendet, wo die Reihenfolge der Eigenschaften von Bedeutung ist. Indem Sie bson.D{{}} als Filter an filterTasks() übergeben, geben Sie an, dass Sie mit allen Dokumenten in der Sammlung übereinstimmen möchten.

In der Funktion filterTasks() iterieren Sie über den von der collection.Find()-Methode zurückgegebenen Cursor und dekodieren jedes Dokument in eine Instanz der Aufgabe. Jede Aufgabe wird dann an den zu Beginn der Funktion erstellten Aufgabenbereich angehängt. Sobald der Cursor erschöpft ist, wird er geschlossen und das Aufgabenfenster zurückgegeben.

Bevor Sie einen Befehl zum Auflisten aller Aufgaben erstellen, erstellen wir eine Hilfsfunktion, die einen Teil der Aufgaben übernimmt und in die Standardausgabe druckt. Sie verwenden das Paket color, um die Ausgabe zu färben.

Bevor Sie dieses Paket verwenden können, installieren Sie es mit:

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

Sie sehen die folgende Ausgabe:

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

Und importieren Sie sie zusammen mit dem fmt-Paket in Ihre main.go-Datei:

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"
)
. . .

Erstellen Sie als Nächstes eine neue printTasks-Funktion, die Ihrer Hauptfunktion folgt:

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)
        }
    }
}
. . .

Diese printTasks-Funktion übernimmt eine Reihe von Aufgaben, durchläuft jede einzelne und druckt sie in der Standardausgabe aus. Dabei wird die grüne Farbe verwendet, um abgeschlossene Aufgaben anzuzeigen, und gelb für unvollständige Aufgaben.

Fahren Sie fort und fügen Sie die folgenden hervorgehobenen Zeilen hinzu, um einen neuen all-Befehl im Fenster Befehle zu erstellen. Dieser Befehl druckt alle hinzugefügten Aufgaben in die Standardausgabe:

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)
    }
}

. . .

Der Befehl all ruft alle in der Datenbank vorhandenen Aufgaben ab und druckt sie in die Standardausgabe. Wenn keine Aufgaben vorhanden sind, wird stattdessen eine Eingabeaufforderung zum Hinzufügen einer neuen Aufgabe gedruckt.

Speichern und schließen Sie Ihre Datei.

Erstellen und führen Sie Ihr Programm mit dem Befehl all aus:

  • go run main.go all

Er wird alle Aufgaben, die Sie bisher hinzugefügt haben, auflisten:

Output
1: Learn Go 2: Read a book

Nachdem Sie nun alle Aufgaben in der Datenbank anzeigen können, können Sie im nächsten Schritt die Möglichkeit hinzufügen, eine Aufgabe als erledigt zu markieren.

Schritt 5 — Abschließen einer Aufgabe

In diesem Schritt erstellen Sie einen neuen Unterbefehl namens done, mit dem Sie eine vorhandene Aufgabe in der Datenbank als erledigt markieren können. Um eine Aufgabe als abgeschlossen zu markieren, können Sie die Methode collection.FindOneAndUpdate() verwenden. Sie ermöglicht es Ihnen, ein Dokument in einer Sammlung zu lokalisieren und einige oder alle seine Eigenschaften zu aktualisieren. Diese Methode erfordert einen Filter zum Auffinden des Dokuments und ein Aktualisierungsdokument zum Beschreiben des Vorgangs. Beide werden mit den Typen bson.D erstellt.

Beginnen Sie durch Öffnen Ihrer main.go-Datei:

  • nano main.go

Fügen Sie den folgenden Snippet nach Ihrer Funktion filterTasks ein:

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)
}
. . .

Die Funktion entspricht dem ersten Dokument, in dem die Texteigenschaft dem Textparameter entspricht. Das Dokument update gibt an, dass die Eigenschaft completed auf true gesetzt wird. Wenn beim Vorgang FindOneAndUpdate() ein Fehler auftritt, wird dieser von completeTask() zurückgegeben. Andernfalls wird nil zurückgegeben.

Als Nächstes fügen wir Ihrem CLI-Programm einen neuen done-Befehl hinzu, der eine Aufgabe als erledigt markiert:

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)
    }
}

. . .

Sie verwenden das an den Befehl done übergebene Argument, um das erste Dokument zu finden, dessen Texteigenschaft übereinstimmt. Wenn es gefunden wurde, wird die Eigenschaft completed im Dokument auf true gesetzt.

Speichern und schließen Sie Ihre Datei.

Führen Sie dann Ihr Programm mit dem Befehl done aus:

  • go run main.go done "Learn Go"

Wenn Sie den Befehl all erneut verwenden, werden Sie feststellen, dass die als erledigt markierte Aufgabe jetzt grün gedruckt wird.

  • go run main.go all

Screenshot der Terminalausgabe nach dem Ausführen einer Aufgabe

Manchmal möchten Sie nur Aufgaben anzeigen, die noch nicht erledigt sind. Wir fügen diese Eigenschaft als Nächstes hinzu.

Schritt 6 — Nur ausstehende Aufgaben anzeigen

In diesem Schritt integrieren Sie einen Code zum Abrufen ausstehender Aufgaben aus der Datenbank mithilfe des MongoDB-Treibers. Ausstehende Aufgaben sind Aufgaben, deren Eigenschaft completed auf true gesetzt ist.

Lassen Sie uns eine neue Funktion hinzufügen, die Aufgaben abruft, die noch nicht abgeschlossen sind. Öffnen Sie Ihre main.go-Datei:

  • nano main.go

Fügen Sie dann dieses Snippet nach der Funktion completeTask hinzu:

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

    return filterTasks(filter)
}
. . .

Sie erstellen einen Filter mit den Paketen bson und primitive aus dem MongoDB-Treiber, der mit Dokumenten übereinstimmt, deren Eigenschaft completed auf true gesetzt ist. Das Segment ausstehender Aufgaben wird dann an den Anrufer zurückgegeben.

Anstatt einen neuen Befehl zum Auflisten ausstehender Aufgaben zu erstellen, sollten Sie ihn zur Standardaktion machen, wenn Sie das Programm ohne Befehle ausführen. Sie können dies tun, indem Sie dem Programm eine Action-Eigenschaft wie folgt hinzufügen:

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)
                },
            },
. . .

Die Action-Eigenschaft führt eine Standardaktion aus, wenn das Programm ohne Unterbefehle ausgeführt wird. Hier wird eine Logik für das Auflisten ausstehender Aufgaben platziert. Die Funktion getPending() wird aufgerufen und die resultierenden Aufgaben werden mit printTasks() in die Standardausgabe gedruckt. Wenn keine ausstehenden Aufgaben vorhanden sind, wird stattdessen eine Eingabeaufforderung angezeigt, in der der Benutzer aufgefordert wird, mit dem Befehl add eine neue Aufgabe hinzuzufügen.

Speichern und schließen Sie Ihre Datei.

Wenn Sie das Programm jetzt ausführen, ohne Befehle hinzuzufügen, werden alle ausstehenden Aufgaben in der Datenbank aufgelistet:

  • go run main.go

Sie sehen die folgende Ausgabe:

Output
1: Read a book

Nachdem Sie unvollständige Aufgaben aufgelistet haben, fügen wir einen weiteren Befehl hinzu, mit dem Sie nur abgeschlossene Aufgaben anzeigen können.

Schritt 7 — Anzeigen von abgeschlossenen Aufgaben

In diesem Schritt fügen Sie einen neuen Unterbefehl finished hinzu, der erledigte Aufgaben aus der Datenbank abruft und auf dem Bildschirm anzeigt. Dies beinhaltet das Filtern und Zurückgeben von Aufgaben, deren Eigenschaft completed auf true gesetzt ist.

Öffnen Sie Ihre main.go-Datei:

  • nano main.go

Fügen Sie dann am Ende Ihrer Datei den folgenden Code hinzu:

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

    return filterTasks(filter)
}
. . .

Ähnlich wie bei der Funktion getPending() haben Sie eine Funktion getFinished() hinzugefügt, die einen Teil der abgeschlossenen Aufgaben zurückgibt. In diesem Fall hat der Filter die Eigenschaft completed auf true gesetzt, sodass nur die Dokumente zurückgegeben werden, die dieser Bedingung entsprechen.

Erstellen Sie als Nächstes einen Befehl finished, der alle erledigten Aufgaben druckt:

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)
    }
}
. . .

Der Befehl finished ruft Aufgaben ab, deren Eigenschaft completed über die hier erstellte Funktion getFinished() auf true gesetzt ist. Anschließend werden sie an die Funktion printTasks übergeben, sodass sie in der Standardausgabe gedruckt werden.

Speichern und schließen Sie Ihre Datei.

Führen Sie den folgenden Befehl aus:

  • go run main.go finished

Sie sehen die folgende Ausgabe:

Output
1: Learn Go

Im letzten Schritt geben Sie Benutzern die Option, Aufgaben aus der Datenbank zu löschen.

Schritt 8 — Löschen einer Aufgabe

In diesem Schritt fügen Sie einen neuen Unterbefehl delete hinzu, um Benutzern zu ermöglichen, eine Aufgabe aus der Datenbank zu löschen. Um eine einzelne Aufgabe zu löschen, verwenden Sie die Methode collection.DeleteOne() vom MongoDB-Treiber. Außerdem stützt er sich auf einen Filter, der dem Dokument entspricht, um das Dokument zu löschen.

Öffnen Sie Ihre main.go-Datei erneut:

  • nano main.go

Fügen Sie diese Funktion deleteTask hinzu, um Aufgaben direkt nach Ihrer Funktion getFinished aus der Datenbank zu löschen:

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
}
. . .

Diese deleteTask-Methode verwendet ein Zeichenfolgenargument, das das zu löschende Aufgabenelement darstellt. Ein Filter wird so erstellt, dass er mit dem Aufgabenelement übereinstimmt, dessen Texteigenschaft auf das Zeichenfolgenargument festgelegt ist. Sie übergeben den Filter an die DeleteOne()-Methode, die dem Element in der Auflistung entspricht, und löschen es.

Sie können die DeletedCount-Eigenschaft für das Ergebnis der DeleteOne-Methode überprüfen, um zu bestätigen, ob Dokumente gelöscht wurden. Wenn der Filter nicht mit einem zu löschenden Dokument übereinstimmen kann, ist DeletedCount Null und Sie können in diesem Fall einen Fehler zurückgeben.

Fügen Sie nun einen neuen Befehl rm wie hervorgehoben hinzu:

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)
    }
}
. . .

Wie bei allen anderen zuvor hinzugefügten Unterbefehlen verwendet der Befehl rm sein erstes Argument, um eine Aufgabe in der Datenbank abzugleichen und zu löschen.

Speichern und schließen Sie Ihre Datei.

Sie können ausstehende Aufgaben auflisten, indem Sie Ihr Programm ausführen, ohne Unterbefehle zu übergeben:

  • go run main.go
Output
1: Read a book

Wenn Sie den Unterbefehl rm für die Aufgabe „Buch lesen“ ausführen, wird er aus der Datenbank gelöscht:

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

Wenn Sie alle ausstehenden Aufgaben erneut auflisten, werden Sie feststellen, dass die Aufgabe „Buch lesen“ nicht mehr angezeigt wird, sondern stattdessen eine Aufforderung zum Hinzufügen einer neuen Aufgabe:

  • go run main.go
Output
Nothing to see here Run `add 'task'` to add a task

In diesem Schritt haben Sie eine Funktion hinzugefügt, um Aufgaben aus der Datenbank zu löschen.

Zusammenfassung

Sie haben erfolgreich ein Task-Manager-Befehlszeilenprogramm erstellt und dabei die Grundlagen der Verwendung des MongoDB Go-Treibers erlernt.

Lesen Sie unbedingt die vollständige Dokumentation zum MongoDB Go-Treiber bei GoDoc, um mehr über die Funktionen zu erfahren, die die Verwendung des Treibers bietet. Die Dokumentation, in der die Verwendung von Aggregationen oder Transaktionen beschrieben wird, könnte für Sie von besonderem Interesse sein.

Der endgültige Code für dieses Tutorial kann in diesem GitHub repo betrachtet werden.

0 Comments

Creative Commons License