This page looks best with JavaScript enabled

Repository

 ·  ☕ 10 min read  ·  ✍️ t1
+------------------------+ | Business Logic | |------------------------| | - Use Repository | | interface to | | manipulate data | +------------------------+ | | +------------------------+ | Repository Interface | |------------------------| | - Define methods | | for manipulating | | data | +------------------------+ | | +------------------------+ | Concrete Repository | |------------------------| | - Implement Repository | | interface | | - Handle details of | | storing and | | retrieving data | +------------------------+

In software engineering, the repository design pattern is a layer between the business logic and the data access logic. It provides an abstract interface for managing the storage of data, such as saving and retrieving data from a database.

The repository design pattern is used to decouple the business logic of an application from the data access logic. This allows the business logic to be more reusable and testable, as it is not tied to a specific data storage implementation. It also makes it easier to switch between different data storage options, such as a relational database, a NoSQL database, a file system, a remote API, or even in-memory data structures.

The repository design pattern typically consists of a repository interface and one or more concrete implementations of the repository. The interface defines the methods that can be used to manipulate the data, such as saving, updating, and deleting data. The concrete implementations of the repository handle the details of storing and retrieving the data from the underlying data store.

The repository design pattern is often used in conjunction with the unit of work design pattern, which manages the transactions and state of the data in the repository.

Here is an example of a repository interface and a concrete implementation in Go:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Repository is the interface for a user repository.
type Repository interface {
	Get(id int) (*User, error)
	Save(user *User) error
	Update(user *User) error
	Delete(id int) error
}

// UserRepository is a concrete implementation of a user repository.
type UserRepository struct {
	db *sql.DB
}

// Get retrieves a user from the repository by ID.
func (r *UserRepository) Get(id int) (*User, error) {
	// Query the database for the user with the given ID.
	row := r.db.QueryRow("SELECT * FROM users WHERE id = $1", id)

	// Scan the row into a user struct.
	var user User
	err := row.Scan(&user.ID, &user.Name, &user.Email)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("user not found")
		}
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	return &user, nil
}

// Save saves a new user to the repository.
func (r *UserRepository) Save(user *User) error {
	// Insert the user into the database.
	_, err := r.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", user.Name, user.Email)
	if err != nil {
		return fmt.Errorf("failed to save user: %w", err)
	}

	return nil
}

// Update updates an existing user in the repository.
func (r *UserRepository) Update(user *User) error {
	// Update the user in the database.
	_, err := r.db.Exec("UPDATE users SET name = $1, email = $2 WHERE id = $3", user.Name, user.Email, user.ID)
	if err != nil {
		return fmt.Errorf("failed to update user: %w", err)
	}

	return nil
}

// Delete removes a user from the repository.
func (r *UserRepository) Delete(id int) error {
	// Delete the user from the database.
	_, err := r.db.Exec("DELETE FROM users WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("failed to delete user: %w", err)
	}

	return nil
}

To use the repository, the business logic of the application can depend on the Repository interface and use it to manipulate the data without having to worry about the details of how the data is stored. For example:

1
2
3
4
5
6
7
8
func UpdateUser(repo Repository, user *User) error {
	// Update the user in the repository.
	if err := repo.Update(user); err != nil {
		return fmt.Errorf("failed to update user: %w", err)
	}

	return nil
}

To use the repository in the client code, you will need to instantiate a concrete implementation of the repository and inject it into the client code. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func main() {
	// Connect to the database.
	db, err := sql.Open("postgres", "user=postgres password=mypassword dbname=mydb sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Create a new user repository.
	repo := &UserRepository{db: db}

	// Use the repository to update a user.
	user := &User{ID: 1, Name: "John", Email: "[email protected]"}
	if err := UpdateUser(repo, user); err != nil {
		log.Fatal(err)
	}
}

In this example, the client code connects to a database and creates a new UserRepository instance, passing in the database connection as a dependency. It then calls the UpdateUser function and passes in the repo instance as the repository. The UpdateUser function can then use the Repository interface to update the user in the repository without having to worry about the details of how the data is stored.

The repository design pattern can be applied to any type of data storage or retrieval system, not just databases.

For example, suppose you are building a client for a REST API that provides access to a collection of resources. You could implement a repository interface that defines methods for retrieving, creating, updating, and deleting resources from the API, and a concrete implementation that handles the details of making HTTP requests to the API and parsing the responses. The business logic of your application could then depend on the repository interface and use it to manipulate the resources without having to worry about the details of how the data is being stored or retrieved.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Repository is the interface for a resource repository.
type Repository interface {
	Get(id string) (*Resource, error)
	GetAll() ([]*Resource, error)
	Save(resource *Resource) (*Resource, error)
	Update(resource *Resource) (*Resource, error)
	Delete(id string) error
}

// ResourceRepository is a concrete implementation of a resource repository.
type ResourceRepository struct {
	apiClient *http.Client
	baseURL   string
}

// Get retrieves a resource from the repository by ID.
func (r *ResourceRepository) Get(id string) (*Resource, error) {
	// Make a GET request to the API to retrieve the resource.
	req, err := http.NewRequest("GET", r.baseURL+"/resources/"+id, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := r.apiClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to get resource: %w", err)
	}
	defer resp.Body.Close()

	// Parse the response body into a resource struct.
	var resource Resource
	if err := json.NewDecoder(resp.Body).Decode(&resource); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &resource, nil
}

// GetAll retrieves all resources from the repository.
func (r *ResourceRepository) GetAll() ([]*Resource, error) {
	// Make a GET request to the API to retrieve all resources.
	req, err := http.NewRequest("GET", r.baseURL+"/resources", nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := r.apiClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to get resources: %w", err)
	}
	defer resp.Body.Close()

	// Parse the response body into a slice of resource structs.
	var resources []*Resource
	if err := json.NewDecoder(resp.Body).Decode(&resources); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return resources, nil
}

// Save creates a new resource in the repository.
func (r *ResourceRepository) Save(resource *Resource) (*Resource, error) {
	// Marshal the resource into a JSON payload.
	payload, err := json.Marshal(resource)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal resource: %w", err)
	}

	// Make a POST request to the API to create the resource.
	req, err := http.NewRequest("POST", r.baseURL+"/resources", bytes.NewReader(payload))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := r.apiClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to create resource: %w", err)
	}
	defer resp.Body.Close()

	// Parse the response body into a resource struct.
	var createdResource Resource
	if err := json.NewDecoder(resp.Body).Decode(&createdResource); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &createdResource, nil
}
// Update updates an existing resource in the repository.
func (r *ResourceRepository) Update(resource *Resource) (*Resource, error) {
	// Marshal the resource into a JSON payload.
	payload, err := json.Marshal(resource)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal resource: %w", err)
	}

	// Make a PUT request to the API to update the resource.
	req, err := http.NewRequest("PUT", r.baseURL+"/resources/"+resource.ID, bytes.NewReader(payload))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := r.apiClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to update resource: %w", err)
	}
	defer resp.Body.Close()

	// Parse the response body into a resource struct.
	var updatedResource Resource
	if err := json.NewDecoder(resp.Body).Decode(&updatedResource); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &updatedResource, nil
}

// Delete removes a resource from the repository.
func (r *ResourceRepository) Delete(id string) error {
	// Make a DELETE request to the API to delete the resource.
	req, err := http.NewRequest("DELETE", r.baseURL+"/resources/"+id, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := r.apiClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to delete resource: %w", err)
	}
	defer resp.Body.Close()

	return nil
}

The client code can instantiate a concrete implementation of the repository and inject it into the function when it is called, like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func main() {
	// Create a new HTTP client.
	client := &http.Client{}

	// Create a new resource repository.
	repo := &ResourceRepository{apiClient: client, baseURL: "https://api.example.com"}

	// Use the repository to retrieve a resource by ID.
	resource, err := repo.Get("1")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resource)

	// Use the repository to retrieve all resources.
	resources, err := repo.GetAll()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(resources)

	// Use the repository to save a new resource.
	newResource := &Resource{Name: "New Resource", Description: "This is a new resource"}
	savedResource, err := repo.Save(newResource)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(savedResource)

	// Use the repository to update the new resource.
	savedResource.Description = "This resource has been updated"
	updatedResource, err := repo.Update(savedResource)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(updatedResource)

	// Use the repository to delete the new resource.
	if err := repo.Delete(savedResource.ID); err != nil {
		log.Fatal(err)
	}
}

In this example, the main function creates a new ResourceRepository and uses it to retrieve a resource by ID, retrieve all resources, save a new resource, update the new resource, and delete the new resource. The repository handles the details of making the appropriate HTTP requests to the API and parsing the responses, allowing the business logic to focus on manipulating the resources.

Share on

t1
WRITTEN BY
t1
Dev