Skip to content

Commit

Permalink
Introduce keyed service resolution for RocksDb stores
Browse files Browse the repository at this point in the history
  • Loading branch information
Havret committed Dec 31, 2024
1 parent a03c279 commit 88764a8
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 7 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ rocksDbBuilder.AddStore<string, User, UsersStore>("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<UsersStore>("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:
Expand Down
10 changes: 7 additions & 3 deletions src/RocksDb.Extensions/IRocksDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ public interface IRocksDbBuilder
/// <summary>
/// Adds a RocksDB store to the builder for the specified column family.
/// </summary>
/// <param name="columnFamily"></param>
/// <param name="columnFamily">The name of the column family to associate with the store.</param>
/// <typeparam name="TKey">The type of the store's key.</typeparam>
/// <typeparam name="TValue">The type of the store's value.</typeparam>
/// <typeparam name="TStore">The type of the store to add.</typeparam>
/// <returns>The builder instance for method chaining</returns>
/// <returns>The builder instance for method chaining.</returns>
/// <exception cref="InvalidOperationException">Thrown if the specified column family is already registered.</exception>
/// <remarks>
/// The <typeparamref name="TStore"/> type must be a concrete implementation of the abstract class
/// <see cref="RocksDbStore{TKey,TValue}"/>.
/// <see cref="RocksDbStore{TKey,TValue}"/>. 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 <c>GetRequiredKeyedService&lt;TStore&gt;(columnFamily)</c> to retrieve a specific store instance.
/// </remarks>
IRocksDbBuilder AddStore<TKey, TValue, TStore>(string columnFamily) where TStore : RocksDbStore<TKey, TValue>;
}
2 changes: 1 addition & 1 deletion src/RocksDb.Extensions/RocksDb.Extensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

<ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="RocksDB" Version="8.11.3.46984" />
</ItemGroup>
Expand Down
8 changes: 6 additions & 2 deletions src/RocksDb.Extensions/RocksDbBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace RocksDb.Extensions;
Expand All @@ -22,8 +23,8 @@ public IRocksDbBuilder AddStore<TKey, TValue, TStore>(string columnFamily) where
}

_ = _serviceCollection.Configure<RocksDbOptions>(options => { options.ColumnFamilies.Add(columnFamily); });

_ = _serviceCollection.AddSingleton(provider =>
_serviceCollection.AddKeyedSingleton<TStore>(columnFamily, (provider, _) =>
{
var rocksDbContext = provider.GetRequiredService<RocksDbContext>();
var columnFamilyHandle = rocksDbContext.Db.GetColumnFamily(columnFamily);
Expand All @@ -38,6 +39,9 @@ public IRocksDbBuilder AddStore<TKey, TValue, TStore>(string columnFamily) where
);
return ActivatorUtilities.CreateInstance<TStore>(provider, rocksDbAccessor);
});

_serviceCollection.TryAddSingleton(typeof(TStore), provider => provider.GetRequiredKeyedService<TStore>(columnFamily));

return this;
}

Expand Down
80 changes: 80 additions & 0 deletions test/RocksDb.Extensions.Tests/KeyedStoreTests.cs
Original file line number Diff line number Diff line change
@@ -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_store_as_keyed_service_when_registered_under_different_column_names()
{
// Arrange
using var testFixture = TestFixture.Create(rockDb =>
{
_ = rockDb.AddStore<CacheKey, CacheValue, RocksDbGenericStore<CacheKey, CacheValue>>("my-store-1");
_ = rockDb.AddStore<CacheKey, CacheValue, RocksDbGenericStore<CacheKey, CacheValue>>("my-store-2");
}, options =>
{
options.SerializerFactories.Clear();
options.SerializerFactories.Add(new ProtobufSerializerFactory());
});

// Act
var store1 = testFixture.ServiceProvider.GetRequiredKeyedService<RocksDbGenericStore<CacheKey, CacheValue>>("my-store-1");
var store2 = testFixture.ServiceProvider.GetRequiredKeyedService<RocksDbGenericStore<CacheKey, CacheValue>>("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<CacheKey, CacheValue, RocksDbGenericStore<CacheKey, CacheValue>>("my-store-1");
}, options =>
{
options.SerializerFactories.Clear();
options.SerializerFactories.Add(new ProtobufSerializerFactory());
});

// Act & Assert
Assert.Throws<InvalidOperationException>(() => testFixture.ServiceProvider.GetRequiredKeyedService<RocksDbGenericStore<CacheKey, CacheValue>>("non-existent-store"));
}

[Test]
public void should_resolve_default_store_as_first_registered_keyed_service()
{
// Arrange
using var testFixture = TestFixture.Create(rockDb =>
{
_ = rockDb.AddStore<CacheKey, CacheValue, RocksDbGenericStore<CacheKey, CacheValue>>("my-store-1");
_ = rockDb.AddStore<CacheKey, CacheValue, RocksDbGenericStore<CacheKey, CacheValue>>("my-store-2");
}, options =>
{
options.SerializerFactories.Clear();
options.SerializerFactories.Add(new ProtobufSerializerFactory());
});

// Act
var store1 = testFixture.ServiceProvider.GetRequiredKeyedService<RocksDbGenericStore<CacheKey, CacheValue>>("my-store-1");
var store2 = testFixture.ServiceProvider.GetRequiredKeyedService<RocksDbGenericStore<CacheKey, CacheValue>>("my-store-2");
var store = testFixture.ServiceProvider.GetRequiredService<RocksDbGenericStore<CacheKey, CacheValue>>();

// Assert
Assert.Multiple(() =>
{
Assert.True(ReferenceEquals(store1, store));
Assert.False(ReferenceEquals(store2, store));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NScenario" Version="4.3.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
Expand Down
2 changes: 2 additions & 0 deletions test/RocksDb.Extensions.Tests/Utils/TestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public T GetStore<T>() where T : notnull
return _serviceProvider.GetRequiredService<T>();
}

public IServiceProvider ServiceProvider => _serviceProvider;

public void Dispose()
{
_serviceProvider.Dispose();
Expand Down

0 comments on commit 88764a8

Please sign in to comment.