Skip to content

Commit

Permalink
IGNITE-23651 .NET: Add ServiceCollection extensions for DI integration (
Browse files Browse the repository at this point in the history
#4811)

Add ServiceCollection extensions for DI integration with IgniteClientGroup.

---------

Co-authored-by: gurustron <gurustron@apache.org>
  • Loading branch information
gurustron and gurustron authored Dec 4, 2024
1 parent 1edda62 commit b6be0ce
Show file tree
Hide file tree
Showing 4 changed files with 389 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Apache.Ignite.Tests;

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;

/// <summary>
/// Tests for <see cref="IgniteServiceCollectionExtensionsTests"/>.
/// </summary>
public class IgniteServiceCollectionExtensionsTests
{
private FakeServer _server;

[SetUp]
public void StartServer() => _server = new FakeServer
{
AllowMultipleConnections = true
};

[TearDown]
public void StopServer() => _server.Dispose();

[Test]
public async Task TestRegisterSingleClient()
{
await ValidateRegisterSingleClient(
services => services.AddIgniteClientGroup(CreateGroupConfig()),
services => services.GetService<IgniteClientGroup>());

await ValidateRegisterSingleClient(
services => services.AddIgniteClientGroup(_ => CreateGroupConfig()),
services => services.GetService<IgniteClientGroup>());

const string serviceKey = "key";

await ValidateRegisterSingleClient(
services => services.AddIgniteClientGroupKeyed(serviceKey, CreateGroupConfig()),
services => services.GetKeyedService<IgniteClientGroup>(serviceKey));

await ValidateRegisterSingleClient(
services => services.AddIgniteClientGroupKeyed(serviceKey, _ => CreateGroupConfig()),
services => services.GetKeyedService<IgniteClientGroup>(serviceKey));

await ValidateRegisterSingleClient(
services => services.AddIgniteClientGroupKeyed(serviceKey, (_, _) => CreateGroupConfig()),
services => services.GetKeyedService<IgniteClientGroup>(serviceKey));
}

[Test]
public async Task TestRegisterConfigurationInstanceRoundRobin()
{
const int count = 3;

await ValidateRegisterRoundRobin(
count,
services => services.AddIgniteClientGroup(CreateGroupConfig(count)),
services => services.GetService<IgniteClientGroup>());

await ValidateRegisterRoundRobin(
count,
services => services.AddIgniteClientGroup(_ => CreateGroupConfig(count)),
services => services.GetService<IgniteClientGroup>());

const string? serviceKey = "key";

await ValidateRegisterRoundRobin(
count,
services => services.AddIgniteClientGroupKeyed(serviceKey, CreateGroupConfig(count)),
services => services.GetKeyedService<IgniteClientGroup>(serviceKey));

await ValidateRegisterRoundRobin(
count,
services => services.AddIgniteClientGroupKeyed(serviceKey, _ => CreateGroupConfig(count)),
services => services.GetKeyedService<IgniteClientGroup>(serviceKey));

await ValidateRegisterRoundRobin(
count,
services => services.AddIgniteClientGroupKeyed(serviceKey, (_, _) => CreateGroupConfig(count)),
services => services.GetKeyedService<IgniteClientGroup>(serviceKey));
}

[Test]
public void TestRegisterScopesConfiguration([Values]ServiceLifetime lifetime)
{
var services = new ServiceCollection();

var resServices = services.AddIgniteClientGroup(CreateGroupConfig(), lifetime);
Assert.AreSame(services, resServices);

var servicesDescriptors = services
.Where(sd => sd.ServiceType == typeof(IgniteClientGroup))
.ToList();

Assert.AreEqual(1, servicesDescriptors.Count);
Assert.AreEqual(lifetime, servicesDescriptors.First().Lifetime);
}

[Test]
public void TestRegisterScopesConfigurationFunc([Values]ServiceLifetime lifetime)
{
var services = new ServiceCollection();

var resServices = services.AddIgniteClientGroup(_ => CreateGroupConfig(), lifetime);
Assert.AreSame(services, resServices);

var servicesDescriptors = services
.Where(sd => sd.ServiceType == typeof(IgniteClientGroup))
.ToList();

Assert.AreEqual(1, servicesDescriptors.Count);
Assert.AreEqual(lifetime, servicesDescriptors.First().Lifetime);
}

[Test]
public void TestRegisterScopesConfigurationKeyed([Values] ServiceLifetime lifetime)
{
ValidateKeyedRegistrationScope(
lifetime,
(s, key) => s.AddIgniteClientGroupKeyed(key, CreateGroupConfig(), lifetime));
}

[Test]
public void TestRegisterScopesConfigurationFuncKeyed([Values] ServiceLifetime lifetime)
{
ValidateKeyedRegistrationScope(
lifetime,
(s, key) => s.AddIgniteClientGroupKeyed(key, _ => CreateGroupConfig(), lifetime));
}

[Test]
public void TestRegisterScopesConfigurationFuncWithKeyKeyed([Values] ServiceLifetime lifetime)
{
ValidateKeyedRegistrationScope(
lifetime,
(s, key) => s.AddIgniteClientGroupKeyed(key, (_, _) => CreateGroupConfig(), lifetime));
}

private static async Task ValidateRegisterSingleClient(Action<ServiceCollection> register, Func<IServiceProvider, IgniteClientGroup?> resolve)
{
var services = new ServiceCollection();

register(services);

var serviceProvider = services.BuildServiceProvider();

var group = resolve(serviceProvider);
var group2 = resolve(serviceProvider);

Assert.That(group, Is.Not.Null);
Assert.That(group2, Is.Not.Null);
Assert.AreSame(group, group2);

IIgnite client = await group.GetIgniteAsync();
IIgnite client2 = await group.GetIgniteAsync();
IIgnite client3 = await group2.GetIgniteAsync();

Assert.That(client, Is.Not.Null);
Assert.AreSame(client, client2);
Assert.AreSame(client, client2);
Assert.AreSame(client, client3);

await client.Tables.GetTablesAsync();
}

private static async Task ValidateRegisterRoundRobin(
int count,
Action<ServiceCollection> register,
Func<IServiceProvider, IgniteClientGroup?> resolve)
{
var services = new ServiceCollection();

register(services);

var serviceProvider = services.BuildServiceProvider();

var group = resolve(serviceProvider);

Assert.That(group, Is.Not.Null);

var clients = await Task.WhenAll(
Enumerable.Range(0, count + 1).Select(async _ => await group.GetIgniteAsync()));

var uniqueClients = clients.Distinct().ToArray();

Assert.That(uniqueClients.Length, Is.EqualTo(count));

await clients.First().Tables.GetTablesAsync();
}

private static void ValidateKeyedRegistrationScope(ServiceLifetime lifetime, Action<ServiceCollection, object> register)
{
var services = new ServiceCollection();
var keys = new[] { "key1", "key2" };

foreach (var key in keys)
{
register(services, key);
}

var servicesDescriptors = services
.Where(sd => sd.ServiceType == typeof(IgniteClientGroup))
.ToList();

var actualKeys = servicesDescriptors.Select(s => s.ServiceKey).ToArray();

Assert.AreEqual(keys.Length, servicesDescriptors.Count);
foreach (var key in keys)
{
CollectionAssert.Contains(actualKeys, key);
}

Assert.AreEqual(1, servicesDescriptors.Select(sd => sd.Lifetime).Distinct().Count());
Assert.AreEqual(lifetime, servicesDescriptors.First().Lifetime);
}

private IgniteClientGroupConfiguration CreateGroupConfig(int size = 1) =>
new()
{
Size = size,
ClientConfiguration = new IgniteClientConfiguration(_server.Endpoint)
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageReference Include="NodaTime" Version="[3.*,)" />
<PackageReference Include="Remotion.Linq" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="[6.*,)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[8.*,)" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Apache.Ignite;

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

/// <summary>
/// Extension methods for setting up Apache Ignite services
/// in an <see cref="Microsoft.Extensions.DependencyInjection.IServiceCollection" />.
/// </summary>
public static class IgniteServiceCollectionExtensions
{
/// <summary>
/// Registers an <see cref="IgniteClientGroup" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="configuration">
/// <see cref="IgniteClientGroupConfiguration" /> instance.
/// </param>
/// <param name="clientGroupLifetime">
/// The lifetime with which to register the <see cref="IgniteClientGroup" /> service in the container.
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
/// </param>
/// <returns>Original service collection to chain multiple calls.</returns>
public static IServiceCollection AddIgniteClientGroup(
this IServiceCollection services,
IgniteClientGroupConfiguration configuration,
ServiceLifetime clientGroupLifetime = ServiceLifetime.Singleton) =>
AddIgniteClientGroupCore(services, (_, _) => configuration, clientGroupLifetime);

/// <summary>
/// Registers an <see cref="IgniteClientGroup" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="configure">
/// Function to build the <see cref="IgniteClientGroupConfiguration" />.
/// </param>
/// <param name="clientGroupLifetime">
/// The lifetime with which to register the <see cref="IgniteClientGroup" /> service in the container.
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
/// </param>
/// <returns>Original service collection to chain multiple calls.</returns>
public static IServiceCollection AddIgniteClientGroup(
this IServiceCollection services,
Func<IServiceProvider, IgniteClientGroupConfiguration> configure,
ServiceLifetime clientGroupLifetime = ServiceLifetime.Singleton) =>
AddIgniteClientGroupCore(services, (sp, _) => configure(sp), clientGroupLifetime);

/// <summary>
/// Registers an <see cref="IgniteClientGroup" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="serviceKey">
/// The <see cref="ServiceDescriptor.ServiceKey"/> of the client group.
/// </param>
/// <param name="configuration">
/// <see cref="IgniteClientGroupConfiguration" /> instance.
/// </param>
/// <param name="clientGroupLifetime">
/// The lifetime with which to register the <see cref="IgniteClientGroup" /> service in the container.
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
/// </param>
/// <returns>Original service collection to chain multiple calls.</returns>
public static IServiceCollection AddIgniteClientGroupKeyed(
this IServiceCollection services,
object? serviceKey,
IgniteClientGroupConfiguration configuration,
ServiceLifetime clientGroupLifetime = ServiceLifetime.Singleton) =>
AddIgniteClientGroupCore(services, (_, _) => configuration, clientGroupLifetime, serviceKey);

/// <summary>
/// Registers an <see cref="IgniteClientGroup" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="serviceKey">
/// The <see cref="ServiceDescriptor.ServiceKey"/> of the client group.
/// </param>
/// <param name="configure">
/// Function to build the <see cref="IgniteClientGroupConfiguration" />.
/// </param>
/// <param name="clientGroupLifetime">
/// The lifetime with which to register the <see cref="IgniteClientGroup" /> service in the container.
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
/// </param>
/// <returns>Original service collection to chain multiple calls.</returns>
public static IServiceCollection AddIgniteClientGroupKeyed(
this IServiceCollection services,
object? serviceKey,
Func<IServiceProvider, IgniteClientGroupConfiguration> configure,
ServiceLifetime clientGroupLifetime = ServiceLifetime.Singleton) =>
AddIgniteClientGroupCore(
services,
(sp, _) => configure(sp),
clientGroupLifetime,
serviceKey);

/// <summary>
/// Registers an <see cref="IgniteClientGroup" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="serviceKey">
/// The <see cref="ServiceDescriptor.ServiceKey"/> of the client group.
/// </param>
/// <param name="configure">
/// Function to build the <see cref="IgniteClientGroupConfiguration" />.
/// </param>
/// <param name="clientGroupLifetime">
/// The lifetime with which to register the <see cref="IgniteClientGroup" /> service in the container.
/// Defaults to <see cref="ServiceLifetime.Singleton" />.
/// </param>
/// <returns>Original service collection to chain multiple calls.</returns>
public static IServiceCollection AddIgniteClientGroupKeyed(
this IServiceCollection services,
object? serviceKey,
Func<IServiceProvider, object?, IgniteClientGroupConfiguration> configure,
ServiceLifetime clientGroupLifetime = ServiceLifetime.Singleton) =>
AddIgniteClientGroupCore(services, configure, clientGroupLifetime, serviceKey);

private static IServiceCollection AddIgniteClientGroupCore(
IServiceCollection services,
Func<IServiceProvider, object?, IgniteClientGroupConfiguration> configure,
ServiceLifetime clientGroupLifetime = ServiceLifetime.Singleton,
object? key = null)
{
services.TryAdd(new ServiceDescriptor(
typeof(IgniteClientGroup),
key,
(sp, innerKey) => new IgniteClientGroup(configure(sp, innerKey)),
clientGroupLifetime));

return services;
}
}
Loading

0 comments on commit b6be0ce

Please sign in to comment.