The Purpose of the Repository Pattern
The Repository Pattern is more than three decades old, while its purpose has shifted over the years, it still provides a simple solution to constrain your data access code.
The Repository Pattern is an interface:
public interface IWidgetRepository
{
Task AddWidget(Widget widget);
Task<Widget> GetWidgetById(Guid id);
Task DeleteWidgetById(Guid id);
}
Implement the interface, inject where needed, and go.
Notice, I said interface because this pattern does not imply any of the following: Dapper, ORM, EntityFramework, Unit of Work, SQL, DbSet, or LINQ.
It's merely a Fascade enforcing Interface Segregation.
Microservices, Service Layer, CQRS, DDD. None of these matter.
It Constrains Options
What is a better engineered solution: three options or unlimited options? I would argue that fewer options are always better.
More options equals more bugs and a higher rate of entropy.
It Makes Testing Easier
Check out this application service level code:
public async Task <Widget> FindWidget(Guid id)
{
Widget widget;
// Retrieve widget from some data source.
if (widget != null)
{
return widget;
}
return Widget.EmptyWidget();
}
How do you unit test this code? Repository. 1) Mock the method. 2) Assert the result.
Raw disk, file, or database access. Good luck. Each of those options requires multiple paragraphs in its own right.
Good software reduces options. The Repository Pattern is no different in that it only provides what has been deemed necessary--interface segregation in the purest form.