Skip to content

Latest commit

 

History

History

repository-implementations

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Repository Implementations

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

Data SourceLibrary
In-MemoryNuget
Entity FrameworkNuget
MongoDBNuget

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

using System;
using Deveel.Data;

namespace Foo {
    public class MongoData : IData {
        public ObjectId? Id { get; }
        string? IData.Id => Id?.ToString();
        public string ContentType { get; set; }
        public byte[] Content { get; set; }
    }
    
    public class MongoDataRepository : MongoRepository<MongoData>, IDataRepository<MongoData> {
        public MongoDataRepository(IMongoDbContext context)
            : base(context) {
        }
        
        public Task SetContenAsync(MongoData data, string contentType, byte[] content, CancellationToken cancellationToken = default) {
            data.ContentType = contentType;
            data.Content = content;
            return Task.CompletedTask;
        }
        
        public Task<byte[]> GetContentAsync(MongoData data, CancellationToken cancellationToken = default) {
            return Task.FromResult(data.Content);
        }
        
         public Task<string> GetContentTypeAsync(MongoData data, CancellationToken cancellationToken = default) {
            return Task.FromResult(data.ContentType);
        }
    }
}

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

using System;
using Deveel.Data;

namespace Foo {
    public class DbData : IData {
        public Guid? Id { get; }
        string? IData.Id => Id?.ToString();
        public string ContentType { get; set; }
        public string Content { get; set; }
        byte[] IData.Content => Convert.FromBase64String(Content);
    }
    
    public class EntityDataRepository : EntityRepository<MongoData>, IDataRepository<MongoData> {
        public EntityDataRepository(DataDbContext context)
            : base(context) {
        }
        
        public Task SetContenAsync(DbData data, string contentType, byte[] content, CancellationToken cancellationToken = default) {
            data.ContentType = contentType;
            data.Content = Convert.ToBase64(Encoding.UTF8.GetString(content));
            return Task.CompletedTask;
        }
        
        public Task<byte[]> GetContentAsync(DbData data, CancellationToken cancellationToken = default) {
            return Task.FromResult(Convert.FromBase64(Encoding.UTF8.GetBytes(data.Content));
        }
        
         public Task<string> GetContentTypeAsync(MongoData data, CancellationToken cancellationToken = default) {
            return Task.FromResult(data.ContentType);
        }
    }
}