This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new b6be0ce886 IGNITE-23651 .NET: Add ServiceCollection extensions for DI 
integration (#4811)
b6be0ce886 is described below

commit b6be0ce886608830d20c38e5beae3e0d05f56bac
Author: gurustron <[email protected]>
AuthorDate: Wed Dec 4 18:50:42 2024 +0300

    IGNITE-23651 .NET: Add ServiceCollection extensions for DI integration 
(#4811)
    
    Add ServiceCollection extensions for DI integration with IgniteClientGroup.
    
    ---------
    
    Co-authored-by: gurustron <[email protected]>
---
 .../IgniteServiceCollectionExtensionsTests.cs      | 239 +++++++++++++++++++++
 .../dotnet/Apache.Ignite/Apache.Ignite.csproj      |   1 +
 .../IgniteServiceCollectionExtensions.cs           | 148 +++++++++++++
 modules/platforms/dotnet/DEVNOTES.md               |   2 +-
 4 files changed, 389 insertions(+), 1 deletion(-)

diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServiceCollectionExtensionsTests.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServiceCollectionExtensionsTests.cs
new file mode 100644
index 0000000000..ecdd56e26a
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Tests/IgniteServiceCollectionExtensionsTests.cs
@@ -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)
+        };
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj 
b/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
index 8df6c0517d..5837e8dae6 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
@@ -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>
diff --git 
a/modules/platforms/dotnet/Apache.Ignite/IgniteServiceCollectionExtensions.cs 
b/modules/platforms/dotnet/Apache.Ignite/IgniteServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..ef95a77bc0
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite/IgniteServiceCollectionExtensions.cs
@@ -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;
+    }
+}
diff --git a/modules/platforms/dotnet/DEVNOTES.md 
b/modules/platforms/dotnet/DEVNOTES.md
index bbc5e07864..36ce144a54 100644
--- a/modules/platforms/dotnet/DEVNOTES.md
+++ b/modules/platforms/dotnet/DEVNOTES.md
@@ -1,5 +1,5 @@
 ## Prerequisites
-* .NET 6 SDK
+* .NET 8 SDK
 * Java 11 SDK
 
 ## Build Java

Reply via email to