DEV Community

Tirumal Rao
Tirumal Rao

Posted on

Implementing the Repository Pattern in Go (Golang)

The Repository Pattern is a commonly used pattern in software architecture that abstracts the data access logic for an application. It promotes a cleaner separation of concerns, making the application more maintainable, scalable, and testable.

When applied to Go (or "Golang"), the Repository Pattern provides a straightforward way to abstract the access to your data source, which could be a database, a web service, or even a file system. Here's a brief overview of how you can implement it:

1. Define your domain model:

// models/user.go
package models

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
}
Enter fullscreen mode Exit fullscreen mode

2. Define the Repository interface:

This interface will outline the methods that your repository must implement.

// repository/user_repository.go
package repository

import "your_package_path/models"

type UserRepository interface {
    GetAll() ([]models.User, error)
    GetByID(id int) (models.User, error)
    Save(user models.User) error
    Delete(id int) error
}
Enter fullscreen mode Exit fullscreen mode

3. Implement the Repository:

For this example, let's assume we're using a simple in-memory store.

// repository/memory/user_repository_impl.go
package memory

import (
    "your_package_path/models"
    "your_package_path/repository"
)

type UserRepositoryMemory struct {
    users []models.User
}

func NewUserRepositoryMemory() repository.UserRepository {
    return &UserRepositoryMemory{
        users: []models.User{},
    }
}

func (r *UserRepositoryMemory) GetAll() ([]models.User, error) {
    return r.users, nil
}

func (r *UserRepositoryMemory) GetByID(id int) (models.User, error) {
    for _, user := range r.users {
        if user.ID == id {
            return user, nil
        }
    }
    return models.User{}, errors.New("User not found")
}

func (r *UserRepositoryMemory) Save(user models.User) error {
    r.users = append(r.users, user)
    return nil
}

func (r *UserRepositoryMemory) Delete(id int) error {
    for index, user := range r.users {
        if user.ID == id {
            r.users = append(r.users[:index], r.users[index+1:]...)
            return nil
        }
    }
    return errors.New("User not found")
}
Enter fullscreen mode Exit fullscreen mode

4. Use the Repository in your services or application logic:

package main

import (
    "your_package_path/models"
    "your_package_path/repository"
    "your_package_path/repository/memory"
)

func main() {
    // Using the in-memory implementation
    repo := memory.NewUserRepositoryMemory()

    user := models.User{
        ID:   1,
        Name: "John Doe",
    }

    repo.Save(user)

    users, _ := repo.GetAll()
    for _, user := range users {
        fmt.Println(user.Name)
    }
}
Enter fullscreen mode Exit fullscreen mode

With this pattern in place, you can easily swap out your in-memory implementation for, say, a PostgreSQL or a MongoDB implementation without changing your main application logic.

Top comments (2)

Collapse
 
gjae profile image
gjae

Could You show us about how to implements this pattern changing between InMemory and InDatabase without instance directly the UserRepositoryMemory? Thanks and great post

Collapse
 
dogers profile image
Dogers

It's not quite a clean implementation as the imports and code in 4 would have to change. You'd need to make it a bit more generic and alias the import so you could do repo := repository.NewRepository(). The idea is you can make lots of 3's which implement different backends.

This smells a lot like MVC without the V :)