jz22
jz22

Reputation: 2638

How to reuse MongoDB connection in Go

I would like to connect my server that was written in Go with a MongoDB but I'm not sure how to do it in an efficient way. A couple of examples I found implemented it like shown below.

libs/mongodb/client.go

package mongodb

import (
    "context"
    "log"
    "project/keys"

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

func GetClient() *mongo.Database {
    client, err := mongo.Connect(
        context.Background(),
        options.Client().ApplyURI(keys.GetKeys().MONGO_URI),
    )

    if err != nil {
        log.Fatal(err)
    }

    return client.Database(keys.GetKeys().MONGO_DB_NAME)
}

services/user/findOne.go

package userservices

import (
    "context"
    "log"
    "project/libs/mongodb"
    "project/models"

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

func FindOne(filter bson.M) (models.User, error) {
    var user models.User

    collection := mongodb.GetClient().Collection("users")
    result := collection.FindOne(context.TODO(), filter)

    if result.Err() != nil {
        return user, result.Err()
    }

    if err := result.Decode(&user); err != nil {
        log.Println("Failed to decode user with error:", err)
        return user, err
    }

    return user, nil
}

The GetClient function returns a database instance that is then used throughout the app. This seems to work, but I'm wondering if this really is best practice as it seems to create a new connection every time a new client is requested as shown in the second code snippet or is that assumption incorrect? I also thought about converting GetClient to a singleton, that always returns the same database instance but how would a lost connection be handled in that case? Thank you

Upvotes: 11

Views: 12121

Answers (3)

Binwin Viju
Binwin Viju

Reputation: 334

What I have done is this way:

I have a app.go file to make a mongoDB connection inside config folder that I have created

func Connection() *mongo.Client {
if err := godotenv.Load(); err != nil {
    log.Println("No .env file found")
}
uri := os.Getenv("MONGODB_URI")
if uri == "" {
    log.Fatal("You must set your 'MONGODB_URI' environmental variable. See\n\t https://www.mongodb.com/docs/drivers/go/current/usage-examples/#environment-variable")
}
// Set client options
clientOptions := options.Client().ApplyURI(uri)

// Connect to MongoDB
client, err := mongo.Connect(context.TODO(), clientOptions)

if err != nil {
    log.Fatal(err)
}

// Check the connection
err = client.Ping(context.TODO(), nil)

if err != nil {
    log.Fatal(err)
}

fmt.Println("Connected to MongoDB!")

return client
}

I have called this function in another file called employee.go which resides in model folder

var CNX = config.Connection()

func (e *Employee) CreateEmployeeDetails() *Employee {
    coll := CNX.Database("employee").Collection("detail")
    fmt.Println(coll)
    result, err := coll.InsertOne(context.TODO(), e)
    fmt.Printf("Inserted document with _id: %v\n", result.InsertedID)
    if err != nil {
        panic(err)
    }

    return e
}

Upvotes: 0

Illud
Illud

Reputation: 317

I solved it doing this

var CNX = Connection()
func Connection() *mongo.Client {
    // Set client options
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")

    // Connect to MongoDB
    client, err := mongo.Connect(context.TODO(), clientOptions)

    if err != nil {
        log.Fatal(err)
    }

    // Check the connection
    err = client.Ping(context.TODO(), nil)

    if err != nil {
    log.Fatal(err)
    }

    fmt.Println("Connected to MongoDB!")

    return client
  }

//calll connection
 func main() {
      collection := db.CNX.Database("tasks").Collection("task")
 }

output "Connected to MongoDB!"

Upvotes: 1

Prateek Bhuwania
Prateek Bhuwania

Reputation: 805

I do it this way. Do it once at the service start and then pass the MongoDatastore object around to orchestrator, service layers and repository layers. I am using the "github.com/mongodb/mongo-go-driver/mongo" driver for mongo. I think it internally monitors and recycles idle connections. Hence, we don't have to bother about broken connections as long as reference to the mongo.Client object is not lost.


const CONNECTED = "Successfully connected to database: %v"

type MongoDatastore struct {
    db      *mongo.Database
    Session *mongo.Client
    logger  *logrus.Logger
}

func NewDatastore(config config.GeneralConfig, logger *logrus.Logger) *MongoDatastore {

    var mongoDataStore *MongoDatastore
    db, session := connect(config, logger)
    if db != nil && session != nil {

        // log statements here as well

        mongoDataStore = new(MongoDatastore)
        mongoDataStore.db = db
        mongoDataStore.logger = logger
        mongoDataStore.Session = session
        return mongoDataStore
    }

    logger.Fatalf("Failed to connect to database: %v", config.DatabaseName)

    return nil
}

func connect(generalConfig config.GeneralConfig, logger *logrus.Logger) (a *mongo.Database, b *mongo.Client) {
    var connectOnce sync.Once
    var db *mongo.Database
    var session *mongo.Client
    connectOnce.Do(func() {
        db, session = connectToMongo(generalConfig, logger)
    })

    return db, session
}

func connectToMongo(generalConfig config.GeneralConfig, logger *logrus.Logger) (a *mongo.Database, b *mongo.Client) {

    var err error
    session, err := mongo.NewClient(generalConfig.DatabaseHost)
    if err != nil {
        logger.Fatal(err)
    }
    session.Connect(context.TODO())
    if err != nil {
        logger.Fatal(err)
    }

    var DB = session.Database(generalConfig.DatabaseName)
    logger.Info(CONNECTED, generalConfig.DatabaseName)

    return DB, session
}

You may now create your repository as below:-

type TestRepository interface{
    Find(ctx context.Context, filters interface{}) []Document, error
}

type testRepository struct {
    store      *datastore.MongoDatastore
}

func (r *testRepository) Find(ctx context.Context , filters interface{}) []Document, error{
    cur, err := r.store.GetCollection("some_collection_name").Find(ctx, filters)
    if err != nil {
        return nil, err
    }
    defer cur.Close(ctx)
    var result = make([]models.Document, 0)
    for cur.Next(ctx) {
        var currDoc models.Document
        err := cur.Decode(&currDoc)
        if err != nil {
            //log here
            continue
        }
        result = append(result, currDoc)
    }
    return result, err
}

Upvotes: 19

Related Questions