From 32eee8dbe424c3df304fc243c72bad4ef1382881 Mon Sep 17 00:00:00 2001 From: Havret Date: Tue, 31 Dec 2024 16:47:26 +0100 Subject: [PATCH] Introduce keyed service resolution for RocksDb stores --- README.md | 9 +++ src/RocksDb.Extensions/IRocksDbBuilder.cs | 10 ++- .../RocksDb.Extensions.csproj | 2 +- src/RocksDb.Extensions/RocksDbBuilder.cs | 8 +- .../KeyedStoreTests.cs | 80 +++++++++++++++++++ .../RocksDb.Extensions.Tests.csproj | 2 +- .../Utils/TestFixture.cs | 2 + 7 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 test/RocksDb.Extensions.Tests/KeyedStoreTests.cs diff --git a/README.md b/README.md index 451547f..2e9f23e 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,15 @@ rocksDbBuilder.AddStore("users-store"); This registers an instance of `UsersStore` with RocksDb under the name "users-store". +#### Keyed Service Resolution +You can also resolve your store as a keyed service using the column family name: + +```csharp +var usersStore = serviceProvider.GetRequiredKeyedService("users-store"); +``` + +This approach allows you to register and retrieve multiple stores of the same type, each differentiated by their column family name. + ### Use your store Once you have registered your store, you can use it to add, get, and remove data from RocksDb. For example: diff --git a/src/RocksDb.Extensions/IRocksDbBuilder.cs b/src/RocksDb.Extensions/IRocksDbBuilder.cs index 2523c51..7d1124c 100644 --- a/src/RocksDb.Extensions/IRocksDbBuilder.cs +++ b/src/RocksDb.Extensions/IRocksDbBuilder.cs @@ -8,14 +8,18 @@ public interface IRocksDbBuilder /// /// Adds a RocksDB store to the builder for the specified column family. /// - /// + /// The name of the column family to associate with the store. /// The type of the store's key. /// The type of the store's value. /// The type of the store to add. - /// The builder instance for method chaining + /// The builder instance for method chaining. + /// Thrown if the specified column family is already registered. /// /// The type must be a concrete implementation of the abstract class - /// . + /// . Each store is registered uniquely based on its column family name. + /// + /// Stores can also be resolved as keyed services using their associated column family name. + /// Use GetRequiredKeyedService<TStore>(columnFamily) to retrieve a specific store instance. /// IRocksDbBuilder AddStore(string columnFamily) where TStore : RocksDbStore; } \ No newline at end of file diff --git a/src/RocksDb.Extensions/RocksDb.Extensions.csproj b/src/RocksDb.Extensions/RocksDb.Extensions.csproj index 3a0df8f..18ac99e 100644 --- a/src/RocksDb.Extensions/RocksDb.Extensions.csproj +++ b/src/RocksDb.Extensions/RocksDb.Extensions.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/RocksDb.Extensions/RocksDbBuilder.cs b/src/RocksDb.Extensions/RocksDbBuilder.cs index 0d38ef9..46d22dc 100644 --- a/src/RocksDb.Extensions/RocksDbBuilder.cs +++ b/src/RocksDb.Extensions/RocksDbBuilder.cs @@ -1,5 +1,6 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; namespace RocksDb.Extensions; @@ -22,8 +23,8 @@ public IRocksDbBuilder AddStore(string columnFamily) where } _ = _serviceCollection.Configure(options => { options.ColumnFamilies.Add(columnFamily); }); - - _ = _serviceCollection.AddSingleton(provider => + + _serviceCollection.AddKeyedSingleton(columnFamily, (provider, _) => { var rocksDbContext = provider.GetRequiredService(); var columnFamilyHandle = rocksDbContext.Db.GetColumnFamily(columnFamily); @@ -38,6 +39,9 @@ public IRocksDbBuilder AddStore(string columnFamily) where ); return ActivatorUtilities.CreateInstance(provider, rocksDbAccessor); }); + + _serviceCollection.TryAddSingleton(typeof(TStore), provider => provider.GetRequiredKeyedService(columnFamily)); + return this; } diff --git a/test/RocksDb.Extensions.Tests/KeyedStoreTests.cs b/test/RocksDb.Extensions.Tests/KeyedStoreTests.cs new file mode 100644 index 0000000..fc4606d --- /dev/null +++ b/test/RocksDb.Extensions.Tests/KeyedStoreTests.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using RocksDb.Extensions.Protobuf; +using RocksDb.Extensions.Tests.Utils; + +namespace RocksDb.Extensions.Tests; + +public class KeyedStoreTests +{ + [Test] + public void should_resolve_rocksdb_stores_as_keyed_services_when_registered_under_different_column_names() + { + // Arrange + using var testFixture = TestFixture.Create(rockDb => + { + _ = rockDb.AddStore>("my-store-1"); + _ = rockDb.AddStore>("my-store-2"); + }, options => + { + options.SerializerFactories.Clear(); + options.SerializerFactories.Add(new ProtobufSerializerFactory()); + }); + + // Act + var store1 = testFixture.ServiceProvider.GetRequiredKeyedService>("my-store-1"); + var store2 = testFixture.ServiceProvider.GetRequiredKeyedService>("my-store-2"); + + // Assert + Assert.Multiple(() => + { + Assert.That(store1, Is.Not.Null); + Assert.That(store2, Is.Not.Null); + Assert.That(ReferenceEquals(store1, store2), Is.False); + }); + } + + [Test] + public void should_throw_when_resolving_non_existent_store() + { + // Arrange + using var testFixture = TestFixture.Create(rockDb => + { + _ = rockDb.AddStore>("my-store-1"); + }, options => + { + options.SerializerFactories.Clear(); + options.SerializerFactories.Add(new ProtobufSerializerFactory()); + }); + + // Act & Assert + Assert.Throws(() => testFixture.ServiceProvider.GetRequiredKeyedService>("non-existent-store")); + } + + [Test] + public void should_resolve_default_store_as_first_registered_keyed_service() + { + // Arrange + using var testFixture = TestFixture.Create(rockDb => + { + _ = rockDb.AddStore>("my-store-1"); + _ = rockDb.AddStore>("my-store-2"); + }, options => + { + options.SerializerFactories.Clear(); + options.SerializerFactories.Add(new ProtobufSerializerFactory()); + }); + + // Act + var store1 = testFixture.ServiceProvider.GetRequiredKeyedService>("my-store-1"); + var store2 = testFixture.ServiceProvider.GetRequiredKeyedService>("my-store-2"); + var store = testFixture.ServiceProvider.GetRequiredService>(); + + // Assert + Assert.Multiple(() => + { + Assert.That(ReferenceEquals(store1, store), Is.True); + Assert.That(ReferenceEquals(store2, store), Is.False); + }); + } +} \ No newline at end of file diff --git a/test/RocksDb.Extensions.Tests/RocksDb.Extensions.Tests.csproj b/test/RocksDb.Extensions.Tests/RocksDb.Extensions.Tests.csproj index bcaa74a..f518a20 100644 --- a/test/RocksDb.Extensions.Tests/RocksDb.Extensions.Tests.csproj +++ b/test/RocksDb.Extensions.Tests/RocksDb.Extensions.Tests.csproj @@ -14,7 +14,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/RocksDb.Extensions.Tests/Utils/TestFixture.cs b/test/RocksDb.Extensions.Tests/Utils/TestFixture.cs index af0054d..f4f1a92 100644 --- a/test/RocksDb.Extensions.Tests/Utils/TestFixture.cs +++ b/test/RocksDb.Extensions.Tests/Utils/TestFixture.cs @@ -32,6 +32,8 @@ public T GetStore() where T : notnull return _serviceProvider.GetRequiredService(); } + public IServiceProvider ServiceProvider => _serviceProvider; + public void Dispose() { _serviceProvider.Dispose();