Currently I'm working on a project that will interact with a database. Based on my research, I would like to develop a repository class which responsability is to write/extract entities to/from a database, as mentioned in Ivar Jacobson's book "Object-oriented Software Engineering: A Use Case Driven Approach" and further in Uncle bob's keynote Architecture the Lost Years (I also plan on implementing Interactor and Boundaries later on, but I'm starting with the database for now).
I would like to design an implementation that follow some simple rules I think are valid for the Repository
class:
- The name of the database is parametric, which means that each child of the
Repository
class must know the name of the database it is connecting to. - It should get the information for a
IDbConnection
from an factory, which will be injected as a singleton dependency during runtime. - It should expose methods for queries and commands, where the INSERT command should return the Entities Id, and other ones (i.e. UPDATE, DELETE) should return the number of rows affected.
To do such, I've implemented it using Dapper
(a Micro ORM) on a PostgreSQL
database this way:
`
public abstract class Repository<TEntity> where TEntity : class, IEntity
{
private readonly IDatabaseConnectionFactory databaseConnectionFactory;
private readonly string databaseName;
protected Repository(IDatabaseConnectionFactory databaseConnectionFactory, string databaseName)
{
this.databaseConnectionFactory = databaseConnectionFactory;
this.databaseName = databaseName;
}
protected async Task<int> ExecuteCommandAsync(string command, object dynamicParameters)
{
using (IDbConnection databaseConnection = databaseConnectionFactory.GetDbConnection(databaseName))
return await SqlMapper.ExecuteAsync(databaseConnection, command, dynamicParameters);
}
protected async Task<int> ExecuteInsertCommandAsync(string command, object dynamicParameters)
{
command = AppendReturnIdIfNotContains(command);
using (IDbConnection databaseConnection = databaseConnectionFactory.GetDbConnection(databaseName))
return await SqlMapper.ExecuteScalarAsync<int>(databaseConnection, command, dynamicParameters);
}
protected async Task<IEnumerable<TEntity>> ExecuteQueryAsync(string query, object dynamicParameters)
{
using (IDbConnection databaseConnection = databaseConnectionFactory.GetDbConnection(databaseName))
return await SqlMapper.QueryAsync<TEntity>(databaseConnection, query, dynamicParameters);
}
private static string AppendReturnIdIfNotContains(string command)
{
if (!command.Contains(" RETURNING id"))
command += " RETURNING id";
return command;
}
}
I've found the ExecuteCommandAsync
and ExecuteInsertCommandAsync
very similar to one another, which makes me think if i shouldn't make the INSERT, UPDATE and DELETE commands follow the first method, and if needed, run a query such as SELECT lastVal()
for the id or SELECT @@ROWCOUNT
for the number of affected rows, as it will also be complient on a CQRS point of view. Should I implement it or keep it the way it is?