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

Microsoft Azure Well-Architected Framework - Maturity models

The Azure Well-Architected Framework has always been a great way to assess and review workloads. But with the recent updates—especially the introduction of maturity levels —it’s becoming much more than just a checklist. 💡 It’s evolving into a concept. Not only can teams review their architecture, but they can now score, track progress, and continuously improve . The maturity model provides clear stages—from establishing a solid foundation to achieving future-proof agility—making it easier to understand where you are today and where you should aim tomorrow. Why is this important? ✅ It transforms reviews into a roadmap for growth ✅ It allows measurable scoring of architecture maturity ✅ It pushes teams to focus not only on compliance, but also on resilience, agility, and future-readiness Each update makes the Azure Well-Architected Framework better and stronger —helping organizations align technology decisions with long-term business success. 👉 In my view, this is the right dire...

Coinbase x402 is great option for paymant per request!

  🚀 What is Coinbase x402? x402 is a new open payment protocol built by Coinbase that revives the long-forgotten HTTP 402 “Payment Required” status and turns it into a modern, internet-native payment layer . Using stablecoins (such as USDC ) directly over HTTP, x402 allows services: APIs, websites, or digital content platforms to charge per request. At the same time,  clients (including AI agents and automated systems ) can pay programmatically , without accounts, subscriptions, or human interaction. In simple terms: request → pay → get response , all at the protocol level. How It Works (Simplified) A client requests a protected resource (API, data, article, service). The server responds with HTTP 402 Payment Required , including payment instructions. The client (app or AI agent) generates a payment transaction using its wallet. The client resends the request with payment details included in HTTP headers. The server verifies the payment and immediately ret...

Microsoft Azure Well-Architected Framework - Reliability

Reliability is a foundational pillar when building resilient systems, especially for critical components. Outages and malfunctions pose serious risks to any workload, so a truly reliable system must be designed to detect, withstand, and recover from failures within an acceptable timeframe. It must ensure continued functionality and maintain availability so that users can access services as expected, both in terms of uptime and quality. 🔧 Aligned with Azure’s Reliability Checklist Keep it simple and efficient Strive for a solution that meets requirements without unnecessary complexity—simplicity simplifies reliability Identify and prioritize flows Map out user and system flows, assess their criticality, and focus engineering efforts on those with the highest business impact Conduct failure mode analysis (FMA) Investigate every dependency and component with a methodical FMA to uncover weak points, and design mitigation strategies accordingly Define clear reliability and r...