Photo by Cleyder Duque from Pexels
Explain it simply
The repository, in simple words, is a pattern used to keep a loose connection between the client and the server data storing procedures hiding all complex implementation. This means that the client will not have to be concerned as to how to access the database, add or remove items from a collection of items, pointers, etc.
Why Would I Want To Use It
Let’s make a scenario where you just call API endpoints from your client and use it in a certain way. In a real-world scenario, you would probably call the same endpoint on different points in your files depending on where you need to use the data. For example:
const todoItemsElContainer = document.getElementById("todo-items");
fetch('http://example.com/todoItems.json')
.then(response => response.json())
.then(data => {
const todoItemDiv = document.createElement('div');
todoItemDiv.id = data.ID;
const innerText = document.createElement('p');
innerText.textContent = `${data.DESCR} duration: ${todoItemDiv.duration}`;
todoItemDiv.appendChild(innerText);
todoItemsElContainer.appendChild(todoItemDiv);
});
function addTodoItem(description, duration) {
const data = {
DESCR: description,
DURATION: duration
}
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response.json();
}
Issues with the above approach
There are several issues with the above code example. For one in the first fetch call we are mixing the presentation with fetching of the data. This makes the fetch non-reusable.
Furthermore, what happens now if we want to change a certain attribute of the requested object now?
Let’s suppose the API changes and we need to change an attribute name from todoList.DESCR to todoList.DESCRIPTION. We will have to go to every single instance and change that attribute to the new attribute name.
This is quite unproductive and might lead to accumulating mistakes and errors over time. (There is a chance we forget some instance unchanged and find that out later, which would be very frustrating).
Another possible scenario is that there will be some business logic involved in the procedure. By scattering around functions that are related to that object you are creating more violations to SOLID. But enough talking about the problem. What is the solution?
Repository Pattern to the Rescue
With the repository pattern, you manage to map attributes that come directly from the database to the repository model which gives us flexibility. If the attributes of the item change we can easily change them at the ONE spot where they are used: The Repository. No need to go to every file the object is retrieved, respecting the SOLID principle.
The power of abstraction and inheritance gives us the power to create a default use for simple API object management to minimize the boilerplate. Then with inheritance, the concrete implementations can overwrite the default behavior.
Additionally, business logic is encapsulated in functions inside the Repository. If the implementation ever changes you have it all in one place to change it however you like.
Below is an example of the TodoItemRepository.
class TodoItem {
/**
* Naming attributes the same as in the database
* helps when adding the items back to the database.
*/
constructor(id, description, duration) {
this.DESCR = description;
this.DURATION = duration;
this.ID = id;
}
getDescription() {
return this.DESCR;
}
getDuration() {
return this.DURATION;
}
getId() {
return this.ID;
}
}
class TodoItemRepository {
constructor() {
this.todoItems = [];
this.todoItemDataContext = new TodoItemDataContext();
}
getAll() {
return this.todoItemDataContext.getAll().then(response => {
if (Array.isArray(response)) {
response.map( todoItem => {
this.todoItems.push(new TodoItem(todoItem.ID, todoItem.DESCRIPTION, todoItem.DURATION));
})
}
return this.todoItems;
});
}
add(todoItem) {
this.todoItemDataContext.add(todoItem).then((newTodoItem) => {
this.todoItems.push(todoItem);
}).catch((error) => {
console.error('Error:', error);
});
}
}
First of all above, we decoupled the fetching of the data with the presentation of it. Now we can reuse it.
If now the database changes the DESCR attribute to DESCRIPTION all we need to do is change our Repository class to accept this change. In short, the design became more SOLID. Note here that you need to use getters, or setters (make the attributes private) within your application so you are not dealing directly with the attribute names.
In Conclusion
The repository pattern:
- Helps us keep the code SOLID
- Is an abstraction that hides the complexity of communicating with the data persistence layer
- Makes our code more robust
Top comments (3)
Hi, good article.
But in this line
this.todoItemDataContext = new TodoItemDataContext();
Where does the
TodoItemDataContext
class come from?Looking over it, I would say
TodoItemDataContext()
is the data source. In the example for bothgetAll()
andadd()
, the same methods are called on the object in a promise.Well, good article but instead of using those getters to maintain key consistency between entities, why not adopting the Adapter pattern?