Skip to content

Commit

Permalink
First go at defining pluggable modules
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Sep 15, 2023
1 parent b4e32fc commit a4b9563
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 12 deletions.
19 changes: 19 additions & 0 deletions src/ApiHost1/Module.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Reflection;
using Infrastructure.WebApi.Common;

namespace ApiHost1;

public class Module : ISubDomainModule
{
public Action<WebApplication> MinimalApiRegistrationFunction
{
get { return app => app.RegisterRoutes(); }
}

public Action<ConfigurationManager, IServiceCollection> RegisterServicesFunction
{
get { return (configuration, services) => { }; }
}

public Assembly ApiAssembly => typeof(Program).Assembly;
}
25 changes: 13 additions & 12 deletions src/ApiHost1/Program.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
using ApiHost1;
using CarsApi;
using CarsApplication;
using Infrastructure.WebApi.Common;
using JetBrains.Annotations;
using MinimalApiRegistration = CarsApi.MinimalApiRegistration;

//TODO: Add the modules of each API here
var modules = new SubDomainModules();
modules.Register(new Module());
modules.Register(new ApiHost1.Module());

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMediatR(configuration =>
{
//TODO: need to add API assembly of each module
configuration.RegisterServicesFromAssemblies(typeof(ApiHost1.MinimalApiRegistration).Assembly,
typeof(MinimalApiRegistration).Assembly);
configuration.RegisterServicesFromAssemblies(modules.HandlerAssemblies.ToArray());
});
builder.Services.AddScoped<ICarsApplication, CarsApplication.CarsApplication>();

//TODO: need register dependencies of each module
modules.RegisterServices(builder.Configuration, builder.Services);

var app = builder.Build();

//TODO: need to add validation
//TODO: need to add swaggerUI

//TODO: need to add validation (https://www.youtube.com/watch?v=XKN0084p7WQ)
//TODO: need to add authentication/authorization (https://www.youtube.com/watch?v=XKN0084p7WQ)
//TODO: need to add swaggerUI (https://www.youtube.com/watch?v=XKN0084p7WQ)

//TODO: need to call the registration function the minimal API endpoints of each module
app.RegisterRoutes();
MinimalApiRegistration.RegisterRoutes(app);
modules.ConfigureHost(app);

app.Run();

Expand Down
22 changes: 22 additions & 0 deletions src/CarsApi/Module.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Reflection;
using Infrastructure.WebApi.Common;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace CarsApi;

public class Module : ISubDomainModule
{
public Action<WebApplication> MinimalApiRegistrationFunction
{
get { return app => app.RegisterRoutes(); }
}

public Action<ConfigurationManager, IServiceCollection> RegisterServicesFunction
{
get { return (configuration, services) => { }; }
}

public Assembly ApiAssembly => typeof(CarsApi).Assembly;
}
1 change: 1 addition & 0 deletions src/Infrastructure.WebApi.Common.UnitTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Common.UnitTests\Common.UnitTests.csproj" />
<ProjectReference Include="..\Infrastructure.WebApi.Common\Infrastructure.WebApi.Common.csproj" />
</ItemGroup>

</Project>
114 changes: 114 additions & 0 deletions src/Infrastructure.WebApi.Common.UnitTests/SubDomainModulesSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Reflection;
using FluentAssertions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure.WebApi.Common.UnitTests;

[Trait("Category", "Unit")]
public class SubDomainModulesSpec
{
private readonly SubDomainModules _modules;

public SubDomainModulesSpec()
{
_modules = new SubDomainModules();
}

[Fact]
public void WhenRegisterAndNullModule_ThenThrows()
{
_modules
.Invoking(x => x.Register(null!))
.Should().Throw<ArgumentNullException>();
}

[Fact]
public void WhenRegisterAndNullApiAssembly_ThenThrows()
{
_modules
.Invoking(x => x.Register(new TestModule { ApiAssembly = null! }))
.Should().Throw<ArgumentNullException>();
}

[Fact]
public void WhenRegisterAndNullMinimalApiRegistrationFunction_ThenThrows()
{
_modules
.Invoking(x => x.Register(new TestModule
{ ApiAssembly = typeof(SubDomainModulesSpec).Assembly, RegisterServicesFunction = (_, _) => { } }))
.Should().Throw<ArgumentNullException>();
}

[Fact]
public void WhenRegisterAndNullRegisterServicesFunction_ThenRegisters()
{
_modules.Register(new TestModule
{
ApiAssembly = typeof(SubDomainModulesSpec).Assembly, MinimalApiRegistrationFunction = _ => { },
RegisterServicesFunction = null!
});

_modules.HandlerAssemblies.Should().ContainSingle();
}

[Fact]
public void WhenRegisterServicesAndNoModules_ThenAppliedToAllModules()
{
var configuration = new ConfigurationManager();
var services = new ServiceCollection();

_modules.RegisterServices(configuration, services);
}

[Fact]
public void WhenRegisterServices_ThenAppliedToAllModules()
{
var configuration = new ConfigurationManager();
var services = new ServiceCollection();
var wasCalled = false;
_modules.Register(new TestModule
{
ApiAssembly = typeof(SubDomainModulesSpec).Assembly,
MinimalApiRegistrationFunction = _ => { },
RegisterServicesFunction = (_, _) => { wasCalled = true; }
});

_modules.RegisterServices(configuration, services);

wasCalled.Should().BeTrue();
}

[Fact]
public void WhenConfigureHostAndNoModules_ThenAppliedToAllModules()
{
var app = WebApplication.Create();

_modules.ConfigureHost(app);
}

[Fact]
public void WhenConfigureHost_ThenAppliedToAllModules()
{
var app = WebApplication.Create();
var wasCalled = false;
_modules.Register(new TestModule
{
ApiAssembly = typeof(SubDomainModulesSpec).Assembly,
MinimalApiRegistrationFunction = _ => { wasCalled = true; },
RegisterServicesFunction = (_, _) => { }
});

_modules.ConfigureHost(app);

wasCalled.Should().BeTrue();
}
}

public class TestModule : ISubDomainModule
{
public Assembly? ApiAssembly { get; init; }
public Action<WebApplication>? MinimalApiRegistrationFunction { get; init; }
public Action<ConfigurationManager, IServiceCollection>? RegisterServicesFunction { get; init; }
}
55 changes: 55 additions & 0 deletions src/Infrastructure.WebApi.Common/SubDomainModules.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure.WebApi.Common;

public class SubDomainModules
{
private readonly List<Assembly> _handlerAssemblies;
private readonly List<Action<WebApplication>> _minimalApiRegistrationFunctions;
private readonly List<Action<ConfigurationManager, IServiceCollection>> _serviceCollectionFunctions;

public SubDomainModules()
{
_handlerAssemblies = new List<Assembly>();
_minimalApiRegistrationFunctions = new List<Action<WebApplication>>();
_serviceCollectionFunctions = new List<Action<ConfigurationManager, IServiceCollection>>();
}

public IReadOnlyList<Assembly> HandlerAssemblies => _handlerAssemblies;

public void Register(ISubDomainModule module)
{
ArgumentNullException.ThrowIfNull(module);
ArgumentNullException.ThrowIfNull(module.ApiAssembly, nameof(module.ApiAssembly));
ArgumentNullException.ThrowIfNull(module.MinimalApiRegistrationFunction,
nameof(module.MinimalApiRegistrationFunction));

_handlerAssemblies.Add(module.ApiAssembly);
_minimalApiRegistrationFunctions.Add(module.MinimalApiRegistrationFunction);
if (module.RegisterServicesFunction is not null)
{
_serviceCollectionFunctions.Add(module.RegisterServicesFunction);
}
}

public void RegisterServices(ConfigurationManager configuration, IServiceCollection serviceCollection)
{
_serviceCollectionFunctions.ForEach(func => func(configuration, serviceCollection));
}

public void ConfigureHost(WebApplication app)
{
_minimalApiRegistrationFunctions.ForEach(func => func(app));
}
}

public interface ISubDomainModule
{
public Assembly ApiAssembly { get; }

public Action<WebApplication> MinimalApiRegistrationFunction { get; }
public Action<ConfigurationManager, IServiceCollection>? RegisterServicesFunction { get; }
}
12 changes: 12 additions & 0 deletions src/SaaStack.sln
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTesting.WebApi.C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.WebApi.Generators", "Infrastructure.WebApi.Generators\Infrastructure.WebApi.Generators.csproj", "{7AB39FD6-660F-4400-9955-B92684378492}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{19ADDB2F-B589-49EF-9BDA-BD9908057D60}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.WebApi.Common.UnitTests", "Infrastructure.WebApi.Common.UnitTests\Infrastructure.WebApi.Common.UnitTests.csproj", "{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -150,6 +154,12 @@ Global
{7AB39FD6-660F-4400-9955-B92684378492}.Release|Any CPU.Build.0 = Release|Any CPU
{7AB39FD6-660F-4400-9955-B92684378492}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
{7AB39FD6-660F-4400-9955-B92684378492}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}.Release|Any CPU.Build.0 = Release|Any CPU
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F5C77A86-38AF-40E4-82FC-617E624B2754} = {508E7DA4-4DF2-4201-955D-CCF70C41AD05}
Expand Down Expand Up @@ -179,5 +189,7 @@ Global
{23FF9513-1B26-41F4-A7FE-1D8A9F0808AE} = {BA1AEAEC-68CD-4855-A8CB-0DC2070B6A8C}
{A7CA7AD7-70CA-43F0-BE73-75A01342D571} = {5838EE94-374F-4A6F-A231-1BC1C87985F4}
{7AB39FD6-660F-4400-9955-B92684378492} = {B68592DF-E8E8-452A-A46F-5C8ECB178FDF}
{19ADDB2F-B589-49EF-9BDA-BD9908057D60} = {B68592DF-E8E8-452A-A46F-5C8ECB178FDF}
{4CF7C7E2-C95D-4440-9ECF-5D1CE2A46D7A} = {19ADDB2F-B589-49EF-9BDA-BD9908057D60}
EndGlobalSection
EndGlobal

0 comments on commit a4b9563

Please sign in to comment.