Browse Source

feat(mongodb): Add MongoClientFactory to reuse MongoClient per connection string"

pull/23007/head
Mustafa Ozan Sindi 1 year ago
parent
commit
a4b32d4190
  1. 5
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs
  2. 9
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs
  3. 32
      framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs
  4. 18
      framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs
  5. 82
      framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs

5
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs

@ -8,6 +8,7 @@ using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Domain.Repositories.MongoDB;
using Volo.Abp.Modularity;
using Volo.Abp.MongoDB.Clients;
using Volo.Abp.MongoDB.DependencyInjection;
using Volo.Abp.Uow.MongoDB;
using Volo.Abp.MongoDB.DistributedEvents;
@ -30,6 +31,8 @@ public class AbpMongoDbModule : AbpModule
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddSingleton<IMongoClientFactory, MongoClientFactory>();
context.Services.TryAddTransient(
typeof(IMongoDbContextProvider<>),
typeof(UnitOfWorkMongoDbContextProvider<>)
@ -61,4 +64,4 @@ public class AbpMongoDbModule : AbpModule
options.IgnoredEventSelectors.Add<IncomingEventRecord>();
});
}
}
}

9
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/IMongoClientFactory.cs

@ -0,0 +1,9 @@
using MongoDB.Driver;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.MongoDB.Clients;
public interface IMongoClientFactory : ISingletonDependency
{
MongoClient GetClient(string connectionString);
}

32
framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/Clients/MongoClientFactory.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
namespace Volo.Abp.MongoDB.Clients;
public class MongoClientFactory : IMongoClientFactory
{
private readonly ConcurrentDictionary<string, MongoClient> _clients = new();
private readonly AbpMongoDbContextOptions Options;
public MongoClientFactory(IOptions<AbpMongoDbContextOptions> options)
{
Options = options.Value;
}
public MongoClient GetClient(string connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new ArgumentException("Connection string must not be null or empty.", nameof(connectionString));
}
return _clients.GetOrAdd(connectionString, cs =>
{
var mongoClientSettings = MongoClientSettings.FromUrl(new MongoUrl(cs));
Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings);
return new MongoClient(mongoClientSettings);
});
}
}

18
framework/src/Volo.Abp.MongoDB/Volo/Abp/Uow/MongoDB/UnitOfWorkMongoDbContextProvider.cs

@ -9,6 +9,7 @@ using MongoDB.Bson;
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.Clients;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
@ -26,6 +27,7 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
protected readonly ICurrentTenant CurrentTenant;
protected readonly AbpMongoDbContextOptions Options;
protected readonly IMongoDbContextTypeProvider DbContextTypeProvider;
protected readonly IMongoClientFactory MongoClientFactory;
public UnitOfWorkMongoDbContextProvider(
IUnitOfWorkManager unitOfWorkManager,
@ -33,13 +35,14 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant,
IOptions<AbpMongoDbContextOptions> options,
IMongoDbContextTypeProvider dbContextTypeProvider)
IMongoDbContextTypeProvider dbContextTypeProvider, IMongoClientFactory mongoClientFactory)
{
UnitOfWorkManager = unitOfWorkManager;
ConnectionStringResolver = connectionStringResolver;
CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant;
DbContextTypeProvider = dbContextTypeProvider;
MongoClientFactory = mongoClientFactory;
Options = options.Value;
Logger = NullLogger<UnitOfWorkMongoDbContextProvider<TMongoDbContext>>.Instance;
@ -77,12 +80,11 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
databaseName = ConnectionStringNameAttribute.GetConnStringName(targetDbContextType);
}
//TODO: Create only single MongoDbClient per connection string in an application (extract MongoClientCache for example).
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
() => new MongoDbDatabaseApi(CreateDbContext(unitOfWork, mongoUrl, databaseName)));
return (TMongoDbContext)((MongoDbDatabaseApi) databaseApi).DbContext;
return (TMongoDbContext)((MongoDbDatabaseApi)databaseApi).DbContext;
}
public virtual async Task<TMongoDbContext> GetDbContextAsync(CancellationToken cancellationToken = default)
@ -121,11 +123,10 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
}
return (TMongoDbContext)((MongoDbDatabaseApi) databaseApi).DbContext;
return (TMongoDbContext)((MongoDbDatabaseApi)databaseApi).DbContext;
}
[Obsolete("Use CreateDbContextAsync")]
private TMongoDbContext CreateDbContext(IUnitOfWork unitOfWork, MongoUrl mongoUrl, string databaseName)
{
var client = CreateMongoClient(mongoUrl);
@ -299,14 +300,11 @@ public class UnitOfWorkMongoDbContextProvider<TMongoDbContext> : IMongoDbContext
protected virtual MongoClient CreateMongoClient(MongoUrl mongoUrl)
{
var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl);
Options.MongoClientSettingsConfigurer?.Invoke(mongoClientSettings);
return new MongoClient(mongoClientSettings);
return MongoClientFactory.GetClient(mongoUrl.ToString());
}
protected virtual CancellationToken GetCancellationToken(CancellationToken preferredValue = default)
{
return CancellationTokenProvider.FallbackToProvider(preferredValue);
}
}
}

82
framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/Clients/MongoClient_Factory_Tests.cs

@ -0,0 +1,82 @@
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Volo.Abp.TestApp.Testing;
using Xunit;
namespace Volo.Abp.MongoDB.Clients;
[Collection(MongoTestCollection.Name)]
public class MongoClient_Factory_Tests : MongoDbTestBase
{
private readonly IMongoClientFactory _factory;
public MongoClient_Factory_Tests()
{
_factory = GetRequiredService<IMongoClientFactory>();
}
[Fact]
public void Should_Return_Same_Instance_For_Same_ConnectionString()
{
// Arrange
var connectionString = "mongodb://localhost:27017/my-db";
// Act
var client1 = _factory.GetClient(connectionString);
var client2 = _factory.GetClient(connectionString);
// Assert
Assert.Same(client1, client2);
}
[Fact]
public void Should_Return_Different_Instances_For_Different_ConnectionStrings()
{
// Arrange
var cs1 = "mongodb://localhost:27017/db1";
var cs2 = "mongodb://localhost:27017/db2";
// Act
var client1 = _factory.GetClient(cs1);
var client2 = _factory.GetClient(cs2);
// Assert
Assert.NotSame(client1, client2);
}
[Fact]
public void Should_Not_Throw_For_Valid_But_Unreachable_Connection()
{
// Arrange
var cs = "mongodb://unreachablehost:12345/any";
// Act
var client = _factory.GetClient(cs);
// Assert
Assert.NotNull(client); // Even though it's not connectable now, the instance can be created
}
[Fact]
public void Should_Be_ThreadSafe_When_Accessed_Concurrently()
{
var connectionString = "mongodb://localhost:27017/threadsafe";
MongoClient[] results = new MongoClient[100];
Parallel.For(0, 100, i =>
{
results[i] = _factory.GetClient(connectionString);
});
Assert.All(results, client => Assert.Same(results[0], client));
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void Should_Throw_If_ConnectionString_Is_Null_Or_Empty(string connectionString)
{
Assert.Throws<ArgumentException>(() => _factory.GetClient(connectionString));
}
}
Loading…
Cancel
Save