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"`
}
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
}
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")
}
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)
}
}
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)
Could You show us about how to implements this pattern changing between InMemory and InDatabase without instance directly the UserRepositoryMemory? Thanks and great post
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 :)