mongo

package
v1.30.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 8, 2024 License: MIT Imports: 17 Imported by: 0

README

MongoDB Client Library

MongoDB Go Version GoDoc

A production-ready MongoDB client wrapper for Go applications with built-in support for connection pooling, transactions, telemetry, and testing.

Table of Contents

Features

Core Features
  • 🔄 Automatic connection management and pooling
  • 📊 Comprehensive transaction support
  • 📈 Built-in telemetry and monitoring
  • 🔁 Configurable retry mechanisms
  • ⏱️ Query timeout handling
  • 🧪 In-memory testing capabilities
  • 🔍 Detailed logging and debugging
  • 🛡️ Connection security options
Performance Features
  • Connection pooling optimization
  • Automatic retry with exponential backoff
  • Query timeout management
  • Efficient resource cleanup

Installation

go get -u github.com/SolomonAIEngineering/backend-core-library/database/mongo

Quick Start

package main

import (
    "context"
    "time"
    
    "github.com/SolomonAIEngineering/backend-core-library/database/mongo"
    "github.com/SolomonAIEngineering/backend-core-library/instrumentation"
    "go.uber.org/zap"
    "go.mongodb.org/mongo-driver/mongo/options"
)

func main() {
    // Initialize client
    client, err := mongo.New(
        mongo.WithDatabaseName("myapp"),
        mongo.WithConnectionURI("mongodb://localhost:27017"),
        mongo.WithQueryTimeout(30 * time.Second),
        mongo.WithLogger(zap.L()),
    )
    if err != nil {
        panic(err)
    }
    defer client.Close()

    // Basic CRUD operations
    collection, err := client.GetCollection("users")
    if err != nil {
        panic(err)
    }

    // Insert a document
    ctx := context.Background()
    user := map[string]interface{}{
        "name": "John Doe",
        "email": "john@example.com",
        "created_at": time.Now(),
    }
    
    result, err := collection.InsertOne(ctx, user)
    if err != nil {
        panic(err)
    }
}

Configuration

Available Options
type Config struct {
    // Client configuration
    opts := []mongo.Option{
        // Database Configuration
        mongo.WithDatabaseName("mydb"),
        mongo.WithConnectionURI("mongodb://localhost:27017"),
        
        // Timeout Settings
        mongo.WithQueryTimeout(60 * time.Second),
        mongo.WithRetryTimeOut(30 * time.Second),
        
        // Retry Configuration
        mongo.WithMaxConnectionAttempts(3),
        mongo.WithMaxRetriesPerOperation(3),
        mongo.WithOperationSleepInterval(5 * time.Second),
        
        // Monitoring & Logging
        mongo.WithTelemetry(&instrumentation.Client{}),
        mongo.WithLogger(zap.L()),
        
        // Collection Management
        mongo.WithCollectionNames([]string{"users", "products", "orders"}),
        
        // Advanced Options
        mongo.WithClientOptions(options.Client().
            ApplyURI("mongodb://localhost:27017").
            SetMaxPoolSize(100).
            SetMinPoolSize(10)),
    }
}
Security Configuration
// TLS Configuration
tlsConfig := &tls.Config{
    // ... your TLS configuration
}

opts := []mongo.Option{
    mongo.WithClientOptions(options.Client().
        ApplyURI("mongodb://localhost:27017").
        SetTLSConfig(tlsConfig)),
    mongo.WithCredentials(options.Credential{
        Username: "user",
        Password: "pass",
        AuthSource: "admin",
    }),
}

Working with Collections

Basic CRUD Operations
// Get collection
collection, err := client.GetCollection("users")
if err != nil {
    return err
}

// Insert
doc := map[string]interface{}{
    "name": "Alice",
    "age":  30,
}
result, err := collection.InsertOne(ctx, doc)

// Find
var user map[string]interface{}
err = collection.FindOne(ctx, bson.M{"name": "Alice"}).Decode(&user)

// Update
update := bson.M{"$set": bson.M{"age": 31}}
_, err = collection.UpdateOne(
    ctx,
    bson.M{"name": "Alice"},
    update,
)

// Delete
_, err = collection.DeleteOne(ctx, bson.M{"name": "Alice"})
Bulk Operations
// Bulk Insert
documents := []interface{}{
    bson.D{{"name", "User1"}, {"age", 25}},
    bson.D{{"name", "User2"}, {"age", 30}},
}
result, err := collection.InsertMany(ctx, documents)

// Bulk Update
updates := []mongo.WriteModel{
    mongo.NewUpdateOneModel().
        SetFilter(bson.M{"name": "User1"}).
        SetUpdate(bson.M{"$set": bson.M{"age": 26}}),
    mongo.NewUpdateOneModel().
        SetFilter(bson.M{"name": "User2"}).
        SetUpdate(bson.M{"$set": bson.M{"age": 31}}),
}
_, err = collection.BulkWrite(ctx, updates)

Transactions

Standard Transaction Example
err := client.StandardTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
    // Get collections
    users, err := client.GetCollection("users")
    if err != nil {
        return nil, err
    }
    orders, err := client.GetCollection("orders")
    if err != nil {
        return nil, err
    }

    // Perform operations
    _, err = users.InsertOne(sessCtx, userDoc)
    if err != nil {
        return nil, err
    }
    
    _, err = orders.InsertOne(sessCtx, orderDoc)
    if err != nil {
        return nil, err
    }

    return nil, nil
})
Complex Transaction with Return Value
type TransactionResult struct {
    UserID  string
    OrderID string
}

result, err := client.ComplexTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
    // Your transaction logic here
    userResult, err := users.InsertOne(sessCtx, userDoc)
    if err != nil {
        return nil, err
    }
    
    orderResult, err := orders.InsertOne(sessCtx, orderDoc)
    if err != nil {
        return nil, err
    }

    return &TransactionResult{
        UserID:  userResult.InsertedID.(string),
        OrderID: orderResult.InsertedID.(string),
    }, nil
})

Error Handling

// Custom error types
var (
    ErrConnectionFailed = errors.New("failed to connect to mongodb")
    ErrQueryTimeout     = errors.New("query timeout")
)

// Error handling example
collection, err := client.GetCollection("users")
if err != nil {
    switch {
    case errors.Is(err, mongo.ErrCollectionNotFound):
        // Handle collection not found
    case errors.Is(err, mongo.ErrDatabaseNotFound):
        // Handle database not found
    default:
        // Handle other errors
    }
    return err
}

Telemetry & Monitoring

// Initialize with telemetry
telemetryClient := &instrumentation.Client{
    // Configure your telemetry client
}

client, err := mongo.New(
    mongo.WithTelemetry(telemetryClient),
    mongo.WithMetricsEnabled(true),
)

// Access metrics
metrics := client.GetMetrics()
fmt.Printf("Total Queries: %d\n", metrics.TotalQueries)
fmt.Printf("Failed Queries: %d\n", metrics.FailedQueries)
fmt.Printf("Average Query Time: %v\n", metrics.AverageQueryTime)

Testing

In-Memory Testing
func TestUserRepository(t *testing.T) {
    // Create test client
    testClient, err := mongo.NewInMemoryTestDbClient(
        []string{"users", "orders"},
    )
    if err != nil {
        t.Fatal(err)
    }
    defer testClient.Teardown()

    // Run tests
    t.Run("InsertUser", func(t *testing.T) {
        collection, err := testClient.GetCollection("users")
        if err != nil {
            t.Fatal(err)
        }

        user := bson.M{"name": "Test User"}
        _, err = collection.InsertOne(context.Background(), user)
        assert.NoError(t, err)
    })
}
Mocking
type MockMongoClient struct {
    mongo.Client
    MockInsertOne func(context.Context, interface{}) (*mongo.InsertOneResult, error)
}

func (m *MockMongoClient) InsertOne(ctx context.Context, document interface{}) (*mongo.InsertOneResult, error) {
    return m.MockInsertOne(ctx, document)
}

Best Practices

Connection Management
// Initialize once at application startup
client, err := mongo.New(opts...)
if err != nil {
    log.Fatal(err)
}
defer client.Close()

// Reuse the client throughout your application
Query Optimization
// Use appropriate indexes
collection.CreateIndex(ctx, mongo.IndexModel{
    Keys: bson.D{{"field", 1}},
    Options: options.Index().SetUnique(true),
})

// Use projection to limit returned fields
collection.FindOne(ctx, filter, options.FindOne().SetProjection(bson.M{
    "needed_field": 1,
    "_id": 0,
}))
Resource Management
// Use context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Use cursor properly
cursor, err := collection.Find(ctx, filter)
if err != nil {
    return err
}
defer cursor.Close(ctx)

Migration Guide

Upgrading from v1.x to v2.x
// Old v1.x configuration
oldClient := mongo.NewClient(
    "mongodb://localhost:27017",
    "mydb",
)

// New v2.x configuration
newClient, err := mongo.New(
    mongo.WithConnectionURI("mongodb://localhost:27017"),
    mongo.WithDatabaseName("mydb"),
)

Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup
# Clone the repository
git clone https://github.com/SolomonAIEngineering/backend-core-library.git

# Install dependencies
go mod download

# Run tests
go test -v ./...

# Run linting
golangci-lint run

License

This MongoDB client is released under the MIT License. See LICENSE for details.


Support

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ObjectIDFromHexIDString

func ObjectIDFromHexIDString(str *string) (primitive.ObjectID, error)

ObjectIDFromHexIDString takes a pointer to a string as input and returns a primitive.ObjectID and an error. It converts the input string, which is expected to be a hexadecimal representation of an ObjectID, to a primitive.ObjectID. The function is a method of the MongoDatabase struct and can be used to convert a string representation of an ObjectID to a primitive.ObjectID that can be used in MongoDB queries.

Types

type Client

type Client struct {
	// Conn serves as the database co
	Conn *mongo.Client
	// Logger is the logging utility used by this object
	Logger *zap.Logger
	// MaxConnectionAttempts outlines the maximum connection attempts
	// to initiate against the database
	MaxConnectionAttempts int
	// MaxRetriesPerOperation defines the maximum retries to attempt per failed database
	// connection attempt
	MaxRetriesPerOperation int
	// RetryTimeOut defines the maximum time until a retry operation is observed as a
	// timed out operation
	RetryTimeOut time.Duration
	// OperationSleepInterval defines the amount of time between retry operations
	// that the system sleeps
	OperationSleepInterval time.Duration
	// QueryTimeout defines the maximal amount of time a query can execute before being cancelled
	QueryTimeout time.Duration
	// Telemetry defines the object by which we will emit metrics, trace requests, and database operations
	Telemetry *instrumentation.Client
	// DatabaseName database name
	DatabaseName *string

	// CollectionNames is a list of collection names
	CollectionNames []string
	// ClientOptions defines the options to use when connecting to the database
	ClientOptions *options.ClientOptions
	// ConnectionURI defines the connection string to use when connecting to the database
	ConnectionURI *string
	// contains filtered or unexported fields
}

func New

func New(options ...Option) (*Client, error)

New creates a new instance of the mongo client

func (*Client) Close

func (c *Client) Close() error

Close closes the database connection

func (*Client) ComplexTransaction

func (c *Client) ComplexTransaction(ctx context.Context, callback MongoTx) (any, error)

ComplexTransaction is a wrapper around the `WithTransaction` method of the `mongo.Session` object.

func (*Client) GetCollection

func (c *Client) GetCollection(name string) (*mongo.Collection, error)

GetCollection returns a collection object by name

func (*Client) GetConnection

func (c *Client) GetConnection() *mongo.Client

GetConnection returns the database connection

func (*Client) StandardTransaction

func (c *Client) StandardTransaction(ctx context.Context, callback MongoTx) error

StandardTransaction is a wrapper around the `WithTransaction` method of the `mongo.Session` object.

func (*Client) StartDbSegment

func (c *Client) StartDbSegment(ctx context.Context, name, collectionName string) *newrelic.DatastoreSegment

StartDbSegment starts a new `newrelic.DatastoreSegment` for database operations. It takes in a context, a name for the operation, and a collection name as parameters. If telemetry is enabled, it retrieves the transaction from the context and starts a new datastore segment for the operation using the `StartNosqlDatastoreSegment` method from the `Telemetry` object. It returns the newly created segment. If telemetry is not enabled, it returns `nil`.

func (*Client) Validate

func (c *Client) Validate() error

Validate validates the client

type IClient

type IClient interface{}

type InMemoryTestDbClient

type InMemoryTestDbClient struct {
	DatabaseName string
	Client       *Client
	Server       *mim.Server
}

func NewInMemoryTestDbClient

func NewInMemoryTestDbClient(collectionNames []string) (*InMemoryTestDbClient, error)

NewInMemoryTestDbClient creates a new in-memory MongoDB database and returns a client to it.

func (*InMemoryTestDbClient) Teardown

func (c *InMemoryTestDbClient) Teardown() error

Teardown cleans up resources initialized by Setup. This function must be called once after all tests have finished running.

type MongoTx

type MongoTx func(sessCtx mongo.SessionContext) (any, error)

MongoTx is a type alias for the `WithTransaction` method of the `mongo.Session` object.

type Option

type Option func(*Client)

func WithClientOptions

func WithClientOptions(clientOptions *options.ClientOptions) Option

WithClientOptions sets the client options

func WithCollectionNames

func WithCollectionNames(collectionNames []string) Option

WithCollectionNames sets the collection names

func WithConnectionURI

func WithConnectionURI(connectionURI string) Option

WithConnectionURI sets the connection URI

func WithDatabaseName

func WithDatabaseName(databaseName string) Option

WithDatabaseName sets the database name

func WithLogger

func WithLogger(logger *zap.Logger) Option

WithLogger sets the logging utility used by this object

func WithMaxConnectionAttempts

func WithMaxConnectionAttempts(maxConnectionAttempts int) Option

WithMaxConnectionAttempts sets the maximum connection attempts to initiate against the database

func WithMaxRetriesPerOperation

func WithMaxRetriesPerOperation(maxRetriesPerOperation int) Option

WithMaxRetriesPerOperation sets the maximum retries to attempt per failed database connection attempt

func WithOperationSleepInterval

func WithOperationSleepInterval(operationSleepInterval time.Duration) Option

WithOperationSleepInterval sets the amount of time between retry operations that the system sleeps

func WithQueryTimeout

func WithQueryTimeout(queryTimeout time.Duration) Option

WithQueryTimeout sets the maximal amount of time a query can execute before being cancelled

func WithRetryTimeOut

func WithRetryTimeOut(retryTimeOut time.Duration) Option

WithRetryTimeOut sets the maximum time until a retry operation is observed as a timed out operation

func WithTelemetry

func WithTelemetry(telemetry *instrumentation.Client) Option

WithTelemetry sets the object by which we will emit metrics, trace requests, and database operations

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL