Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add wrapper function to support MongoDB transactions #12

Merged
merged 6 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,6 @@ linters:
- nilnil # checks that there is no simultaneous return of nil error and an invalid value
- noctx # finds sending http request without context.Context
- nolintlint # reports ill-formed or insufficient nolint directives
- nonamedreturns # reports all named returns
- nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL
- predeclared # finds code that shadows one of Go's predeclared identifiers
- promlinter # checks Prometheus metrics naming via promlint
Expand All @@ -269,8 +268,9 @@ linters:
#- decorder # checks declaration order and count of types, constants, variables and functions
#- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega
#- goheader # checks is file header matches to pattern
# - godox # detects FIXME, TODO and other comment keywords
#- godox # detects FIXME, TODO and other comment keywords
#- ireturn # accept interfaces, return concrete types
#- nonamedreturns # reports all named returns
#- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated
#- tagalign # checks that struct tags are well aligned
#- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope
Expand Down
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Easily manage **meta fields** in models without cluttering Go structs.
- Supports **union types**, expanding data capabilities.
- Implement strict field requirements with struct tags for **data integrity**.
- Built-in support for **multi-tenant** systems.
- Wrapper around the **official** Mongo Go Driver.

## Requirements
Expand All @@ -42,8 +43,8 @@ For existing database connection,
import "github.com/Lyearn/mgod"

func init() {
// dbConn is the database connection obtained using Go Mongo Driver's Connect method.
mgod.SetDefaultConnection(dbConn)
// client is the MongoDB client obtained using Go Mongo Driver's Connect method.
mgod.SetDefaultClient(client)
}
```

Expand All @@ -59,10 +60,9 @@ import (
func init() {
// `cfg` is optional. Can rely on default configurations by providing `nil` value in argument.
cfg := &mgod.ConnectionConfig{Timeout: 5 * time.Second}
dbName := "mgod-test"
opts := options.Client().ApplyURI("mongodb://root:mgod123@localhost:27017")

err := mgod.ConfigureDefaultConnection(cfg, dbName, opts)
err := mgod.ConfigureDefaultClient(cfg, opts)
}
```

Expand All @@ -84,12 +84,11 @@ import (
)

model := User{}
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
Timestamps: true,
}
dbName := "mgoddb"
collection := "users"

userModel, _ := mgod.NewEntityMongoModel(model, schemaOpts)
opts := mgod.NewEntityMongoModelOptions(dbName, collection, nil)
userModel, _ := mgod.NewEntityMongoModel(model, *opts)
```

Use the entity ODM to perform CRUD operations with ease.
Expand All @@ -106,14 +105,12 @@ user, _ := userModel.InsertOne(context.TODO(), userDoc)
```

**Output:**
```json
```js
{
"_id": ObjectId("65697705d4cbed00e8aba717"),
"name": "Gopher",
"emailId": "gopher@mgod.com",
"joinedOn": ISODate("2023-12-01T11:32:19.290Z"),
"createdAt": ISODate("2023-12-01T11:32:19.290Z"),
"updatedAt": ISODate("2023-12-01T11:32:19.290Z"),
"__v": 0
}
```
Expand Down Expand Up @@ -143,9 +140,9 @@ Inspired by the easy interface of MongoDB handling using [Mongoose](https://gith

## Future Scope
The current version of mgod is a stable release. However, there are plans to add a lot more features like -
- [ ] Enable functionality to opt out of the default conversion of date fields to ISOString format.
- [x] Implement a setup step for storing a default Mongo connection, eliminating the need to pass it during EntityMongoModel creation.
- [ ] Provide support for transactions following the integration of default Mongo connection logic.
- [x] Provide support for transactions following the integration of default Mongo connection logic.
- [ ] Enable functionality to opt out of the default conversion of date fields to ISOString format.
- [ ] Develop easy to use wrapper functions around MongoDB Aggregation operation.
- [ ] Introduce automatic MongoDB collection selection based on Go struct names as a default behavior.
- [ ] Add test cases to improve code coverage.
Expand Down
20 changes: 9 additions & 11 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ import (
"go.mongodb.org/mongo-driver/mongo/options"
)

var dbConn *mongo.Database
var mClient *mongo.Client
var defaultTimeout = 10 * time.Second

// ConnectionConfig is the configuration options available for a MongoDB connection.
type ConnectionConfig struct {
// Timeout is the timeout for various operations performed on the MongoDB server like Connect, Ping, Session etc.
// Timeout is the timeout for various operations performed on the MongoDB server like Connect, Ping etc.
Timeout time.Duration
}

// SetDefaultConnection sets the default connection to be used by the package.
func SetDefaultConnection(conn *mongo.Database) {
dbConn = conn
// SetDefaultClient sets the default MongoDB client to be used by the package.
func SetDefaultClient(client *mongo.Client, dbName string) {
harsh-2711 marked this conversation as resolved.
Show resolved Hide resolved
mClient = client
}

// ConfigureDefaultConnection opens a new connection using the provided config options and sets it as a default connection to be used by the package.
func ConfigureDefaultConnection(cfg *ConnectionConfig, dbName string, opts ...*options.ClientOptions) error {
// ConfigureDefaultClient opens a new connection using the provided config options and sets the default MongoDB client to be used by the package.
func ConfigureDefaultClient(cfg *ConnectionConfig, opts ...*options.ClientOptions) (err error) {
if cfg == nil {
cfg = defaultConnectionConfig()
}

client, err := newClient(cfg, opts...)
mClient, err = newClient(cfg, opts...)
if err != nil {
return err
}
Expand All @@ -37,13 +37,11 @@ func ConfigureDefaultConnection(cfg *ConnectionConfig, dbName string, opts ...*o
defer cancel()

// Ping the MongoDB server to check if connection is established.
err = client.Ping(ctx, nil)
err = mClient.Ping(ctx, nil)
if err != nil {
return err
}

dbConn = client.Database(dbName)

return nil
}

Expand Down
21 changes: 21 additions & 0 deletions connection_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package mgod

import "go.mongodb.org/mongo-driver/mongo"

// dbConnCache is a cache of MongoDB database connections.
var dbConnCache map[string]*mongo.Database

func init() {
dbConnCache = make(map[string]*mongo.Database)
}

// getDBConn returns a MongoDB database connection from the cache.
// If the connection is not present in the cache, it creates a new connection and adds it to the cache (Write-through policy).
func getDBConn(dbName string) *mongo.Database {
harsh-2711 marked this conversation as resolved.
Show resolved Hide resolved
// Initialize the cache entry if it is not present.
if dbConnCache[dbName] == nil {
dbConnCache[dbName] = mClient.Database(dbName)
}

return dbConnCache[dbName]
}
4 changes: 3 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ This directory contains user-facing documentation. For those who wish to underst
* [Meta fields](./meta_fields.md)

### Advanced Guide
* [Unions](./union_types.md)
* [Multi Tenancy](./multi_tenancy.md)
* [Unions](./union_types.md)
* [Transactions](./transactions.md)
14 changes: 8 additions & 6 deletions docs/basic_usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ For existing database connection,
import "github.com/Lyearn/mgod"

func init() {
// dbConn is the database connection obtained using Go Mongo Driver's Connect method.
mgod.SetDefaultConnection(dbConn)
// client is the MongoDB client obtained using Go Mongo Driver's Connect method.
mgod.SetDefaultClient(client, dbName)
}
```

Expand All @@ -26,10 +26,9 @@ import (
func init() {
// `cfg` is optional. Can rely on default configurations by providing `nil` value in argument.
cfg := &mgod.ConnectionConfig{Timeout: 5 * time.Second}
dbName := "mgod-test"
opts := options.Client().ApplyURI("mongodb://root:mgod123@localhost:27017")

err := mgod.ConfigureDefaultConnection(cfg, dbName, opts)
err := mgod.ConfigureDefaultClient(cfg, opts)
}
```

Expand Down Expand Up @@ -57,12 +56,15 @@ import (
)

model := User{}
dbName := "mgoddb"
collection := "users"

schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
Timestamps: true,
}

userModel, _ := mgod.NewEntityMongoModel(model, schemaOpts)
opts := mgod.NewEntityMongoModelOptions(dbName, collection, &schemaOpts)
userModel, _ := mgod.NewEntityMongoModel(model, *opts)
```

Use the entity ODM to perform CRUD operations with ease.
Expand Down
7 changes: 3 additions & 4 deletions docs/meta_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ It is the meta field that stores the timestamp of the document creation. This fi

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
Timestamps: true,
}

userDoc := User{
Name: "Gopher",
EmailID: "gopher@mgod.com",
}

user, _ := userModel.InsertOne(context.TODO(), userDoc)
```

Expand All @@ -59,7 +59,6 @@ It is the meta field that stores the timestamp of the document updation. This fi

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
Timestamps: true,
}

Expand Down Expand Up @@ -98,14 +97,14 @@ It is the field that stores the version of the document. This field is automatic

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
VersionKey: true
}

userDoc := User{
Name: "Gopher",
EmailID: "gopher@mgod.com",
}

user, _ := userModel.InsertOne(context.TODO(), userDoc)
```

Expand All @@ -124,14 +123,14 @@ If `VersionKey` is set to `false`.

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
VersionKey: false
}

userDoc := User{
Name: "Gopher",
EmailID: "gopher@mgod.com",
}

user, _ := userModel.InsertOne(context.TODO(), userDoc)
```

Expand Down
44 changes: 44 additions & 0 deletions docs/multi_tenancy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Multi Tenancy
---

`mgod` comes with the built-in support for multi-tenancy, enabling the use of a single Go struct with multiple databases. This feature allows creation of multiple `EntityMongoModel` of the same Go struct to be attached to different databases while using the same underlying MongoDB client connection.

## Usage

Create separate `EntityMongoModel` for different tenants using same Go struct and corresponding databases.

```go
type User struct {
Name string
EmailID string `bson:"emailId"`
Amount float32
}
collection := "users"

tenant1DB := "tenant1"
tenant2DB := "tenant2"

tenant1Model, _ := mgod.NewEntityMongoModelOptions(tenant1DB, collection, nil)
tenant2Model, _ := mgod.NewEntityMongoModelOptions(tenant2DB, collection, nil)
```

These models can now be used simultaneously inside the same service logic as well as in a transaction operation.

```go
amount := 10000

tenant1Model.UpdateMany(context.TODO(), bson.M{"name": "Gopher Tenant 1"}, bson.M{"$inc": {"amount": -amount}})
tenant2Model.UpdateMany(context.TODO(), bson.M{"name": "Gopher Tenant 2"}, bson.M{"$inc": {"amount": amount}})
```

:::note
The `EntityMongoModel` is always bound to the specified database at the time of its declaration and, as such, cannot be used to perform operations across multiple databases.
:::

```go
amount := 10000
harsh-2711 marked this conversation as resolved.
Show resolved Hide resolved

result, _ := tenant1Model.FindOne(context.TODO(), bson.M{"name": "Gopher Tenant 2"})
// result will be <nil> value in this case
```
19 changes: 0 additions & 19 deletions docs/schema_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,6 @@ Schema Options is Mongo Schema level options (which modifies actual MongoDB doc)

`mgod` supports the following schema options -

## Collection

- Accepts Type: `string`
- Is Optional: `No`

It is the name of the mongo collection in which the entity is stored. For example, `users` collection of MongoDB for `User` model in Golang.

### Usage

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users", // MongoDB collection name
}
```

## Timestamps

- Accepts Type: `bool`
Expand All @@ -33,7 +18,6 @@ It is used to track `createdAt` and `updatedAt` meta fields for the entity. See

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
Timestamps: true,
}
```
Expand All @@ -50,7 +34,6 @@ This reports whether to add a version key (`__v`) for the entity. See [Meta Fiel

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "users",
VersionKey: true,
}
```
Expand All @@ -67,7 +50,6 @@ It defines whether the entity is a union type. See [Union Types](union_types.md)

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "resources",
IsUnionType: true,
}
```
Expand All @@ -90,7 +72,6 @@ It is the key used to identify the underlying type in case of a union type entit

```go
schemaOpts := schemaopt.SchemaOptions{
Collection: "resources",
IsUnionType: true,
DiscriminatorKey: "type",
}
Expand Down
Loading