Skip to content

Commit

Permalink
feat: add multi-tenancy builder
Browse files Browse the repository at this point in the history
Inspired by how Microsoft does it in ASP.NET Core. It's
the main entry point on configuring multi-tenancy services.
  • Loading branch information
Xzelsius committed Nov 19, 2024
1 parent cefe6c4 commit 6a96ad0
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@
<PackageTags>$(PackageCommonTags);multi-tenancy</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Raphael Strotz. All rights reserved.

namespace Ayaka.MultiTenancy.DependencyInjection;

using Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Provides functionality to configure multi-tenancy services.
/// </summary>
public interface IMultiTenancyBuilder
{
/// <summary>
/// Gets the <see cref="IServiceCollection"/> where multi-tenancy services are configured.
/// </summary>
IServiceCollection Services { get; }
}
2 changes: 2 additions & 0 deletions src/Ayaka.MultiTenancy.Abstractions/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#nullable enable
Ayaka.MultiTenancy.DependencyInjection.IMultiTenancyBuilder
Ayaka.MultiTenancy.DependencyInjection.IMultiTenancyBuilder.Services.get -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
Ayaka.MultiTenancy.ITenantContextAccessor
Ayaka.MultiTenancy.ITenantContextAccessor.TenantContext.get -> Ayaka.MultiTenancy.TenantContext?
Ayaka.MultiTenancy.ITenantContextAccessor.TenantContext.set -> void
Expand Down
8 changes: 4 additions & 4 deletions src/Ayaka.MultiTenancy/Ayaka.MultiTenancy.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="Ayaka.MultiTenancy.Tests" />
</ItemGroup>

<PropertyGroup>
<PackageDescription>Provides functionality for creating multi-tenanted applications. $(PackageDescriptionAppendix)</PackageDescription>
<PackageTags>$(PackageCommonTags);multi-tenancy</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Ayaka.MultiTenancy.Abstractions\Ayaka.MultiTenancy.Abstractions.csproj" />
</ItemGroup>
Expand Down
23 changes: 23 additions & 0 deletions src/Ayaka.MultiTenancy/DependencyInjection/MultiTenancyBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Raphael Strotz. All rights reserved.

namespace Ayaka.MultiTenancy.DependencyInjection;

using Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Allows configuration of multi-tenancy services.
/// </summary>
internal sealed class MultiTenancyBuilder : IMultiTenancyBuilder
{
/// <summary>
/// Initializes a new instance of the <see cref="MultiTenancyBuilder"/> class.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
public MultiTenancyBuilder(IServiceCollection services)
{
Services = services;
}

/// <inheritdoc />
public IServiceCollection Services { get; }
}
1 change: 1 addition & 0 deletions src/Ayaka.MultiTenancy/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ Ayaka.MultiTenancy.AsyncLocalTenantContextAccessor.AsyncLocalTenantContextAccess
Ayaka.MultiTenancy.AsyncLocalTenantContextAccessor.TenantContext.get -> Ayaka.MultiTenancy.TenantContext?
Ayaka.MultiTenancy.AsyncLocalTenantContextAccessor.TenantContext.set -> void
Ayaka.MultiTenancy.ServiceCollectionExtensions
static Ayaka.MultiTenancy.ServiceCollectionExtensions.AddMultiTenancy(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Ayaka.MultiTenancy.DependencyInjection.IMultiTenancyBuilder!
static Ayaka.MultiTenancy.ServiceCollectionExtensions.AddTenantContextAccessor(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
24 changes: 24 additions & 0 deletions src/Ayaka.MultiTenancy/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Ayaka.MultiTenancy;

using Ayaka.MultiTenancy.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

Expand All @@ -10,6 +11,22 @@ namespace Ayaka.MultiTenancy;
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds multi-tenancy services to the specified <see cref="IServiceCollection"/>.
/// </summary>
/// <remarks>
/// In order to fine-tune the multi-tenancy configuration, use the <see cref="IMultiTenancyBuilder"/> returned by
/// this method.
/// </remarks>
/// <param name="services">The <see cref="IServiceCollection"/> to add the service to.</param>
/// <returns>A <see cref="IMultiTenancyBuilder"/> that can be used to further configure multi-tenancy.</returns>
public static IMultiTenancyBuilder AddMultiTenancy(this IServiceCollection services)
{
ConfigureDefaultServices(services);

return new MultiTenancyBuilder(services);
}

/// <summary>
/// Adds the default implementation for the <see cref="ITenantContextAccessor"/> service.
/// </summary>
Expand All @@ -20,4 +37,11 @@ public static IServiceCollection AddTenantContextAccessor(this IServiceCollectio
services.TryAddSingleton<ITenantContextAccessor, AsyncLocalTenantContextAccessor>();
return services;
}

[SuppressMessage("Style", "IDE0058:Expression value is never used")]
private static void ConfigureDefaultServices(this IServiceCollection services)
{
// The heart of multi-tenancy
services.AddTenantContextAccessor();
}
}
47 changes: 47 additions & 0 deletions test/Ayaka.MultiTenancy.Tests/ServiceCollectionExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,57 @@

namespace Ayaka.MultiTenancy.Tests;

using Ayaka.MultiTenancy.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

public sealed class ServiceCollectionExtensionsTest
{
public sealed class AddMultiTenancy
{
[Fact]
public void Does_return_instance_of_MultiTenancyBuilder()
{
var services = new ServiceCollection();

var builder = services.AddMultiTenancy();

builder.Should().NotBeNull();
builder.Should().BeOfType<MultiTenancyBuilder>();
}

[Fact]
public void Does_use_specified_service_collection()
{
var services = new ServiceCollection();

var builder = services.AddMultiTenancy();

builder.Services.Should().BeSameAs(services);
}

[Fact]
public void Does_add_default_services()
{
var services = new ServiceCollection();

services.AddMultiTenancy();

var accessor = services.FirstOrDefault(x => x.ServiceType == typeof(ITenantContextAccessor));
accessor.Should().NotBeNull("ITenantContextAccessor should be registered");
}

[Fact]
public void Does_not_add_default_services_twice()
{
var services = new ServiceCollection();

services.AddMultiTenancy();
services.AddMultiTenancy();

services.Count(x => x.ServiceType == typeof(ITenantContextAccessor)).Should().Be(1);
}
}

public sealed class AddTenantContextAccessor
{
[Fact]
Expand Down

0 comments on commit 6a96ad0

Please sign in to comment.