Database Support
One generic repository interface over MongoDB, SQL and DynamoDB, with built-in CRUD, pagination and custom repository support.
Ginboot provides a powerful and flexible multi-database support system through a generic repository interface. This allows you to interact with different database systems (MongoDB, SQL, DynamoDB) using a consistent API, making your application more modular, testable, and adaptable to various data storage needs.
Generic Repository Interface
The core of Ginboot's database abstraction is the GenericRepository[T any] interface. This interface defines a comprehensive set of common data access operations, ensuring a uniform way to interact with different database types.
type GenericRepository[T any] interface {
FindById(id string) (T, error)
FindAllById(ids []string) ([]T, error)
Save(doc T) error
SaveOrUpdate(doc T) error
SaveAll(docs []T) error
Update(doc T) error
Delete(id string) error
FindOneBy(field string, value interface{}) (T, error)
FindOneByFilters(filters map[string]interface{}) (T, error)
FindBy(field string, value interface{}) ([]T, error)
FindByFilters(filters map[string]interface{}) ([]T, error)
FindAll(options ...interface{}) ([]T, error)
FindAllPaginated(pageRequest PageRequest) (PageResponse[T], error)
FindByPaginated(pageRequest PageRequest, filters map[string]interface{}) (PageResponse[T], error)
CountBy(field string, value interface{}) (int64, error)
CountByFilters(filters map[string]interface{}) (int64, error)
ExistsBy(field string, value interface{}) (bool, error)
ExistsByFilters(filters map[string]interface{}) (bool, error)
}Document Interface
For SQL and DynamoDB repositories, your data models must implement the Document interface, which provides the table/collection name.
type Document interface {
GetTableName() string
}Pagination Structures
Ginboot provides standardized structures for handling pagination requests and responses.
type SortField struct {
Field string `json:"field"`
Direction int `json:"direction"` // 1 for ascending, -1 for descending
}
type PageRequest struct {
Page int `json:"page"`
Size int `json:"size"`
Sort SortField `json:"sort"`
}
type PageResponse[T interface{}] struct {
Contents []T `json:"content"`
NumberOfElements int `json:"numberOfElements"`
Pageable PageRequest `json:"pageable"`
TotalPages int `json:"totalPages"`
TotalElements int `json:"totalElements"`
}Choosing a backend
The three supported backends share the interface above — pick the tab for the one you're using.
Ginboot offers robust support for MongoDB through MongoConfig for connection management and MongoRepository for data operations.
MongoDB Configuration
Use ginboot.NewMongoConfig() to build your MongoDB connection string. You can specify host, port, credentials, database name, and additional options.
import (
"log"
"github.com/klass-lk/ginboot"
)
func connectMongo() *mongo.Database {
config := ginboot.NewMongoConfig().
WithHost("localhost", 27017).
WithDatabase("mydatabase").
WithCredentials("myuser", "mypassword").
WithOption("authSource", "admin")
db, err := config.Connect()
if err != nil {
log.Fatalf("Failed to connect to MongoDB: %v", err)
}
fmt.Println("Connected to MongoDB!")
return db
}MongoDB Repository Example
Define your document struct with bson tags for MongoDB field mapping and a ginboot:"_id" tag for the primary key if it's not named ID.
import (
"fmt"
"go.mongodb.org/mongo-driver/mongo"
"github.com/klass-lk/ginboot"
)
type User struct {
ID string `bson:"_id" ginboot:"_id"` // ginboot:_id helps the repository identify the ID field
Name string `bson:"name"`
Age int `bson:"age"`
}
// NewMongoRepository creates a new MongoDB repository instance.
// The collection name is typically the plural of your entity name.
func NewUserRepository(db *mongo.Database) *UserRepository {
return &UserRepository{
MongoRepository: ginboot.NewMongoRepository[User](db, "users"),
}
}
// Example usage of the MongoDB repository
func main() {
db := connectMongo() // Assume connectMongo() returns *mongo.Database
repo := ginboot.NewMongoRepository[User](db, "users")
// Save a new user
user := User{ID: "1", Name: "John Doe", Age: 30}
err := repo.Save(user)
if err != nil { log.Fatal(err) }
fmt.Println("User saved:", user.Name)
// Find user by ID
foundUser, err := repo.FindById("1")
if err != nil { log.Fatal(err) }
fmt.Println("Found user:", foundUser.Name)
// Update user
foundUser.Age = 31
err = repo.Update(foundUser)
if err != nil { log.Fatal(err) }
fmt.Println("User updated:", foundUser.Name)
// Find users by filter
filters := map[string]interface{}{"age": 31}
users, err := repo.FindByFilters(filters)
if err != nil { log.Fatal(err) }
fmt.Println("Users with age 31:", len(users))
// Paginated query
pageRequest := ginboot.PageRequest{Page: 1, Size: 10, Sort: ginboot.SortField{Field: "name", Direction: 1}}
pageResponse, err := repo.FindAllPaginated(pageRequest)
if err != nil { log.Fatal(err) }
fmt.Println("Paginated results:", len(pageResponse.Contents))
}Customizing Repositories
You can easily extend Ginboot's generic repositories to add database-specific methods or custom business logic. This is done by embedding the generic repository within your own custom repository struct.
import (
"go.mongodb.org/mongo-driver/mongo"
"github.com/klass-lk/ginboot"
)
type UserRepository struct {
*ginboot.MongoRepository[User] // Embed the generic repository
}
func NewUserRepository(db *mongo.Database) *UserRepository {
return &UserRepository{
MongoRepository: ginboot.NewMongoRepository[User](db, "users"),
}
}
// Add a custom method specific to UserRepository
func (r *UserRepository) FindUsersByStatus(status string) ([]User, error) {
// You can use the embedded generic repository methods
return r.FindBy("status", status)
}
// Or implement a completely custom query
func (r *UserRepository) GetActiveUsersCount() (int64, error) {
// Access the underlying collection directly if needed
// return r.collection.CountDocuments(context.Background(), bson.M{"status": "active"})
return r.CountBy("status", "active")
}