Reputation: 95
There are many implementations of data loaders available for sql language in go. But how can i implement dataloader functionality in golang using mongodb. I am using gqlgen library for my graphql code.
I want to fetch all the products with their category. I want to cashe the information of category.
My graphql query looks like :
query example {
products: GetProducts{
Id,
Name,
Description,
Price,
Quantity,
Category {
Id,
Name
}
},
}
For above query if there are 1000 products and 10 categories then i will query 1000 times for only 10 categories. Hence I want to cashe it.
My Mongodb Products Schema :
{
"_id": {
"$oid": "663c6371bd05f26997e9d528"
},
"name": "Gulab Jamun",
"description": "this is a product",
"price": 210.45,
"quantity": 10,
"category": "663b89711f437c9bcf6d3864"
}
My Mongodb Categories Schema :
{
"_id": {
"$oid": "663b896d1f437c9bcf6d3863"
},
"name": "electronics"
}
Upvotes: 2
Views: 227
Reputation: 95
After reading docs of dataloaden, I found that it is a generic library which doesn't require our database to be sql only. We can batch request using "$in" operator in mongodb.
I am attaching my category loader code for reference :
package category
import (
"context"
ers "errors"
"graphql_search/models"
"log"
"net/http"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func (a *api) CategoryLoaderMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
config := CategoryLoaderConfig{
Wait: 2 * time.Millisecond,
MaxBatch: 100,
Fetch: func(keys []string) ([]models.CategoryDB, []error) {
log.Println("Fetch Categories called for keys", keys)
// data we need to populate.
categories := make([]models.CategoryDB, len(keys))
errors := make([]error, len(keys))
keysObjectIds := make([]primitive.ObjectID, len(keys))
for i, key := range keys {
var err error
keysObjectIds[i], err = primitive.ObjectIDFromHex(key)
errors[i] = err
}
// do batch request here to mongodb
// Prepare the filter to find documents by their IDs
filter := bson.M{"_id": bson.M{"$in": keysObjectIds}}
cursor, err := a.Database.Collection("category").Find(context.Background(), filter)
if err != nil {
// Handle the error
for i := range errors {
errors[i] = err
}
return categories, errors
}
defer cursor.Close(context.Background())
// Iterate over the cursor and populate the categories slice
var categoriesGot []models.CategoryDB
err = cursor.All(context.Background(), &categoriesGot)
if err != nil {
// Handle the error
for i := range errors {
errors[i] = err
}
return categories, errors
}
log.Println("Categories got for given keys ", categoriesGot)
// iterate categories got and return exact sequence of data we got in keys
var mp map[string]models.CategoryDB = make(map[string]models.CategoryDB)
for _, d := range categoriesGot {
mp[d.ID.Hex()] = models.CategoryDB{
ID: d.ID,
Name: d.Name,
}
}
// now iterate all keys and enter relevent information
for i, key := range keys {
val, ok := mp[key]
if !ok {
errors[i] = ers.New("An Error getting data from map.")
} else {
categories[i] = val
}
}
return categories, errors
},
}
categoryLoader := NewCategoryLoader(config)
// add the categoryloader inside the context.
ctx := context.WithValue(r.Context(), a.CategoryLoaderKey, categoryLoader)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func (a *api) GetCategoryLoader(ctx context.Context) *CategoryLoader {
return ctx.Value(a.CategoryLoaderKey).(*CategoryLoader)
}
Add this middleware to your project and then we can get categoryloader object from our request's context and use it to fetch categories. For eg.
func (a *api) Get(ctx context.Context, id string) (*models.Category, error) {
loader := a.GetCategoryLoader(ctx)
cb, err := loader.Load(id)
if err != nil {
return nil, err
}
// NOTE : COMMENTED CODE IS OLD CODE. THIS IS HOW I USED TO FETCH CATEGORY EACH TIME.
// I WASN'T USING BATCHED REQUEST TO DATABASE AND WAS CALLING REDUDUNT QUERIES.
// ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
// defer cancel()
// objectId, _ := primitive.ObjectIDFromHex(id)
// result := a.Database.Collection("category").FindOne(ctx, bson.M{
// "_id": objectId,
// })
// var categoryDB models.CategoryDB
// err = result.Decode(&categoryDB)
// if err != nil {
// return nil, err
// }
// category := &models.Category{
// ID: categoryDB.ID.Hex(),
// Name: categoryDB.Name,
// }
category := &models.Category{
ID: cb.ID.Hex(),
Name: cb.Name,
}
return category, nil
}
FOR MORE DETAILS PLEASE REFER MY GITHUB REPO : GITHUB REPO OF MY PROJECT
Upvotes: 1