githubEdit

Repository Implementations

The Deveel Repository framework comes with some implementations of the Repository pattern.

Abstraction Patterns

One of the benefits of using a Repository pattern is the abstraction of the data access mechanism, which allows the implementation of a single paradigm for the management of the data, porting it to the various data storage sources, following the Liskov substitution principle.

For example. the EntityManager<TEntity> class uses this approach, implementing a superset of functions on top of the IRepository<TEntity> abstraction.

This allows to switch between implementations of IRepository<TEntity>, without affecting the behavior of the consuming class.

Business Data Logic

A design pattern that I recommend is the separation of the overall data logic from the concrete implementation, especially in scenarios of reuse (eg. NuGet libraries).

For example:

Foo.Service.dll

using System;
using Deveel.Data;

namespace Foo {
    public interface IData {
        string? Id { get; }
        byte[] Content { get; }
        string ContentType { get; }
    }
    
    public interface IDataRepository<TData> : IRepository<TData> where TData : class, IData {
        Task<string> GetContentTypeAsync(TData data, CancellationToken cancellationToken = default);
        Task<byte[]> GetContentAsync(TData data, CancellationToken cancellationToken = default);
        Task SetContentAsync(TData data, string contentType, byte[] content, CancellationTyoken cancellationToken = default);
    }
    
    public class DataManager<TData> : EntityManager<TData> where TData : class, IData {
        public DataManager(IDataRepository<TData> repository, IEntityValidator<TData>? validator = null, IServiceProvider? services = null. ILoggerFactory? loggerFactory = null)
            : base(repository, validator, null, null, services, loggerFactory) {
        }
        
        protected IDataRepository<TData> DataRepository => (IDataRepository<TData>)base.Repository;
        
        public Task<OperationResult> SetContentAsync(TData data, string contentType, byte[] content, CancellationToken? cancellationToken = null) {
            ThrowIfDisposed();
            
            try {
                var existingContentType = await DataRepository.GetContentTypeAsync(data, GetCancellationToken(cancellationToken));
                var existingContent = await DataRepository.GetContentAsync(data, GetCancellationToken(cancellationToken));
                if ((existingContentType != null && existingContentType == contentType) &&
                    (existingContent != null && existingContent == content)) {
                    return OperationResult.NotModified;
                }
                
                await DataRepository.SetContentAsync(data, contentType, content, GetCancellationToken(cancellationToken);
                
                // this will invoke the validation before invoking the 
                // Update method of the repository
                return await UpdateAsync(data);
            } catch (Exception ex) {
                Logger.LogError(ex, "Could not set the content");
                return Fail("DATA_ERROR");
            }
        }
    }
}

While in the Foo.Service.MongoDb.dll you can implement a MongoDB-specific access logic

... and in Foo.Service.EF implement the same logic using EntityFramework Core

Last updated