Skip to content

Commit

Permalink
Merge pull request #31 from deveel/30-tenant-resolver-in-repository-p…
Browse files Browse the repository at this point in the history
…rovider-contexts

Finbuckle Tenant Resolver Service
  • Loading branch information
tsutomi authored Nov 2, 2023
2 parents 851f273 + a879113 commit 941e7f5
Show file tree
Hide file tree
Showing 21 changed files with 406 additions and 270 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/clean-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ jobs:
package-type: 'nuget'
min-versions-to-keep: 10
delete-only-pre-release-versions: "true"

- name: Remove the Old Deveel.Repository.Finbuckle.MultiTenant Package
uses: actions/delete-package-versions@v4
with:
package-name: 'Deveel.Repository.Finbuckle.MultiTenant'
package-type: 'nuget'
min-versions-to-keep: 10
delete-only-pre-release-versions: "true"
7 changes: 7 additions & 0 deletions Deveel.Repository.sln
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.Manager.E
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Deveel.Repository.Manager.EasyCaching.XUnit", "test\Deveel.Repository.Manager.EasyCaching.XUnit\Deveel.Repository.Manager.EasyCaching.XUnit.csproj", "{F47C196B-758D-4C05-8918-FB63C61649EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deveel.Repository.Finbuckle.MultiTenant", "src\Deveel.Repository.Finbuckle.MultiTenant\Deveel.Repository.Finbuckle.MultiTenant.csproj", "{69E01D54-C430-4A1E-989E-5A61AB4C5B54}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -126,6 +128,10 @@ Global
{F47C196B-758D-4C05-8918-FB63C61649EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47C196B-758D-4C05-8918-FB63C61649EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47C196B-758D-4C05-8918-FB63C61649EB}.Release|Any CPU.Build.0 = Release|Any CPU
{69E01D54-C430-4A1E-989E-5A61AB4C5B54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{69E01D54-C430-4A1E-989E-5A61AB4C5B54}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69E01D54-C430-4A1E-989E-5A61AB4C5B54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69E01D54-C430-4A1E-989E-5A61AB4C5B54}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -148,6 +154,7 @@ Global
{638851EF-B000-490C-9035-A962279A3E9B} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
{84D55BE2-7DAD-4CA3-A3E2-EFB4D41FA3CA} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
{F47C196B-758D-4C05-8918-FB63C61649EB} = {50434E05-0F21-4871-AFB3-A483CEE4A300}
{69E01D54-C430-4A1E-989E-5A61AB4C5B54} = {2860FD4D-510F-43C8-870E-5559B90D0CAD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {01FD9B16-84B3-4D99-80C1-11B2F3D65B56}
Expand Down
25 changes: 24 additions & 1 deletion src/Deveel.Repository.Core/Data/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,30 @@ public static IServiceCollection AddRepository(this IServiceCollection services,
return services;
}

public static IServiceCollection AddRepositoryProvider<TProvider>(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
/// <summary>
/// Registers a repository of the given type in the service collection.
/// </summary>
/// <typeparam name="TProvider">
/// The type of the repository to register.
/// </typeparam>
/// <param name="services">
/// The service collection to register the repository.
/// </param>
/// <param name="lifetime">
/// the lifetime of the repository in the service collection.
/// </param>
/// <returns>
/// Returns the same <see cref="IServiceCollection"/> to allow chaining.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when the given <typeparamref name="TProvider"/> is not
/// a class or is abstract.
/// </exception>
/// <exception cref="RepositoryException">
/// Thrown when the given <typeparamref name="TProvider"/> is not a valid
/// repository type.
/// </exception>
public static IServiceCollection AddRepositoryProvider<TProvider>(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
where TProvider : class
=> services.AddRepositoryProvider(typeof(TProvider), lifetime);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,10 @@ namespace Deveel.Data {
/// <typeparam name="TEntity">
/// The type of the entity that is managed by the repository.
/// </typeparam>
/// <typeparam name="TTenantInfo">
/// The type of the tenant information that is used to create the context.
/// </typeparam>
public abstract class EntityRepositoryProviderBase<TContext, TEntity, TTenantInfo> : IDisposable
public abstract class EntityRepositoryProviderBase<TContext, TEntity> : IDisposable
where TContext : DbContext
where TEntity : class
where TTenantInfo : class, ITenantInfo, new() {
private readonly IEnumerable<IMultiTenantStore<TTenantInfo>> tenantStores;
where TEntity : class {
private readonly IRepositoryTenantResolver tenantResolver;
private readonly IDbContextOptionsFactory<TContext> optionsFactory;
private readonly ILoggerFactory loggerFactory;

Expand All @@ -49,18 +45,18 @@ public abstract class EntityRepositoryProviderBase<TContext, TEntity, TTenantInf

internal EntityRepositoryProviderBase(
IDbContextOptionsFactory<TContext> optionsFactory,
IEnumerable<IMultiTenantStore<TTenantInfo>> tenantStores,
IRepositoryTenantResolver tenantResolver,
ILoggerFactory? loggerFactory = null) {
this.optionsFactory = optionsFactory ?? throw new ArgumentNullException(nameof(optionsFactory));
this.tenantStores = tenantStores;
this.tenantResolver = tenantResolver;
this.loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
}

internal EntityRepositoryProviderBase(
DbContextOptions<TContext> options,
IEnumerable<IMultiTenantStore<TTenantInfo>> tenantStores,
IRepositoryTenantResolver tenantResolver,
ILoggerFactory? loggerFactory = null)
: this(new SingletonDbContextOptionsFactory(options), tenantStores, loggerFactory) {
: this(new SingletonDbContextOptionsFactory(options), tenantResolver, loggerFactory) {
}

/// <summary>
Expand All @@ -70,23 +66,17 @@ internal EntityRepositoryProviderBase(
Dispose(disposing: false);
}

internal async Task<TRepository> GetRepositoryAsync<TRepository>(string tenantId) {
internal async Task<TRepository> GetRepositoryAsync<TRepository>(string tenantId, CancellationToken cancellationToken = default) {
// TODO: move the cache here ...

foreach (var store in tenantStores) {
var tenant = await store.TryGetAsync(tenantId);

if (tenant == null)
tenant = await store.TryGetByIdentifierAsync(tenantId);

if (tenant != null)
return CreateRepository<TRepository>(tenant);
}
var tenant = await tenantResolver.FindTenantAsync(tenantId, cancellationToken);
if (tenant == null)
throw new RepositoryException($"The tenant '{tenantId}' was not found");

throw new RepositoryException($"The tenant '{tenantId}' was not found");
return CreateRepository<TRepository>(tenant);
}

private TContext ConstructDbContext(TTenantInfo tenantInfo) {
private TContext ConstructDbContext(ITenantInfo tenantInfo) {
if (contexts == null || !contexts.TryGetValue(tenantInfo.Id!, out var dbContext)) {
var options = optionsFactory.Create(tenantInfo);
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
Expand Down Expand Up @@ -116,7 +106,7 @@ private TContext ConstructDbContext(TTenantInfo tenantInfo) {
return dbContext;
}

private TRepository CreateRepository<TRepository>(TTenantInfo tenant) {
private TRepository CreateRepository<TRepository>(ITenantInfo tenant) {
try {
if (repositories == null || !repositories.TryGetValue(tenant.Id!, out var repository)) {
var dbContext = ConstructDbContext(tenant);
Expand All @@ -137,7 +127,7 @@ private TRepository CreateRepository<TRepository>(TTenantInfo tenant) {
}
}

internal abstract object CreateRepositoryInternal(TContext context, TTenantInfo tenant);
internal abstract object CreateRepositoryInternal(TContext context, ITenantInfo tenant);

/// <summary>
/// Creates a logger for a repository.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,13 @@ namespace Deveel.Data {
/// <typeparam name="TEntity">
/// The type of entity managed by the repository
/// </typeparam>
/// <typeparam name="TKey">
/// The type of the key of the entity managed by the repository.
/// </typeparam>
/// <typeparam name="TContext">
/// The type of <see cref="DbContext"/> used to manage the entities
/// </typeparam>
/// <typeparam name="TTenantInfo">
/// The type of the tenant that is is ued to resolve the context
/// of the tenant.
/// </typeparam>
public class EntityRepositoryProvider<TContext, TEntity, TKey, TTenantInfo> : EntityRepositoryProviderBase<TContext, TEntity, TTenantInfo>,
IRepositoryProvider<TEntity, TKey>
where TContext : DbContext
where TEntity : class
where TTenantInfo : class, ITenantInfo, new() {
public class EntityRepositoryProvider<TContext, TEntity> : EntityRepositoryProviderBase<TContext, TEntity>,
IRepositoryProvider<TEntity>
where TContext : DbContext
where TEntity : class {

/// <summary>
/// Constructs the provider with the given options factory
Expand All @@ -54,17 +46,17 @@ public class EntityRepositoryProvider<TContext, TEntity, TKey, TTenantInfo> : En
/// A service that is used to create the options for the context
/// and a specific tenant.
/// </param>
/// <param name="tenantStores">
/// A list of the available stores to retrieve the tenants from.
/// <param name="tenantResolver">
/// A service that is used to resolve the tenant for a given context.
/// </param>
/// <param name="loggerFactory">
/// A factory to create loggers for the repositories.
/// </param>
public EntityRepositoryProvider(
public EntityRepositoryProvider(
IDbContextOptionsFactory<TContext> optionsFactory,
IEnumerable<IMultiTenantStore<TTenantInfo>> tenantStores,
ILoggerFactory? loggerFactory = null) : base(optionsFactory, tenantStores, loggerFactory) {
}
IRepositoryTenantResolver tenantResolver,
ILoggerFactory? loggerFactory = null) : base(optionsFactory, tenantResolver, loggerFactory) {
}

/// <summary>
/// Constructs the provider with the given options and
Expand All @@ -74,17 +66,17 @@ public EntityRepositoryProvider(
/// The instance of options that are used to create the context
/// for the tenants.
/// </param>
/// <param name="tenantStores">
/// The list of stores to retrieve the tenants from.
/// <param name="tenantResolver">
/// A service that is used to resolve the tenant for a given context.
/// </param>
/// <param name="loggerFactory">
/// A factory to create loggers for the repositories.
/// </param>
public EntityRepositoryProvider(
DbContextOptions<TContext> options,
IEnumerable<IMultiTenantStore<TTenantInfo>> tenantStores,
ILoggerFactory? loggerFactory = null)
: base(options, tenantStores, loggerFactory) {
IRepositoryTenantResolver tenantResolver,
ILoggerFactory? loggerFactory = null)
: base(options, tenantResolver, loggerFactory) {

}

Expand All @@ -105,11 +97,14 @@ public EntityRepositoryProvider(
/// Thrown when the tenant was not found or the repository could not be
/// constructed with the given tenant.
/// </exception>
protected virtual async Task<EntityRepository<TEntity, TKey>> GetRepositoryAsync(string tenantId, CancellationToken cancellationToken = default) {
return await base.GetRepositoryAsync<EntityRepository<TEntity, TKey>>(tenantId);
}
protected virtual async Task<EntityRepository<TEntity>> GetRepositoryAsync(string tenantId, CancellationToken cancellationToken = default) {
return await base.GetRepositoryAsync<EntityRepository<TEntity>>(tenantId);
}

async Task<IRepository<TEntity>> IRepositoryProvider<TEntity>.GetRepositoryAsync(string tenantId, CancellationToken cancellationToken)
=> await GetRepositoryAsync(tenantId, cancellationToken);

async Task<IRepository<TEntity, TKey>> IRepositoryProvider<TEntity, TKey>.GetRepositoryAsync(string tenantId, CancellationToken cancellationToken)
async Task<IRepository<TEntity, object>> IRepositoryProvider<TEntity, object>.GetRepositoryAsync(string tenantId, CancellationToken cancellationToken)
=> await GetRepositoryAsync(tenantId, cancellationToken);

/// <summary>
Expand All @@ -120,11 +115,11 @@ async Task<IRepository<TEntity, TKey>> IRepositoryProvider<TEntity, TKey>.GetRep
/// </param>
/// <param name="tenantInfo"></param>
/// <returns></returns>
protected virtual EntityRepository<TEntity, TKey> CreateRepository(TContext dbContext, ITenantInfo tenantInfo) {
var logger = CreateLogger();
return new EntityRepository<TEntity, TKey>(dbContext, tenantInfo, logger);
}
protected virtual EntityRepository<TEntity> CreateRepository(TContext dbContext, ITenantInfo tenantInfo) {
var logger = base.CreateLogger();
return new EntityRepository<TEntity>(dbContext, tenantInfo, logger);
}

internal override object CreateRepositoryInternal(TContext context, TTenantInfo tenant) => CreateRepository(context, tenant);
internal override object CreateRepositoryInternal(TContext context, ITenantInfo tenant) => CreateRepository(context, tenant);
}
}
Loading

0 comments on commit 941e7f5

Please sign in to comment.