-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add wrapper function to support MongoDB transactions (#12)
* feat: add wrapper function to support MongoDB transactions * doc: mark transactions feature as done in future scope section * fix: support multi tenancy * doc: fix mgod usage example in README * doc: multi tenancy advanced guide * fix: add locking in connection cache
- Loading branch information
1 parent
d05bfe7
commit 539402a
Showing
16 changed files
with
528 additions
and
217 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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) | ||
} | ||
``` | ||
|
||
|
@@ -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) | ||
} | ||
``` | ||
|
||
|
@@ -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. | ||
|
@@ -106,14 +105,12 @@ user, _ := userModel.InsertOne(context.TODO(), userDoc) | |
``` | ||
|
||
**Output:** | ||
```json | ||
```js | ||
{ | ||
"_id": ObjectId("65697705d4cbed00e8aba717"), | ||
"name": "Gopher", | ||
"emailId": "[email protected]", | ||
"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 | ||
} | ||
``` | ||
|
@@ -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. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package mgod | ||
|
||
import ( | ||
"sync" | ||
|
||
"go.mongodb.org/mongo-driver/mongo" | ||
) | ||
|
||
// dbConnCache is a cache of MongoDB database connections. | ||
var dbConnCache *connectionCache | ||
|
||
func init() { | ||
dbConnCache = newConnectionCache() | ||
} | ||
|
||
// connectionCache is a thread safe construct to cache MongoDB database connections. | ||
type connectionCache struct { | ||
cache map[string]*mongo.Database | ||
mux sync.RWMutex | ||
} | ||
|
||
func newConnectionCache() *connectionCache { | ||
return &connectionCache{ | ||
cache: map[string]*mongo.Database{}, | ||
} | ||
} | ||
|
||
func (c *connectionCache) Get(dbName string) *mongo.Database { | ||
c.mux.RLock() | ||
defer c.mux.RUnlock() | ||
|
||
return c.cache[dbName] | ||
} | ||
|
||
func (c *connectionCache) Set(dbName string, db *mongo.Database) { | ||
c.mux.Lock() | ||
defer c.mux.Unlock() | ||
|
||
c.cache[dbName] = db | ||
} | ||
|
||
// 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 { | ||
dbConn := dbConnCache.Get(dbName) | ||
|
||
// Initialize the cache entry if it is not present. | ||
if dbConn == nil { | ||
dbConn = mClient.Database(dbName) | ||
dbConnCache.Set(dbName, dbConn) | ||
} | ||
|
||
return dbConn | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: "[email protected]", | ||
} | ||
|
||
user, _ := userModel.InsertOne(context.TODO(), userDoc) | ||
``` | ||
|
||
|
@@ -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, | ||
} | ||
|
||
|
@@ -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: "[email protected]", | ||
} | ||
|
||
user, _ := userModel.InsertOne(context.TODO(), userDoc) | ||
``` | ||
|
||
|
@@ -124,14 +123,14 @@ If `VersionKey` is set to `false`. | |
|
||
```go | ||
schemaOpts := schemaopt.SchemaOptions{ | ||
Collection: "users", | ||
VersionKey: false | ||
} | ||
|
||
userDoc := User{ | ||
Name: "Gopher", | ||
EmailID: "[email protected]", | ||
} | ||
|
||
user, _ := userModel.InsertOne(context.TODO(), userDoc) | ||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
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 | ||
result, _ := tenant1Model.FindOne(context.TODO(), bson.M{"name": "Gopher Tenant 2"}) | ||
// result will be <nil> value in this case | ||
``` |
Oops, something went wrong.