Skip to main content

Hints and Hacks: Mastering Go Project Structure and Generic Repositories with GORM

 

Mastering Go Project Structure and Generic Repositories with GORM

Establishing a robust project structure is paramount when building scalable and maintainable Go applications. Coupled with the flexibility of a generic repository, this approach ensures clean, reusable, and extendable code. In this article, we'll explore how to set up a well-structured Go project and implement a generic repository pattern with GORM, focusing on practical examples and best practices.


Why Project Structure Matters

A well-organized project structure is critical for:

  1. Scalability: Adding new features or components becomes seamless.
  2. Readability: Developers can easily navigate the codebase.
  3. Maintainability: Issues can be identified and resolved efficiently.

Adhering to a modular structure, your application remains clean and manageable as it grows.


Recommended Go Project Structure

Here is a typical project layout for a Go application leveraging GORM and a generic repository:

app_db_generic/
├── config/               # Configuration setup
│   └── config.go
├── db/                   # Database connection logic
│   └── db.go
├── models/               # Database models
│   └── user.go
├── repositories/         # Generic and specific repositories
│   └── repository.go
├── services/             # Business logic layer
│   └── user_service.go
├── handlers/             # HTTP handlers
│   └── user_handler.go
├── routes/               # Router setup
│   └── routes.go
├── migrations/           # migrations scripts and run migration entry point
│   └── main.go
├── main.go # Application entry point ├── go.mod # Go module file ├── go.sum # Dependency lock file

This structure separates concerns effectively, making locating and modifying specific components easy.


Setting Up a Generic Repository

What Is a Generic Repository?

A generic repository provides common data access methods (e.g., Create, Read, Update, Delete) that can work with multiple entity types. Using Go’s generics, we can define a single repository that handles various database models, reducing redundancy and improving maintainability.

Implementing the Generic Repository

Below is the implementation of a generic repository using GORM.

repositories/repository.go

package repositories

import "gorm.io/gorm"

type Repository[T any] interface {
    Create(entity *T) error
    GetByID(id uint) (*T, error)
    GetAll() ([]T, error)
    Update(entity *T) error
    Delete(id uint) error
}

type repository[T any] struct {
    db *gorm.DB
}

func NewRepository[T any](db *gorm.DB) Repository[T] {
    return &repository[T]{db: db}
}

func (r *repository[T]) Create(entity *T) error {
    return r.db.Create(entity).Error
}

func (r *repository[T]) GetByID(id uint) (*T, error) {
    var entity T
    err := r.db.First(&entity, id).Error
    return &entity, err
}

func (r *repository[T]) GetAll() ([]T, error) {
    var entities []T
    err := r.db.Find(&entities).Error
    return entities, err
}

func (r *repository[T]) Update(entity *T) error {
    return r.db.Save(entity).Error
}

func (r *repository[T]) Delete(id uint) error {
    return r.db.Delete(new(T), id).Error
}

Using the Generic Repository

Define a Model

Let’s create a sample User model.

models/user.go

package models

type User struct {
    ID       uint   `gorm:"primaryKey"`
    Name     string `gorm:"size:255;not null"`
    Email    string `gorm:"uniqueIndex;not null"`
    Password string `gorm:"not null"`
}

Initialize the Repository

We can now use the generic repository for the User model.

Example Usage:

package main

import (
    "log"
    "app_db_generic/db"
"app_db_generic/models"
"app_db_generic/repositories"
) func main() { // Connect to the database config := db.Config{ Host: "localhost", Port: "5432", User: "postgres", Password: "password", DBName: "mydb", } db.ConnectDatabase(&config) // Initialize a generic repository for User userRepo := repositories.NewRepository[models.User](db.DB) // Create a new user user := &models.User{Name: "John Doe", Email: "john.doe@example.com", Password: "password123"} if err := userRepo.Create(user); err != nil { log.Fatalf("Failed to create user: %v", err) } log.Printf("User created: %+v", user) // Fetch all users users, err := userRepo.GetAll() if err != nil { log.Fatalf("Failed to fetch users: %v", err) } log.Printf("Users: %+v", users) }

Benefits of a Generic Repository

  1. Code Reusability: Common operations are implemented once and reused across all models.
  2. Type Safety: Generics ensure that the repository works with the correct data types.
  3. Scalability: Adding a new model requires minimal setup—no need to rewrite CRUD logic.
  4. Centralized Logic: Centralizing data access logic simplifies debugging and enhances maintainability.

Conclusion

By combining a clear project structure with a generic repository pattern, you can create Go applications that are clean, efficient, and easy to maintain. This approach not only reduces redundancy but also ensures consistency across your codebase.

Whether you’re building a simple application or a complex system, adopting these principles will set you up for long-term success. Try it out and experience the benefits firsthand!



Comments

  1. I used repository pattern previously in c# and it is great to see a good example of adapting it somewhere else like Go.Thanks for sharing!

    ReplyDelete

Post a Comment

Popular posts from this blog

Why Microsoft Azure Well-Architected Framework Can Improve Architecture

Small and medium-sized businesses often face a common challenge: the absence of experienced cloud engineers. Due to limited resources, teams typically choose the quickest path—getting things done in the easiest, fastest way. Unfortunately, this approach often leads to solutions that aren't secure, cost too much, and become nearly impossible to extend or manage effectively. Recognizing this critical challenge, Microsoft Azure has developed the Well-Architected Framework. This comprehensive set of guidelines and best practices helps businesses assess their existing solutions and guides them toward building robust, secure, cost-effective, and manageable cloud infrastructures from the start. The Azure Well-Architected Framework is structured around five essential pillars: Cost Optimization : Ensuring that cloud resources are used efficiently and effectively, reducing unnecessary expenses. Operational Excellence : Focusing on the ability to run and monitor systems effectively, ensuring ...

"Dushnylo" Series: Monolith First approach.

I keep hearing, “You MUST start with a monolith.” Every new project? Always? When I hear that, two thoughts immediately come to mind:      1️⃣ “It depends.” You can’t just blindly say every project must start as a monolith.      2️⃣ My inner Dushnylo whispers: “Time to make a post about this.” So, here’s my take: I disagree. Not only do I disagree, but I believe the most critical and dangerous part of system design is analyzing and understanding business needs before making architectural decisions. Why? Simple. Imagine you’re building a streaming platform that processes massive amounts of data, handles notifications, and integrates with third-party services. Does this sound like something you’d build as a pure monolith? Of course not. But I do agree on one thing—you have to start somewhere. That starting point could be a simple core application —yes, it might look like a monolith at first. But you’re not designing the entire system as a monolith. ...

First Look at Cerbos: A Solution for Dynamic Role & Permission Management

Introduce My next post is about tools for managing roles and dynamically controlling access to resources. Some business requirements demand extreme flexibility, often requiring a combination of RBAC + ABAC at the same time. From my experience, I’ve seen a lot of solutions, but most don’t cover all the key points. There are three circles that are really hard to combine: Performance, Security, and Flexibility . And when someone tries to implement all three—oh, it’s painful. But I found a technology that (almost) solves this challenge: Cerbos —a scalable, open-source authorization layer for handling roles and permissions. ( Cerbos site ) Why is it good? ✅ Centralized configuration – Everything is managed in one place. ✅ Easy integration – SDKs are available for all popular languages:     ðŸ”¹ .NET, Go, Java, JS, PHP, Python, Ruby, Rust ✅ Great documentation – Clear examples and guidance. ✅ Playground for testing – No need to run an app or set up tools. Just te...