gomongo

module
v0.0.0-...-6524a5b Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2022 License: MIT

README

gomongo

A handy wrapper for working with the standard mongo-go-driver. This package lets you avoid a lot of the bson.Ms and the bson.Ds and all the other fun things about working with a language that doesn't deal with JSON natively.

Install

$ go get github.com/clarkmcc/gomongo

Query Builder

This library was originally introduced with condition builders that allowed callers to more easily build queries and pipelines using functions rather than figuring out how to use the bson types. This however was not an appreciable improvement over the user experience of the bson package. I've now added a Go template based query builder that allows you to build queries in JSON form and then insert Go template variables dynamically.

Example

JSON

{
	"$match": {
		"_id": ObjectId("000000000000000000000000")
	}
}

BSON

key, err := primitive.ObjectIdFromHex("000000000000000000000000")
if err != nil {
	panic(err)
}
q := bson.M{"$match": bson.M{
	"_id": key
}}

Query Builder

q, err := qb.Build[bson.M](`{"$match": {"_id": {{ oid .id }}}}`, map[string]any{
	"id": "000000000000000000000000",
})
Regular Expressions

You can also use the query builder to write queries using regular expressions.

q, err := qb.Build[bson.M](`{"$match": {"name": {{ regex .name }}}}`, map[string]any{
    "name": "/john|jane/i",
})
Custom Template Functions

The template function map is exposed so that the behavior of existing functions like oid and regex can be overridden or new functions created

qb.Builtins["foobar"] = func(value reflect.Value) (any, error) {
    // Do something...
    return nil, nil
}

Repositories

A repository represents a collection of methods that are called and executed on a specific collection, in other words you'll always have one repository per collection. The BaseRepository has a bunch of standard helper methods and is used by embedding it into our own custom repositories.

For example, lets say we want to create a repository that interacts with the user collection. We'd start by defining a UserRepository like this:

type UserRepository struct {
    CollectionName string
    *database.BaseRepository
}

We'll then make a method to connect our UserRepository to our MongoDB client:

func NewUserRepository(client *mongo.Client) *UserRepository {
	r, err := database.NewBaseRepository(client, <DATABASE_NAME>, "user")
	if err != nil {
		log.Fatalf("initializing base repository: %v", err)
	}
	return &UserRepository{
		BaseRepository: r,
	}
}

All of the methods in the BaseRepository are available to any custom repository, some of these methods are:

  • Find - Takes a query and an interface and decodes the response into the interface
  • FindCursor - Takes a query and returns the results in the form of a *mongo.Cursor
  • FindOne - Takes a query and an interface and decodes the first matching response into the interface
  • FindById - Takes a string _id and decodes the first matching response into the interface
  • FindByIdList - Takes a list of string _id and an interface and decodes the response into the interface
  • FindDistinct - Takes a fieldName, a query and an interface and decodes the distinct values for the provided fieldName into the interface
  • Aggregate - Takes a pipeline and an allowDiskUse bool and returns a *mongo.Cursor
  • Create - Takes a document of type Document (interface) and inserts it into the repositories collection with an autogenerated _id, createdDate and modifiedDate
  • CreateMany - Does the same as above but accepts a slice of type Document
  • Update - Take a filter (query) and an update document (see https://docs.mongodb.com/manual/reference/operator/update/#id1) and a multi bool. If multi is true then it will update all documents matching the filter, otherwise it will update the first document to match the filter
  • UpdateById - Takes a string _id, and update document, an autoSet bool and a []DocStatus. If you're using the operators defined here (see https://docs.mongodb.com/manual/reference/operator/update/#id1), then you can set autoSet to false, otherwise it will automatically take your update document and wrap it inside a MongoDB $set operator (NOTE: when doing any update, make sure to update the modifiedDate yourself). []DocStatus Has two options, Active = 1 or Inactive = 0 that you can use to filter (filters on the document status field). Document statuses provide an alternative method for 'deleting' documents.
  • UpdateByIdList - Does the same as above but accepts a list of string _id to perform the update operation on
  • DeleteById - Takes a string _id and deletes the document. This is discouraged, instead use the UpdateById method and set the status to 0.
Extending the BaseRepository Functionality

The default functionality could be extended and simplified using our previous example of a UserRepository. FindById decodes the response into a cursor but it can be extended like this in the UserRepository:

func (r *UserRepository) FindUserById(ctx context.Context, id string) (*User, error) {
	user := User{}
	err := r.FindById(ctx, id, &user)
	if err != nil {
		return nil, fmt.Errorf("running FindUserById: %v", err)

	}
	return &user, nil
}

In this case the decoding into a struct is handled in a repository and calling this method returns the user that was found:

user, err := FindUserById(ctx, "5c7836b73a8de34c78fec399")

Condition Pipe

The condition pipe allows you to chain query operators together to create a MongoDB query without having to deal with the bson library directly. Here's an example, refer to the source code (documented) for additional operators:

condition := Pipe(
    DateLessThanOrEqualTo(Condition{
        Key:   "endDate",
        Value: "2006-01-02T15:04:05.000Z",
    }),
    DateGreaterThanOrEqualTo(Condition{
        Key:   "startDate",
        Value: "2006-01-02T15:04:05.000Z",
    }),
)

This outputs the following:

{
    "endDate": {
        "$lte": "2006-01-02T15:04:05Z"
    },
    "startDate": {
        "$gte": "2006-01-02T15:04:05Z"
    }
}

Note that condition pipes can be put inside aggregate operators such as the Match operator (see below).

Aggregate Pipe

The aggregate pipe allows you to chain operators together in order to create a MongoDB pipeline without having to deal with the bson library directly. Here's an example, refer to the source code (documented) for additional operators:

pipeline := Pipe(
    Match(
        Operation(
            condition.Pipe(
                condition.ObjectIdMatch(condition.Condition{
                    Key:   "_id",
                    Value: "5c7836b73a8de34c78fec399"}),
                condition.EqualTo(condition.Condition{
                    Key:   "status",
                    Value: 1,
                }),
                condition.StringStartsWith(condition.Condition{
                    Key:   "model",
                    Value: "T654",
                }),
            ),
        ),
    ),
    Project(
        Operation{
            "name":  1,
            "make":  1,
            "model": 1,
        },
    ),
)

This outputs the following:

[
    {
        "$match": {
            "_id": {
                "$eq": "5c7836b73a8de34c78fec399"
            },
            "model": {
                "$regex": {
                    "Pattern": "^T654",
                    "Options": "i"
                }
            },
            "status": {
                "$eq": 1
            }
        }
    },
    {
        "$project": {
            "make": 1,
            "model": 1,
            "name": 1
        }
    }
]

The following operators are currently supported with more in development:

  • Match
  • Unwind
  • Project
  • Lookup
  • Sort
  • Limit
  • Skip

Directories

Path Synopsis
The util package provides handy tools that are used by the gomongo package as well as by the implementation of the gomongo package.
The util package provides handy tools that are used by the gomongo package as well as by the implementation of the gomongo package.

Jump to

Keyboard shortcuts

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