From 02eac1aefafad84922f478d02de55003f8450303 Mon Sep 17 00:00:00 2001 From: Jezz Santos Date: Tue, 11 Jun 2024 17:36:51 +1200 Subject: [PATCH] Project structure --- src/ApiHost1/ApiHost1.csproj | 1 + src/ApiHost1/HostedModules.cs | 2 + .../ISubscriptionsService.cs | 5 ++ .../Subscriptions/Created.cs | 21 ++++++ src/SaaStack.sln | 66 +++++++++++++++++ .../SubscriptionsApplication.UnitTests.csproj | 18 +++++ .../SubscriptionsApplicationSpec.cs | 9 +++ .../SubscriptionsInProcessServiceClient.cs | 13 ++++ .../ISubscriptionsApplication.cs | 5 ++ .../Persistence/ISubscriptionRepository.cs | 5 ++ .../Persistence/ReadModels/Subscription.cs | 11 +++ .../SubscriptionsApplication.cs | 5 ++ .../SubscriptionsApplication.csproj | 18 +++++ .../SubscriptionRootSpec.cs | 9 +++ .../SubscriptionsDomain.UnitTests.csproj | 18 +++++ src/SubscriptionsDomain/Events.cs | 16 +++++ src/SubscriptionsDomain/Resources.Designer.cs | 62 ++++++++++++++++ src/SubscriptionsDomain/Resources.resx | 27 +++++++ src/SubscriptionsDomain/SubscriptionRoot.cs | 70 +++++++++++++++++++ .../SubscriptionsDomain.csproj | 34 +++++++++ .../SubscriptionsApiSpec.cs | 24 +++++++ ...ionsInfrastructure.IntegrationTests.csproj | 23 ++++++ .../appsettings.Testing.json | 16 +++++ .../Api/Subscriptions/Class1Spec.cs | 9 +++ ...bscriptionsInfrastructure.UnitTests.csproj | 18 +++++ .../Api/Subscriptions/SubscriptionsApi.cs | 20 ++++++ .../ReadModels/SubscriptionProjection.cs | 38 ++++++++++ .../Persistence/SubscriptionRepository.cs | 7 ++ .../Resources.Designer.cs | 62 ++++++++++++++++ .../Resources.resx | 27 +++++++ .../SubscriptionsInfrastructure.csproj | 38 ++++++++++ .../SubscriptionsModule.cs | 56 +++++++++++++++ .../.template.config/template.json | 5 +- .../{SubDomainName}s/{SubDomainName}sApi.cs | 8 ++- .../ReadModels/{SubDomainName}Projection.cs | 40 +++++++++++ ...ameModule.cs => {SubDomainName}sModule.cs} | 13 ++-- 36 files changed, 808 insertions(+), 11 deletions(-) create mode 100644 src/Application.Services.Shared/ISubscriptionsService.cs create mode 100644 src/Domain.Events.Shared/Subscriptions/Created.cs create mode 100644 src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj create mode 100644 src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs create mode 100644 src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs create mode 100644 src/SubscriptionsApplication/ISubscriptionsApplication.cs create mode 100644 src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs create mode 100644 src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs create mode 100644 src/SubscriptionsApplication/SubscriptionsApplication.cs create mode 100644 src/SubscriptionsApplication/SubscriptionsApplication.csproj create mode 100644 src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs create mode 100644 src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj create mode 100644 src/SubscriptionsDomain/Events.cs create mode 100644 src/SubscriptionsDomain/Resources.Designer.cs create mode 100644 src/SubscriptionsDomain/Resources.resx create mode 100644 src/SubscriptionsDomain/SubscriptionRoot.cs create mode 100644 src/SubscriptionsDomain/SubscriptionsDomain.csproj create mode 100644 src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs create mode 100644 src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj create mode 100644 src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json create mode 100644 src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs create mode 100644 src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj create mode 100644 src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs create mode 100644 src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs create mode 100644 src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs create mode 100644 src/SubscriptionsInfrastructure/Resources.Designer.cs create mode 100644 src/SubscriptionsInfrastructure/Resources.resx create mode 100644 src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj create mode 100644 src/SubscriptionsInfrastructure/SubscriptionsModule.cs create mode 100644 src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs rename src/Tools.Templates/InfrastructureProject/{ProjectNameModule.cs => {SubDomainName}sModule.cs} (79%) diff --git a/src/ApiHost1/ApiHost1.csproj b/src/ApiHost1/ApiHost1.csproj index 693923f4..35787a7e 100644 --- a/src/ApiHost1/ApiHost1.csproj +++ b/src/ApiHost1/ApiHost1.csproj @@ -14,6 +14,7 @@ + diff --git a/src/ApiHost1/HostedModules.cs b/src/ApiHost1/HostedModules.cs index a78362ff..81d42da8 100644 --- a/src/ApiHost1/HostedModules.cs +++ b/src/ApiHost1/HostedModules.cs @@ -7,6 +7,7 @@ using ImagesInfrastructure; using Infrastructure.Web.Hosting.Common; using OrganizationsInfrastructure; +using SubscriptionsInfrastructure; using UserProfilesInfrastructure; namespace ApiHost1; @@ -21,6 +22,7 @@ public static SubdomainModules Get() modules.Register(new UserProfilesModule()); modules.Register(new EndUsersModule()); modules.Register(new OrganizationsModule()); + modules.Register(new SubscriptionsModule()); modules.Register(new IdentityModule()); modules.Register(new EventNotificationsModule()); modules.Register(new AncillaryModule()); diff --git a/src/Application.Services.Shared/ISubscriptionsService.cs b/src/Application.Services.Shared/ISubscriptionsService.cs new file mode 100644 index 00000000..6db75749 --- /dev/null +++ b/src/Application.Services.Shared/ISubscriptionsService.cs @@ -0,0 +1,5 @@ +namespace Application.Services.Shared; + +public interface ISubscriptionsService +{ +} \ No newline at end of file diff --git a/src/Domain.Events.Shared/Subscriptions/Created.cs b/src/Domain.Events.Shared/Subscriptions/Created.cs new file mode 100644 index 00000000..0b0658b7 --- /dev/null +++ b/src/Domain.Events.Shared/Subscriptions/Created.cs @@ -0,0 +1,21 @@ +using Domain.Common; +using Domain.Common.ValueObjects; +using JetBrains.Annotations; + +namespace Domain.Events.Shared.Subscriptions; + +public sealed class Created : DomainEvent +{ + public Created(Identifier id) : base(id) + { + } + + [UsedImplicitly] + public Created() + { + } + + public required string CreatedById { get; set; } + + public required string OrganizationId { get; set; } +} \ No newline at end of file diff --git a/src/SaaStack.sln b/src/SaaStack.sln index 43ed16fc..b083ddc3 100644 --- a/src/SaaStack.sln +++ b/src/SaaStack.sln @@ -376,6 +376,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventNotificationsApplicati EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Hosting.Common.UnitTests", "Infrastructure.Hosting.Common.UnitTests\Infrastructure.Hosting.Common.UnitTests.csproj", "{C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsInfrastructure", "SubscriptionsInfrastructure\SubscriptionsInfrastructure.csproj", "{AC4B3281-7555-4CDD-9574-96B73D2414D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsApplication", "SubscriptionsApplication\SubscriptionsApplication.csproj", "{00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsDomain", "SubscriptionsDomain\SubscriptionsDomain.csproj", "{EB8FC27F-3138-4311-BF55-0590019ED5A1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{1A2524F5-09D6-4774-A585-96DBAC6E9CAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsDomain.UnitTests", "SubscriptionsDomain.UnitTests\SubscriptionsDomain.UnitTests.csproj", "{687C172C-1B65-476F-B951-DD82E3C9CB69}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsApplication.UnitTests", "SubscriptionsApplication.UnitTests\SubscriptionsApplication.UnitTests.csproj", "{BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsInfrastructure.UnitTests", "SubscriptionsInfrastructure.UnitTests\SubscriptionsInfrastructure.UnitTests.csproj", "{06282A09-852B-4628-988B-D0379FDFC4E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SubscriptionsInfrastructure.IntegrationTests", "SubscriptionsInfrastructure.IntegrationTests\SubscriptionsInfrastructure.IntegrationTests.csproj", "{90055376-3EB3-4AFD-B413-1CE6EC1CC909}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1211,6 +1227,48 @@ Global {C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}.Release|Any CPU.Build.0 = Release|Any CPU {C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU {C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC4B3281-7555-4CDD-9574-96B73D2414D0}.Release|Any CPU.Build.0 = Release|Any CPU + {AC4B3281-7555-4CDD-9574-96B73D2414D0}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {AC4B3281-7555-4CDD-9574-96B73D2414D0}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.Release|Any CPU.Build.0 = Release|Any CPU + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB8FC27F-3138-4311-BF55-0590019ED5A1}.Release|Any CPU.Build.0 = Release|Any CPU + {EB8FC27F-3138-4311-BF55-0590019ED5A1}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {EB8FC27F-3138-4311-BF55-0590019ED5A1}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {687C172C-1B65-476F-B951-DD82E3C9CB69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {687C172C-1B65-476F-B951-DD82E3C9CB69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {687C172C-1B65-476F-B951-DD82E3C9CB69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {687C172C-1B65-476F-B951-DD82E3C9CB69}.Release|Any CPU.Build.0 = Release|Any CPU + {687C172C-1B65-476F-B951-DD82E3C9CB69}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {687C172C-1B65-476F-B951-DD82E3C9CB69}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.Release|Any CPU.Build.0 = Release|Any CPU + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {06282A09-852B-4628-988B-D0379FDFC4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06282A09-852B-4628-988B-D0379FDFC4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06282A09-852B-4628-988B-D0379FDFC4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06282A09-852B-4628-988B-D0379FDFC4E1}.Release|Any CPU.Build.0 = Release|Any CPU + {06282A09-852B-4628-988B-D0379FDFC4E1}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {06282A09-852B-4628-988B-D0379FDFC4E1}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU + {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.Release|Any CPU.Build.0 = Release|Any CPU + {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.ReleaseForDeploy|Any CPU.ActiveCfg = ReleaseForDeploy|Any CPU + {90055376-3EB3-4AFD-B413-1CE6EC1CC909}.ReleaseForDeploy|Any CPU.Build.0 = ReleaseForDeploy|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F5C77A86-38AF-40E4-82FC-617E624B2754} = {508E7DA4-4DF2-4201-955D-CCF70C41AD05} @@ -1393,5 +1451,13 @@ Global {41729001-D63B-4C95-AAE7-AC976EC306D2} = {E15AB9C5-C0F1-471D-BFC3-72ED133C4470} {E1D4D16F-9F16-4EF7-8893-A43446EC1653} = {E15AB9C5-C0F1-471D-BFC3-72ED133C4470} {C85713CE-CFEC-4A47-8A2E-B9FAAC20EB72} = {F561CAF6-2A8C-4440-B12E-7753F25D9879} + {AC4B3281-7555-4CDD-9574-96B73D2414D0} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807} + {00E15D5F-CECF-4B2C-8C88-FCC526DB9EED} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807} + {EB8FC27F-3138-4311-BF55-0590019ED5A1} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807} + {1A2524F5-09D6-4774-A585-96DBAC6E9CAE} = {124D8FF5-43D1-4019-B07C-7F55DC4A1807} + {687C172C-1B65-476F-B951-DD82E3C9CB69} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE} + {BCD2C514-FEE5-4B06-9A80-A0908AB4BC8E} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE} + {06282A09-852B-4628-988B-D0379FDFC4E1} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE} + {90055376-3EB3-4AFD-B413-1CE6EC1CC909} = {1A2524F5-09D6-4774-A585-96DBAC6E9CAE} EndGlobalSection EndGlobal diff --git a/src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj new file mode 100644 index 00000000..ef1e7a7b --- /dev/null +++ b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplication.UnitTests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + true + + + + + + + + + + + + + diff --git a/src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs new file mode 100644 index 00000000..e7fc5409 --- /dev/null +++ b/src/SubscriptionsApplication.UnitTests/SubscriptionsApplicationSpec.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace SubscriptionsApplication.UnitTests; + +[Trait("Category", "Unit")] +public class SubscriptionsApplicationSpec +{ + //TODO: type testm or testma to create a new test method +} \ No newline at end of file diff --git a/src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs b/src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs new file mode 100644 index 00000000..55c133af --- /dev/null +++ b/src/SubscriptionsApplication/ApplicationServices/SubscriptionsInProcessServiceClient.cs @@ -0,0 +1,13 @@ +using Application.Services.Shared; + +namespace SubscriptionsApplication.ApplicationServices; + +public class SubscriptionsInProcessServiceClient : ISubscriptionsService +{ + private readonly ISubscriptionsApplication _subscriptionsApplication; + + public SubscriptionsInProcessServiceClient(ISubscriptionsApplication subscriptionsApplication) + { + _subscriptionsApplication = subscriptionsApplication; + } +} \ No newline at end of file diff --git a/src/SubscriptionsApplication/ISubscriptionsApplication.cs b/src/SubscriptionsApplication/ISubscriptionsApplication.cs new file mode 100644 index 00000000..97bb0ff0 --- /dev/null +++ b/src/SubscriptionsApplication/ISubscriptionsApplication.cs @@ -0,0 +1,5 @@ +namespace SubscriptionsApplication; + +public interface ISubscriptionsApplication +{ +} \ No newline at end of file diff --git a/src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs b/src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs new file mode 100644 index 00000000..2b0a031e --- /dev/null +++ b/src/SubscriptionsApplication/Persistence/ISubscriptionRepository.cs @@ -0,0 +1,5 @@ +namespace SubscriptionsApplication.Persistence; + +public interface ISubscriptionRepository +{ +} \ No newline at end of file diff --git a/src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs b/src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs new file mode 100644 index 00000000..f84fe895 --- /dev/null +++ b/src/SubscriptionsApplication/Persistence/ReadModels/Subscription.cs @@ -0,0 +1,11 @@ +using Application.Persistence.Common; +using Common; +using QueryAny; + +namespace SubscriptionsApplication.Persistence.ReadModels; + +[EntityName("Subscription")] +public class Subscription : ReadModelEntity +{ + public Optional CreatedById { get; set; } +} \ No newline at end of file diff --git a/src/SubscriptionsApplication/SubscriptionsApplication.cs b/src/SubscriptionsApplication/SubscriptionsApplication.cs new file mode 100644 index 00000000..829d1a19 --- /dev/null +++ b/src/SubscriptionsApplication/SubscriptionsApplication.cs @@ -0,0 +1,5 @@ +namespace SubscriptionsApplication; + +public class SubscriptionsApplication : ISubscriptionsApplication +{ +} \ No newline at end of file diff --git a/src/SubscriptionsApplication/SubscriptionsApplication.csproj b/src/SubscriptionsApplication/SubscriptionsApplication.csproj new file mode 100644 index 00000000..9ae0e497 --- /dev/null +++ b/src/SubscriptionsApplication/SubscriptionsApplication.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + + + + + <_Parameter1>$(AssemblyName).UnitTests + + + + + + + + + diff --git a/src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs b/src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs new file mode 100644 index 00000000..ce8e41b2 --- /dev/null +++ b/src/SubscriptionsDomain.UnitTests/SubscriptionRootSpec.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace SubscriptionsDomain.UnitTests; + +[Trait("Category", "Unit")] +public class SubscriptionRootSpec +{ + //TODO: type testm or testma to create a new test method +} \ No newline at end of file diff --git a/src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj b/src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj new file mode 100644 index 00000000..a236ff46 --- /dev/null +++ b/src/SubscriptionsDomain.UnitTests/SubscriptionsDomain.UnitTests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + true + + + + + + + + + + + + + diff --git a/src/SubscriptionsDomain/Events.cs b/src/SubscriptionsDomain/Events.cs new file mode 100644 index 00000000..63373dd6 --- /dev/null +++ b/src/SubscriptionsDomain/Events.cs @@ -0,0 +1,16 @@ +using Domain.Common.ValueObjects; +using Domain.Events.Shared.Subscriptions; + +namespace SubscriptionsDomain; + +public static class Events +{ + public static Created Created(Identifier id, Identifier organizationId, Identifier createdBy) + { + return new Created(id) + { + OrganizationId = organizationId, + CreatedById = createdBy + }; + } +} \ No newline at end of file diff --git a/src/SubscriptionsDomain/Resources.Designer.cs b/src/SubscriptionsDomain/Resources.Designer.cs new file mode 100644 index 00000000..951cced4 --- /dev/null +++ b/src/SubscriptionsDomain/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SubscriptionsDomain { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SubscriptionsDomain.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/SubscriptionsDomain/Resources.resx b/src/SubscriptionsDomain/Resources.resx new file mode 100644 index 00000000..755958fe --- /dev/null +++ b/src/SubscriptionsDomain/Resources.resx @@ -0,0 +1,27 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + \ No newline at end of file diff --git a/src/SubscriptionsDomain/SubscriptionRoot.cs b/src/SubscriptionsDomain/SubscriptionRoot.cs new file mode 100644 index 00000000..099a9440 --- /dev/null +++ b/src/SubscriptionsDomain/SubscriptionRoot.cs @@ -0,0 +1,70 @@ +using Common; +using Domain.Common.Entities; +using Domain.Common.Identity; +using Domain.Common.ValueObjects; +using Domain.Events.Shared.Subscriptions; +using Domain.Interfaces; +using Domain.Interfaces.Entities; +using Domain.Interfaces.ValueObjects; + +namespace SubscriptionsDomain; + +public sealed class SubscriptionRoot : AggregateRootBase +{ + public static Result Create(IRecorder recorder, IIdentifierFactory idFactory, + Identifier organizationId, Identifier createdById) + { + var root = new SubscriptionRoot(recorder, idFactory); + root.RaiseCreateEvent(SubscriptionsDomain.Events.Created(root.Id, organizationId, createdById)); + return root; + } + + private SubscriptionRoot(IRecorder recorder, IIdentifierFactory idFactory) : base(recorder, idFactory) + { + } + + private SubscriptionRoot(IRecorder recorder, IIdentifierFactory idFactory, + ISingleValueObject identifier) : base( + recorder, idFactory, identifier) + { + } + + public Identifier CreatedById { get; private set; } = Identifier.Empty(); + + public Identifier OrganizationId { get; private set; } = Identifier.Empty(); + + public static AggregateRootFactory Rehydrate() + { + return (identifier, container, _) => new SubscriptionRoot(container.GetRequiredService(), + container.GetRequiredService(), identifier); + } + + public override Result EnsureInvariants() + { + var ensureInvariants = base.EnsureInvariants(); + if (!ensureInvariants.IsSuccessful) + { + return ensureInvariants.Error; + } + + //TODO: add your other invariant rules here + + return Result.Ok; + } + + protected override Result OnStateChanged(IDomainEvent @event, bool isReconstituting) + { + switch (@event) + { + case Created created: + { + OrganizationId = created.OrganizationId.ToId(); + CreatedById = created.CreatedById.ToId(); + return Result.Ok; + } + + default: + return HandleUnKnownStateChangedEvent(@event); + } + } +} \ No newline at end of file diff --git a/src/SubscriptionsDomain/SubscriptionsDomain.csproj b/src/SubscriptionsDomain/SubscriptionsDomain.csproj new file mode 100644 index 00000000..1001a123 --- /dev/null +++ b/src/SubscriptionsDomain/SubscriptionsDomain.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + + + + + <_Parameter1>$(AssemblyName).UnitTests + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + True + True + Resources.resx + + + + diff --git a/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs new file mode 100644 index 00000000..1b3884b6 --- /dev/null +++ b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsApiSpec.cs @@ -0,0 +1,24 @@ +using ApiHost1; +using IntegrationTesting.WebApi.Common; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace SubscriptionsInfrastructure.IntegrationTests; + +[Trait("Category", "Integration.API")] +[Collection("API")] +public class SubscriptionsApiSpec : WebApiSpec +{ + public SubscriptionsApiSpec(WebApiSetup setup) : base(setup, OverrideDependencies) + { + EmptyAllRepositories(); + } + + private static void OverrideDependencies(IServiceCollection services) + { + //TODO: remove this is method if you are not overriding any dependencies with any stubs + throw new NotImplementedException(); + } + + //TIP: type testm or testma to create a new test method +} \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj new file mode 100644 index 00000000..8964110a --- /dev/null +++ b/src/SubscriptionsInfrastructure.IntegrationTests/SubscriptionsInfrastructure.IntegrationTests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + true + + + + + + + + + + + + + + Always + + + + diff --git a/src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json b/src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json new file mode 100644 index 00000000..1f175195 --- /dev/null +++ b/src/SubscriptionsInfrastructure.IntegrationTests/appsettings.Testing.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "System": "Information", + "Microsoft": "Information" + } + }, + "ApplicationServices": { + "Persistence": { + "LocalMachineJsonFileStore": { + "RootPath": "./saastack/testing/subscriptions" + } + } + } +} \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs b/src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs new file mode 100644 index 00000000..fe85e854 --- /dev/null +++ b/src/SubscriptionsInfrastructure.UnitTests/Api/Subscriptions/Class1Spec.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace SubscriptionsInfrastructure.UnitTests.Api.Subscriptions; + +[Trait("Category", "Unit")] +public class Class1Spec +{ + //TODO: type testm or testma to create a new test method +} \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj b/src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj new file mode 100644 index 00000000..4f087f43 --- /dev/null +++ b/src/SubscriptionsInfrastructure.UnitTests/SubscriptionsInfrastructure.UnitTests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + true + + + + + + + + + + + + + diff --git a/src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs b/src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs new file mode 100644 index 00000000..60a2d2ec --- /dev/null +++ b/src/SubscriptionsInfrastructure/Api/Subscriptions/SubscriptionsApi.cs @@ -0,0 +1,20 @@ +using Infrastructure.Interfaces; +using Infrastructure.Web.Api.Interfaces; +using SubscriptionsApplication; + +namespace SubscriptionsInfrastructure.Api.Subscriptions; + +public class SubscriptionsApi : IWebApiService +{ + private readonly ICallerContextFactory _callerFactory; + private readonly ISubscriptionsApplication _subscriptionsApplication; + + public SubscriptionsApi(ICallerContextFactory callerFactory, ISubscriptionsApplication subscriptionsApplication) + { + _callerFactory = callerFactory; + _subscriptionsApplication = subscriptionsApplication; + } + + //TODO: Add your service operation methods here + //Tip: try: postapi and getapi +} \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs b/src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs new file mode 100644 index 00000000..9ae3ca12 --- /dev/null +++ b/src/SubscriptionsInfrastructure/Persistence/ReadModels/SubscriptionProjection.cs @@ -0,0 +1,38 @@ +using Application.Persistence.Common.Extensions; +using Application.Persistence.Interfaces; +using Common; +using Domain.Events.Shared.Subscriptions; +using Domain.Interfaces; +using Domain.Interfaces.Entities; +using Infrastructure.Persistence.Common; +using Infrastructure.Persistence.Interfaces; +using SubscriptionsApplication.Persistence.ReadModels; +using SubscriptionsDomain; + +namespace SubscriptionsInfrastructure.Persistence.ReadModels; + +public class SubscriptionProjection : IReadModelProjection +{ + private readonly IReadModelStore _subscriptions; + + public SubscriptionProjection(IRecorder recorder, IDomainFactory domainFactory, IDataStore store) + { + _subscriptions = new ReadModelStore(recorder, domainFactory, store); + } + + public Type RootAggregateType => typeof(SubscriptionRoot); + + public async Task> ProjectEventAsync(IDomainEvent changeEvent, + CancellationToken cancellationToken) + { + switch (changeEvent) + { + case Created e: + return await _subscriptions.HandleCreateAsync(e.RootId, dto => { dto.CreatedById = e.CreatedById; }, + cancellationToken); + + default: + return false; + } + } +} \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs b/src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs new file mode 100644 index 00000000..5ed03c31 --- /dev/null +++ b/src/SubscriptionsInfrastructure/Persistence/SubscriptionRepository.cs @@ -0,0 +1,7 @@ +using SubscriptionsApplication.Persistence; + +namespace SubscriptionsInfrastructure.Persistence; + +public class SubscriptionRepository : ISubscriptionRepository +{ +} \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure/Resources.Designer.cs b/src/SubscriptionsInfrastructure/Resources.Designer.cs new file mode 100644 index 00000000..f3c116f3 --- /dev/null +++ b/src/SubscriptionsInfrastructure/Resources.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SubscriptionsInfrastructure { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SubscriptionsInfrastructure.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/SubscriptionsInfrastructure/Resources.resx b/src/SubscriptionsInfrastructure/Resources.resx new file mode 100644 index 00000000..755958fe --- /dev/null +++ b/src/SubscriptionsInfrastructure/Resources.resx @@ -0,0 +1,27 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + \ No newline at end of file diff --git a/src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj b/src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj new file mode 100644 index 00000000..e5061771 --- /dev/null +++ b/src/SubscriptionsInfrastructure/SubscriptionsInfrastructure.csproj @@ -0,0 +1,38 @@ + + + + net8.0 + + + + + + + + + + + <_Parameter1>$(AssemblyName).UnitTests + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + True + True + Resources.resx + + + + diff --git a/src/SubscriptionsInfrastructure/SubscriptionsModule.cs b/src/SubscriptionsInfrastructure/SubscriptionsModule.cs new file mode 100644 index 00000000..48e2b183 --- /dev/null +++ b/src/SubscriptionsInfrastructure/SubscriptionsModule.cs @@ -0,0 +1,56 @@ +using System.Reflection; +using Application.Services.Shared; +using Common; +using Domain.Interfaces; +using Infrastructure.Hosting.Common.Extensions; +using Infrastructure.Persistence.Interfaces; +using Infrastructure.Web.Hosting.Common; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SubscriptionsApplication; +using SubscriptionsApplication.ApplicationServices; +using SubscriptionsApplication.Persistence; +using SubscriptionsDomain; +using SubscriptionsInfrastructure.Api.Subscriptions; +using SubscriptionsInfrastructure.Persistence; +using SubscriptionsInfrastructure.Persistence.ReadModels; + +namespace SubscriptionsInfrastructure; + +public class SubscriptionsModule : ISubdomainModule +{ + public Assembly InfrastructureAssembly => typeof(SubscriptionsApi).Assembly; + + public Assembly? DomainAssembly => typeof(SubscriptionRoot).Assembly; + + public Dictionary EntityPrefixes => new() + { + { typeof(SubscriptionRoot), "subscript" } + }; + + public Action> ConfigureMiddleware + { + get { return (app, _) => app.RegisterRoutes(); } + } + + public Action RegisterServices + { + get + { + return (_, services) => + { + services + .AddPerHttpRequest(); + services.AddPerHttpRequest(); + services.RegisterEventing( + c => new SubscriptionProjection(c.GetRequiredService(), + c.GetRequiredService(), + c.GetRequiredService()) + ); + + services.AddPerHttpRequest(); + }; + } + } +} \ No newline at end of file diff --git a/src/Tools.Templates/InfrastructureProject/.template.config/template.json b/src/Tools.Templates/InfrastructureProject/.template.config/template.json index 4a8db2ab..8a382aa9 100644 --- a/src/Tools.Templates/InfrastructureProject/.template.config/template.json +++ b/src/Tools.Templates/InfrastructureProject/.template.config/template.json @@ -20,8 +20,9 @@ "type": "parameter", "datatype": "string", "isRequired": true, - "description": "Name of the subdomain (singular, Title-cased)", - "replaces": "{SubDomainName}" + "description": "Name of the subdomain (Singular, Title-cased)", + "replaces": "{SubDomainName}", + "fileRename": "{SubDomainName}" }, "SubDomainNameLower": { "type": "generated", diff --git a/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs b/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs index 3d73619f..7d834bd8 100644 --- a/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs +++ b/src/Tools.Templates/InfrastructureProject/Api/{SubDomainName}s/{SubDomainName}sApi.cs @@ -1,15 +1,19 @@ +using Infrastructure.Interfaces; +using Infrastructure.Web.Api.Interfaces; + namespace ProjectName.Api.{SubDomainName}s; -public class {SubDomainName}sApi : IwebApiService +public class {SubDomainName}sApi : IWebApiService { private readonly I{SubDomainName}sApplication _{SubDomainNameLower}sApplication; private readonly ICallerContextFactory _callerFactory; - public CarsApi(ICallerContextFactory callerFactory, I{SubDomainName}sApplication {SubDomainNameLower}sApplication) + public {SubDomainName}sApi(ICallerContextFactory callerFactory, I{SubDomainName}sApplication {SubDomainNameLower}sApplication) { _callerFactory = callerFactory; _{SubDomainNameLower}sApplication = {SubDomainNameLower}sApplication; } //TODO: Add your service operation methods here + //Tip: try: postapi and getapi } \ No newline at end of file diff --git a/src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs b/src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs new file mode 100644 index 00000000..df361e86 --- /dev/null +++ b/src/Tools.Templates/InfrastructureProject/Persistence/ReadModels/{SubDomainName}Projection.cs @@ -0,0 +1,40 @@ +using Application.Persistence.Common.Extensions; +using Application.Persistence.Interfaces; +using Common; +using Domain.Interfaces; +using Domain.Interfaces.Entities; +using {SubDomainName}Application.Persistence.ReadModels; +using {SubDomainName}Domain; +using Infrastructure.Persistence.Common; +using Infrastructure.Persistence.Interfaces; + +namespace ProjectName.Persistence.ReadModels; + +public class {SubDomainName}Projection : IReadModelProjection +{ + private readonly IReadModelStore<{SubDomainName}> _{SubDomainNameLower}s; + + public {SubDomainName}Projection(IRecorder recorder, IDomainFactory domainFactory, IDataStore store) + { + _{SubDomainNameLower}s = new ReadModelStore<{SubDomainName}>(recorder, domainFactory, store); + } + + public Type RootAggregateType => typeof({SubDomainName}Root); + + public async Task> ProjectEventAsync(IDomainEvent changeEvent, + CancellationToken cancellationToken) + { + switch (changeEvent) + { + case Created e: + return await _{SubDomainNameLower}s.HandleCreateAsync(e.RootId, dto => + { + dto.UserId = e.UserId; + }, + cancellationToken); + + default: + return false; + } + } +} \ No newline at end of file diff --git a/src/Tools.Templates/InfrastructureProject/ProjectNameModule.cs b/src/Tools.Templates/InfrastructureProject/{SubDomainName}sModule.cs similarity index 79% rename from src/Tools.Templates/InfrastructureProject/ProjectNameModule.cs rename to src/Tools.Templates/InfrastructureProject/{SubDomainName}sModule.cs index 5712dea8..a5afb55c 100644 --- a/src/Tools.Templates/InfrastructureProject/ProjectNameModule.cs +++ b/src/Tools.Templates/InfrastructureProject/{SubDomainName}sModule.cs @@ -1,24 +1,23 @@ using System.Reflection; -using Application.Interfaces.Services; -using Infrastructure.Web.Hosting.Common; -using Common; using Domain.Interfaces; +using Infrastructure.Hosting.Common.Extensions; using Infrastructure.Persistence.Interfaces; using Infrastructure.Web.Hosting.Common; -using Infrastructure.Web.Hosting.Common.Extensions; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using ProjectName.Api.Subscriptions; +using ProjectName.Persistence; namespace ProjectName; -public class {SubDomainName}sModule : ISubDomainModule +public class {SubDomainName}sModule : ISubdomainModule { - public Assembly ApiAssembly => typeof({SubDomainName}sApi).Assembly; + public Assembly InfrastructureAssembly => typeof({SubDomainName}sApi).Assembly; public Assembly? DomainAssembly => typeof({SubDomainName}Root).Assembly; - public Dictionary AggregatePrefixes => new() + public Dictionary EntityPrefixes => new() { { typeof({SubDomainName}Root), "{SubDomainNameLower}" } };