Впровадження універсального шару репозиторію для MongoDB в Go

текст перекладу
pic

Нещодавно в одному з моїх проєктів я реалізував шар репозиторію, використовуючи генераки в Go. У цьому блозі я хочу поділитись, як саме я це реалізував.

Як передумова, потрібно встановити пакети драйвера MongoDB у вашому Go проєкті.
текст перекладу
Ви можете зробити це за допомогою наступної команди:

go get go.mongodb.org/mongo-driver/mongo

Після цього створіть файл з іменем store/store.go та реалізуйте загальний сховище, як показано в наведеному фрагменті коду.

package store  

import (  
 "context"  

 "go.mongodb.org/mongo-driver/bson"  
 "go.mongodb.org/mongo-driver/bson/primitive"  
 "go.mongodb.org/mongo-driver/mongo"  
 "go.mongodb.org/mongo-driver/mongo/options"  
)  

type BasicCrud[T Data] interface {  
 GetByFilters(ctx context.Context, filters bson.M) ([]T, error)  
 GetById(ctx context.Context, id primitive.ObjectID) (T, error)  
 GetByKey(ctx context.Context, key string, value interface{}) (T, error)  
 Create(ctx context.Context, t T) error  
 Update(ctx context.Context, t T, upsert ...bool) error  
 Delete(ctx context.Context, id primitive.ObjectID) error  
 Count(ctx context.Context, filters ...bson.M) (int64, error)  
 DoesExist(ctx context.Context, filters ...bson.M) (bool, error)  
 GetDB() *mongo.Database  
 SetDB(*mongo.Database)  
 GetCollectionName() string  
 SetCollectionName(name string)  
}  

type Data interface {  
 SetId(id primitive.ObjectID)  
 GetId() primitive.ObjectID  
}  

type DataStore[T Data] struct {  
 collectionName string  
 db *mongo.Database  
}  

func (store *DataStore[T]) GetDB() *mongo.Database {  
 return store.db  
}  

func (store *DataStore[T]) SetDB(db *mongo.Database) {  
 store.db = db  
}  

func (store *DataStore[T]) GetCollectionName() string {  
 return store.collectionName  
}  

func (store *DataStore[T]) SetCollectionName(name string) {  
 store.collectionName = name  
}  

func (store *DataStore[T]) GetByFilters(ctx context.Context, filters bson.M) ([]T, error) {  
 var ts []T  
 collection := store.db.Collection(store.collectionName)  

 cursor, err := collection.Find(ctx, filters)  
 if err != nil {  
 return ts, err  
 }  
 defer cursor.Close(ctx)  

 for cursor.Next(ctx) {  
 var t T  
 if err := cursor.Decode(&t); err != nil {  
 return ts, err  
 }  
 ts = append(ts, t)  
 }  

 if err = cursor.Err(); err != nil {  
 return ts, err  
 }  

 return ts, nil  
}  

func (store *DataStore[T]) GetById(ctx context.Context, id primitive.ObjectID) (T, error) {  
 var t T  
 collection := store.db.Collection(store.collectionName)  

 err := collection.FindOne(ctx, bson.M{"_id": id}).Decode(&t)  
 if err != nil {  
 return t, err  
 }  
 return t, nil  
}  

func (store *DataStore[T]) Create(ctx context.Context, t T) error {  
 collection := store.db.Collection(store.collectionName)  

 newId := primitive.NewObjectID()  
 t.SetId(newId)  

 _, err := collection.InsertOne(ctx, &t)  
 if err != nil {  
 return err  
 }  

 return nil  
}  

func (store *DataStore[T]) Update(ctx context.Context, t T, upsert ...bool) error {  
 collection := store.db.Collection(store.collectionName)  

 opt := &options.UpdateOptions{Upsert: &upsert[0]}  
 result, err := collection.UpdateOne(ctx, bson.M{"_id": t.GetId()}, bson.M{"$set": t}, opt)  

 if err != nil {  
 return err  
 }  

 if id, ok := result.UpsertedID.(primitive.ObjectID); ok {  
 t.SetId(id)  
 }  

 return nil  
}  

func (store *DataStore[T]) Delete(ctx context.Context, id primitive.ObjectID) error {  
 collection := store.db.Collection(store.collectionName)  

 _, err := collection.DeleteOne(ctx, bson.M{"_id": id})  
 if err != nil {  
 return err  
 }  

 return nil  
}  

func (store *DataStore[T]) GetByKey(ctx context.Context, key string, value interface{}) (T, error) {  
 var t T  
 collection := store.db.Collection(store.collectionName)  

 err := collection.FindOne(ctx, bson.M{key: value}).Decode(t)  
 if err != nil {  
 return t, err  
 }  
 return t, nil  
}  

func (store *DataStore[T]) Count(ctx context.Context, filters ...bson.M) (int64, error) {  
 collection := store.db.Collection(store.collectionName)  

 count, err := collection.CountDocuments(ctx, filters[0])
текст перекладу
if err != nil {  
 return count, err  
}  
return count, nil  

func (store *DataStore[T]) DoesExist(ctx context.Context, filters ...bson.M) (bool, error) {  
 collection := store.db.Collection(store.collectionName)  

 count, err := collection.CountDocuments(ctx, filters[0])  
 if err != nil {  
 return count > 0, err  
 }  
 return count > 0, nil  
}

У цьому коді DataStore[T Data] реалізує інтерфейс BasicCrud[T Data].
текст перекладу
Ми будемо використовувати інтерфейс BasicCrud[T Data] та структуру DataStore[T Data], щоб вбудувати їх у інтерфейс та структуру для моделі бази даних відповідно, щоб моделі могли отримати доступ до функцій, реалізованих через генерики.

Наприклад, якщо ви хочете реалізувати UserStore для збереження даних у колекцію users, ви можете зробити це наступним чином:

Створіть файл models/user_model.go і реалізуйте модель об'єкта. Модель повинна бути створена так, щоб вона реалізовувала інтерфейс Data, визначений у store.go.

package models  

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

type User struct {  
 Id primitive.ObjectID `bson:"_id"`  
 Email string `bson:"email"`  
 Name string `bson:"name"`  
 Password string `bson:"password"`  
}  

func (u *User) GetId() primitive.ObjectID { // Додайте логіку валідації тут  
 return u.Id  
}  

func (u *User) SetId(id primitive.ObjectID) {  
 u.Id = id  
}

Далі створіть файл з назвою store/user_store.go і реалізуйте його, як показано нижче.

package store  

import (  
 "context"  

 "github.com/amjadnzr/go-generic-repo-layer-blog/models"  
 "go.mongodb.org/mongo-driver/bson"  
 "go.mongodb.org/mongo-driver/mongo"  
)  

type IUserStore interface {  
 BasicCrud[*models.User]  
}  

type userStore struct {  
 *DataStore[*models.User]  
}  

func UserStore(db *mongo.Database) IUserStore {  
 return &userStore{  
 &DataStore[*models.User]{  
 db: db,  
 collectionName: "users",  
 },  
 }  
}

Крім того, цей код можна розширити, додавши унікальну функцію для репозиторію для конкретної колекції. Наприклад, якщо ви хочете додати функцію репозиторію для перевірки існування користувача за наданою адресою електронної пошти, ви можете змінити код, як показано нижче.

Все, що вам потрібно зробити — це реалізувати нову функцію як метод для структури UserStore і додати її в інтерфейс IUserStore.

package store  

import (  
 "context"  

 "github.com/amjadnzr/go-generic-repo-layer-blog/models"  
 "go.mongodb.org/mongo-driver/bson"  
 "go.mongodb.org/mongo-driver/mongo"  
)  

type IUserStore interface {  
 BasicCrud[*models.User]  
 DoesUserExists(ctx context.Context, email string) (bool, error)  
}  

type userStore struct {  
 *DataStore[*models.User]  
}  

func UserStore(db *mongo.Database) IUserStore {  
 return &userStore{  
 &DataStore[*models.User]{  
 db: db,  
 collectionName: "users",  
 },  
 }  
}  

func (store *userStore) DoesUserExists(ctx context.Context, email string) (bool, error) {  
 collection := store.GetDB().Collection(store.GetCollectionName())  

 var user models.User  
 filter := bson.M{"email": email}  

 err := collection.FindOne(ctx, filter).Decode(&user)  
 if err != nil {  
 if err == mongo.ErrNoDocuments {  
 return false, nil  
 }  
 return false, err  
 }  

 return true, nil  
}

Тепер ви можете створити підключення до бази даних. Створіть новий файл database/database.go і додайте наступний код.

package database  

import (  
 "context"  
 "fmt"  
 "time"  

 "go.mongodb.org/mongo-driver/mongo"  
 "go.mongodb.org/mongo-driver/mongo/options"  
)  

func New(uri string, dbName string, timeout time.Duration) (*mongo.Client, *mongo.Database, error) {  

 serverAPI := options.ServerAPI(options.ServerAPIVersion1)  
 clientOptions := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)  

 ctx, cancel := context.WithTimeout(context.Background(), timeout)  
 defer cancel()  

 client, err := mongo.Connect(ctx, clientOptions)  
 if err != nil {  
 return nil, nil, fmt.Errorf("не вдалося підключитися до MongoDB: %s", err)  
 }  

 database := client.Database(dbName)  

 return client, database, nil  
}

Нарешті, для тестування всього рішення ви можете викликати функції, які ви створили, слідуючи цьому блогу. Нижче наведено приклад, як це використовувати у файлі main.go.
текст перекладу
Ви можете продовжити дослідження, викликаючи інші доступні функції або реалізовуючи нові розширені функції.

package main  

import (  
 "context"  
 "fmt"  
 "time"  

 "github.com/amjadnzr/go-generic-repo-layer-blog/database"  
 "github.com/amjadnzr/go-generic-repo-layer-blog/models"  
 "github.com/amjadnzr/go-generic-repo-layer-blog/store"  
)  

func main() {  
 uri := "mongodb://localhost:27017"  
 dbName := "test-db"  
 timeout := 30 * time.Second  

 _, db, err := database.New(uri, dbName, timeout)  
 if err != nil {  
 fmt.Printf("Помилка підключення до MongoDB: %s\n", err)  
 return  
 }  
 fmt.Println("Підключено до MongoDB")  

 userStore := store.UserStore(db)  
 if err := userStore.Create(context.Background(), &models.User{Email: "[email protected]"}); err != nil {  
 fmt.Printf("Помилка при створенні користувача: %s\n", err)  
 return  
 }  
 fmt.Println("Користувача створено успішно")  
}

Це все, що я мав у запасі для представлення. Сподіваюся, ви дізналися щось корисне з цього блогу. Прощаюсь з вами до наступного блогу. Не соромтеся підключатися, коментувати поліпшення та підписуватися на мій профіль, щоб залишатися в курсі моїх майбутніх блогів.

На все добре !!

Перекладено з: Implementing a Generic Repository Layer for MongoDB in Go

Leave a Reply

Your email address will not be published. Required fields are marked *