I am currently developing a set of C# libraries that contain interfaces and other utilities to use in other Clean Architecture projects. They are 4 in total:
- Domain (Entities, Repositories interfaces, etc.)
- Application (Commands, Queries, Handlers, Mappers, Services, etc.)
- Infrastructure (Implementations of various interfaces)
- Api (Web Apis, mainly Controllers)
As I would like to be able to use different Databases for different purposes (SQL, Document, Key-Value, etc.), I tried to code a generic Entity interface at the Domain layer. Then, I coded some generic repositories based on the generic Entity.
public interface IEntity
{
}
public interface IRepository<T, ID> where T : class
{
}
public interface ICrudRepository<T, ID> : IRepository<T, ID> where T : class
{
void InsertOne(T entity);
Task InsertOneAsync(T entity);
void InsertMany(IEnumerable<T> entities);
Task InsertManyAsync(IEnumerable<T> entities);
void ReplaceOne(T entity);
Task ReplaceOneAsync(T entity);
void DeleteOne(Expression<Func<T, bool>> filterExpression);
Task DeleteOneAsync(Expression<Func<T, bool>> filterExpression);
void DeleteById(ID id);
Task DeleteByIdAsync(ID id);
void DeleteMany(Expression<Func<T, bool>> filterExpression);
Task DeleteManyAsync(Expression<Func<T, bool>> filterExpression);
}
Coming to the Infrastructure layer, if I understand correctly the Clean Architecture paradigm, I should place at this level implementations of Repositories interfaces. Unlike the Domain layer, at this level I can and should code implementations tied to specific technologies and databases. So I added this implementation.
public class MongoRepository<TEntity> : ICrudRepository<TEntity, ObjectId> where TEntity : class, IMongoEntity
{
private readonly IMongoCollection<TEntity> _collection;
public MongoRepository(IMongoCollection<TEntity> collection)
{
// TODO: Settings from ENV Variables in Docker Container
_collection = collection;
}
public virtual void InsertOne(TEntity entity) => _collection.InsertOne(entity);
public virtual async Task InsertOneAsync(TEntity entity) => await _collection.InsertOneAsync(entity);
public void InsertMany(IEnumerable<TEntity> entities) => _collection.InsertMany(entities);
public virtual async Task InsertManyAsync(IEnumerable<TEntity> entities) => await _collection.InsertManyAsync(entities);
public void ReplaceOne(TEntity entity) => _collection.FindOneAndReplace(Builders<TEntity>.Filter.Eq(previous => previous.Id, entity.Id), entity);
public virtual async Task ReplaceOneAsync(TEntity entity)
=> await _collection.FindOneAndReplaceAsync(Builders<TEntity>.Filter.Eq(previous => previous.Id, entity.Id), entity);
public void DeleteOne(Expression<Func<TEntity, bool>> filterExpression) => _collection.FindOneAndDelete(filterExpression);
public async Task DeleteOneAsync(Expression<Func<TEntity, bool>> filterExpression) => await _collection.FindOneAndDeleteAsync(filterExpression);
public void DeleteById(ObjectId id) => _collection.FindOneAndDelete(Builders<TEntity>.Filter.Eq(entity => entity.Id, id));
public async Task DeleteByIdAsync(ObjectId id) => await _collection.FindOneAndDeleteAsync(Builders<TEntity>.Filter.Eq(entity => entity.Id, id));
public void DeleteMany(Expression<Func<TEntity, bool>> filterExpression) => _collection.DeleteMany(filterExpression);
public async Task DeleteManyAsync(Expression<Func<TEntity, bool>> filterExpression) => await _collection.DeleteManyAsync(filterExpression);
}
As you can see, this implementation references an interface named IMongoEntity.
public interface IMongoEntity : IEntity
{
[BsonId]
[BsonRepresentation(BsonType.String)]
ObjectId Id { get; set; }
}
This interface extends the Domain layer generic IEntity, and it is currently defined in the Infrastructure layer. The reason is that this kind of entity is tied to a specific Database, for instance MongoDB. At the same time, I had to define it in this set of libraries beacuse multiple projects might reference the library and all should implement IMongoEntity (if they have to query MongoDB, of course). One final consideration, and the reason behind this question, is that in the current state an example project should have Domain layer referencing the library's Infrastructure layer. I don't think it's a good idea, but still I could not find a solution so far.
TLDR: If I have a generic Entity at the Domain layer, what is the correct way in Clean Architecture to implement it for specific Databases? At which layer should I place these implementations?