From 20b8997575ecf67dae06c6e6eb16b0cbb3d1cd81 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 10 Jun 2022 14:10:31 -0300 Subject: [PATCH 01/51] refactor step --- src/abstractions/Backend.Fx/Backend.Fx.csproj | 3 +- .../Environment/DateAndTime/FrozenClock.cs | 3 +- .../AllTenantBackendFxApplicationInvoker.cs | 2 +- .../MultiTenancy/SingleTenantApplication.cs | 2 +- .../Environment/MultiTenancy/TenantEvent.cs | 10 +- .../Environment/MultiTenancy/TenantEvents.cs | 14 ++ .../Environment/MultiTenancy/TenantService.cs | 10 +- .../Persistence/BackendFxDbApplication.cs | 38 +---- .../Backend.Fx/Extensions/AsyncHelper.cs | 4 +- .../Backend.Fx/Extensions/ReflectionEx.cs | 12 ++ .../DataGeneratingApplication.cs | 108 +++++++------- .../DataGeneration/DataGenerationContext.cs | 17 ++- .../BackendFxApplication.cs | 54 +++---- .../BackendFxApplicationDecorator.cs | 54 +++++++ .../BackendFxApplicationInvoker.cs | 58 ++++---- .../DependencyInjection/DomainModule.cs | 140 ++++++++++++++++++ .../ExceptionLoggingAndHandlingInvoker.cs | 2 +- .../ExceptionLoggingAsyncInvoker.cs | 4 +- .../ExceptionLoggingInvoker.cs | 2 +- .../DependencyInjection/ICompositionRoot.cs | 18 +-- .../DependencyInjection/IInstanceProvider.cs | 37 ----- .../Patterns/DependencyInjection/IModule.cs | 4 +- .../InfrastructureModule.cs | 23 --- .../DependencyInjection/InjectionScope.cs | 30 ---- ...uentializingBackendFxApplicationInvoker.cs | 2 +- .../DependencyInjection/WaitForBootInvoker.cs | 2 +- .../Domain/DomainEventAggregator.cs | 9 +- .../Domain/IDomainEventHandlerProvider.cs | 14 -- .../Integration/DynamicSubscription.cs | 7 +- .../Integration/ISubscription.cs | 4 +- .../Integration/InMemoryMessageBusChannel.cs | 2 +- .../Integration/MessageBus.cs | 2 +- .../Integration/MessageBusScope.cs | 2 +- .../Integration/SingletonSubscription.cs | 6 +- .../Integration/TypedSubscription.cs | 6 +- .../Patterns/Jobs/JobApplication.cs | 14 ++ .../Backend.Fx/Patterns/Jobs/JobModule.cs | 28 ++++ .../BackendFxApplicationStartup.cs | 2 +- .../ErrorHandling/ErrorHandlingMiddleware.cs | 32 ++-- ...BackendFxApplicationControllerActivator.cs | 14 +- .../BackendFxApplicationHubActivator.cs | 10 +- ...kendFxApplicationViewComponentActivator.cs | 14 +- .../Mvc/Execution/FlushFilter.cs | 3 +- .../Mvc/HttpContextEx.cs | 27 ++-- .../Bootstrapping/EfCorePersistenceModule.cs | 8 + .../Bootstrapping/EfCorePersistenceModule.cs | 99 +++++++++---- .../MicrosoftCompositionRoot.cs | 55 +++++++ .../MicrosoftDependencyInjectionExtensions.cs | 20 +++ .../SimpleInjectorDataGenerationModule.cs | 23 --- .../Modules/SimpleInjectorDomainModule.cs | 122 --------------- .../SimpleInjectorInfrastructureModule.cs | 52 ------- .../Modules/SimpleInjectorModule.cs | 23 --- .../SimpleInjectorCompositionRoot.cs | 97 ++++++------ .../SimpleInjectorInjectionScope.cs | 24 --- .../SimpleInjectorInstanceProvider.cs | 38 ----- .../SimpleInjectorServiceScope.cs | 24 +++ 56 files changed, 712 insertions(+), 722 deletions(-) create mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs create mode 100644 src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs create mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs delete mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs delete mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs delete mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs delete mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs delete mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs delete mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs create mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScope.cs diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index 8e4bc2c5..ed369fa8 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -7,7 +7,7 @@ false false false - false + false @@ -32,6 +32,7 @@ + diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs index 22f3244a..b67d9fb8 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs @@ -12,10 +12,9 @@ public class FrozenClock : IClock { private static readonly ILogger Logger = Log.Create(); - // ReSharper disable once UnusedParameter.Local public FrozenClock(IClock clock) { - UtcNow = DateTime.UtcNow; + UtcNow = clock.UtcNow; Logger.LogTrace("Freezing clock at {UtcNow}", UtcNow); } diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs index 818a558f..9b4abccc 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs @@ -20,7 +20,7 @@ public AllTenantBackendFxApplicationInvoker(ITenantIdProvider tenantIdProvider, _invoker = invoker; } - public void Invoke(Action action) + public void Invoke(Action action) { var correlationId = Guid.NewGuid(); TenantId[] tenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds().Concat(_tenantIdProvider.GetActiveProductionTenantIds()).ToArray(); diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs index 640c3a38..6c28a0e5 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs @@ -17,7 +17,7 @@ public SingleTenantApplication( ITenantRepository tenantRepository, bool singleTenantIsDemoTenant) { - _tenantService = new TenantService(messageBus, tenantRepository); + _tenantService = new TenantService(tenantRepository); _singleTenantIsDemoTenant = singleTenantIsDemoTenant; } diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs index ec6590c4..5c59e9be 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs @@ -1,16 +1,16 @@ -using Backend.Fx.Patterns.EventAggregation.Integration; - -namespace Backend.Fx.Environment.MultiTenancy +namespace Backend.Fx.Environment.MultiTenancy { - public abstract class TenantEvent : IntegrationEvent + public abstract class TenantEvent { - protected TenantEvent(int tenantId, string name, string description, bool isDemoTenant) : base() + protected TenantEvent(int tenantId, string name, string description, bool isDemoTenant) { + TenantId = tenantId; Name = name; Description = description; IsDemoTenant = isDemoTenant; } + public int TenantId { get; } public string Name { get; } public string Description { get; } diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs new file mode 100644 index 00000000..20cbb70a --- /dev/null +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs @@ -0,0 +1,14 @@ +using System.Collections.Concurrent; + +namespace Backend.Fx.Environment.MultiTenancy +{ + public class TenantEvents + { + private readonly ConcurrentQueue _events = new ConcurrentQueue(); + + public void Publish(TenantEvent tenantEvent) + { + _events.Enqueue(tenantEvent); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs index b5959451..b89394cf 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs @@ -2,7 +2,6 @@ using System.Linq; using Backend.Fx.Exceptions; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Integration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -38,14 +37,12 @@ public interface ITenantService public class TenantService : ITenantService { private static readonly ILogger Logger = Log.Create(); - private readonly IMessageBus _messageBus; private readonly ITenantRepository _tenantRepository; public ITenantIdProvider TenantIdProvider { get; } - public TenantService(IMessageBus messageBus, ITenantRepository tenantRepository) + public TenantService(ITenantRepository tenantRepository) { - _messageBus = messageBus; _tenantRepository = tenantRepository; TenantIdProvider = new TenantServiceTenantIdProvider(this); } @@ -67,7 +64,6 @@ public TenantId CreateTenant(string name, string description, bool isDemonstrati var tenant = new Tenant(name, description, isDemonstrationTenant) { Configuration = configuration }; _tenantRepository.SaveTenant(tenant); var tenantId = new TenantId(tenant.Id); - _messageBus.Publish(new TenantActivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); return tenantId; } @@ -77,7 +73,6 @@ public void ActivateTenant(TenantId tenantId) Tenant tenant = _tenantRepository.GetTenant(tenantId); tenant.State = TenantState.Active; _tenantRepository.SaveTenant(tenant); - _messageBus.Publish(new TenantActivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); } public void DeactivateTenant(TenantId tenantId) @@ -86,7 +81,6 @@ public void DeactivateTenant(TenantId tenantId) Tenant tenant = _tenantRepository.GetTenant(tenantId); tenant.State = TenantState.Inactive; _tenantRepository.SaveTenant(tenant); - _messageBus.Publish(new TenantDeactivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); } public void DeleteTenant(TenantId tenantId) @@ -100,7 +94,6 @@ public void DeleteTenant(TenantId tenantId) } _tenantRepository.DeleteTenant(tenantId); - _messageBus.Publish(new TenantDeactivated(tenant.Id, tenant.Name, tenant.Description, tenant.IsDemoTenant)); } public Tenant GetTenant(TenantId tenantId) @@ -115,7 +108,6 @@ public Tenant UpdateTenant(TenantId tenantId, string name, string description, s tenant.Description = description; tenant.Configuration = configuration; _tenantRepository.SaveTenant(tenant); - _messageBus.Publish(new TenantUpdated(tenant.Id, name, description, tenant.IsDemoTenant)); return tenant; } diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs b/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs index 501273cb..6bd88112 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs @@ -1,58 +1,34 @@ -using System; using System.Threading; using System.Threading.Tasks; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Environment.Persistence { - public class BackendFxDbApplication : IBackendFxApplication + public class BackendFxDbApplication : BackendFxApplicationDecorator { private static readonly ILogger Logger = Log.Create(); - + private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; - private readonly IBackendFxApplication _backendFxApplication; private readonly IDatabaseBootstrapper _databaseBootstrapper; public BackendFxDbApplication(IDatabaseBootstrapper databaseBootstrapper, - IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, - IBackendFxApplication backendFxApplication) + IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, + IBackendFxApplication application) : base(application) { _databaseBootstrapper = databaseBootstrapper; _databaseAvailabilityAwaiter = databaseAvailabilityAwaiter; - _backendFxApplication = backendFxApplication; } - public void Dispose() - { - Logger.LogTrace("Disposing..."); - _backendFxApplication.Dispose(); - } - - public IBackendFxApplicationAsyncInvoker AsyncInvoker => _backendFxApplication.AsyncInvoker; - - public ICompositionRoot CompositionRoot => _backendFxApplication.CompositionRoot; - - public IBackendFxApplicationInvoker Invoker => _backendFxApplication.Invoker; - - public IMessageBus MessageBus => _backendFxApplication.MessageBus; - - public bool WaitForBoot(int timeoutMilliSeconds = Int32.MaxValue, CancellationToken cancellationToken = default) - { - Logger.LogTrace("Waiting for boot..."); - return _backendFxApplication.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); - public async Task BootAsync(CancellationToken cancellationToken = default) + public override async Task BootAsync(CancellationToken cancellationToken = default) { Logger.LogTrace("Booting..."); - await _databaseAvailabilityAwaiter.WaitForDatabase(cancellationToken); + await _databaseAvailabilityAwaiter.WaitForDatabase(cancellationToken).ConfigureAwait(false); _databaseBootstrapper.EnsureDatabaseExistence(); - await _backendFxApplication.BootAsync(cancellationToken); + await base.BootAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs b/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs index 46bf8915..2a9f552f 100644 --- a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs +++ b/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs @@ -26,7 +26,7 @@ public static void RunSync(Func task) { try { - await task(); + await task().ConfigureAwait(false); } catch (Exception e) { @@ -64,7 +64,7 @@ public static T RunSync(Func> task) { try { - ret = await task(); + ret = await task().ConfigureAwait(false); } catch (Exception e) { diff --git a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs index 1d357fa8..378ad0b6 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -6,6 +7,17 @@ namespace Backend.Fx.Extensions { public static class ReflectionEx { + public static IEnumerable GetImplementingTypes(this IEnumerable assemblies, Type serviceType) + { + return assemblies + .Distinct() + .Where(assembly => !assembly.IsDynamic) + .SelectMany(assembly => assembly.GetTypes(), (assembly, type) => new {assembly, type}) + .Where(t => t.type.IsClass && !t.type.IsAbstract) + .Where(t => serviceType.IsAssignableFrom(t.type)) + .Select(t => t.type); + } + public static bool IsImplementationOfOpenGenericInterface(this Type t, Type openGenericInterface) { return t.GetInterfaces().Any(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == openGenericInterface); diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs index 41fef787..25f7d82f 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs @@ -1,12 +1,13 @@ -using System; +using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Extensions; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using JetBrains.Annotations; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.DataGeneration @@ -15,60 +16,36 @@ namespace Backend.Fx.Patterns.DataGeneration /// Enriches the by calling all data generators for all tenants /// on application start and when a tenant gets activated /// - public class DataGeneratingApplication : IBackendFxApplication + public class DataGeneratingApplication : BackendFxApplicationDecorator { private static readonly ILogger Logger = Log.Create(); private readonly ITenantIdProvider _tenantIdProvider; private readonly IBackendFxApplication _application; - private readonly IModule _dataGenerationModule; - + public IDataGenerationContext DataGenerationContext { get; [UsedImplicitly] private set; } /// To be able to query all active demo/production tenants - /// To register the collection of IDataGenerator with the composition root. Internally, IInstanceProvider.GetInstances<IDataGenerator>() is being used /// to make sure data generation will never run in parallel for the same tenant /// the decorated instance public DataGeneratingApplication( ITenantIdProvider tenantIdProvider, - IModule dataGenerationModule, ITenantWideMutexManager tenantWideMutexManager, - IBackendFxApplication application) + IBackendFxApplication application) : base(application) { _tenantIdProvider = tenantIdProvider; _application = application; - _dataGenerationModule = dataGenerationModule; DataGenerationContext = new DataGenerationContext( _application.CompositionRoot, _application.Invoker, tenantWideMutexManager); } - public void Dispose() - { - _application.Dispose(); - } - - public IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; - public ICompositionRoot CompositionRoot => _application.CompositionRoot; - public IBackendFxApplicationInvoker Invoker => _application.Invoker; - - public IMessageBus MessageBus => _application.MessageBus; - - public bool WaitForBoot(int timeoutMilliSeconds = Int32.MaxValue, CancellationToken cancellationToken = default) - { - return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); - - public async Task BootAsync(CancellationToken cancellationToken = default) + public override async Task BootAsync(CancellationToken cancellationToken = default) { - _application.CompositionRoot.RegisterModules(_dataGenerationModule); - EnableDataGenerationForNewTenants(); - - await _application.BootAsync(cancellationToken); - + _application.CompositionRoot.RegisterModules(new DataGenerationModule(_application.Assemblies)); + //EnableDataGenerationForNewTenants(); + await base.BootAsync(cancellationToken).ConfigureAwait(false); SeedDataForAllActiveTenants(); } @@ -80,39 +57,58 @@ private void SeedDataForAllActiveTenants() foreach (TenantId prodTenantId in prodTenantIds) { DataGenerationContext.SeedDataForTenant(prodTenantId, false); - _application.MessageBus.Publish(new DataGenerated(prodTenantId.Value)); } var demoTenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds(); foreach (TenantId demoTenantId in demoTenantIds) { DataGenerationContext.SeedDataForTenant(demoTenantId, true); - _application.MessageBus.Publish(new DataGenerated(demoTenantId.Value)); } } } - private void EnableDataGenerationForNewTenants() + // todo refactor + // private void EnableDataGenerationForNewTenants() + // { + // _application..Subscribe(new DelegateIntegrationMessageHandler(tenantCreated => + // { + // Logger.LogInformation( + // "Seeding data for recently activated tenant (with demo data: {IsDemoTenant}) {TenantId}", + // tenantCreated.IsDemoTenant, + // tenantCreated.TenantId); + // try + // { + // DataGenerationContext.SeedDataForTenant(new TenantId(tenantCreated.TenantId), + // tenantCreated.IsDemoTenant); + // } + // catch (Exception ex) + // { + // Logger.LogError(ex, + // "Seeding data for recently activated tenant (with demo data: {IsDemoTenant}) {TenantId} failed", + // tenantCreated.IsDemoTenant, + // tenantCreated.TenantId); + // } + // })); + // } + } + + public class DataGenerationModule : IModule + { + private readonly Assembly[] _assemblies; + + public DataGenerationModule(Assembly[] assemblies) { - _application.MessageBus.Subscribe(new DelegateIntegrationMessageHandler(tenantCreated => - { - Logger.LogInformation( - "Seeding data for recently activated tenant (with demo data: {IsDemoTenant}) {TenantId}", - tenantCreated.IsDemoTenant, - tenantCreated.TenantId); - try - { - DataGenerationContext.SeedDataForTenant(new TenantId(tenantCreated.TenantId), - tenantCreated.IsDemoTenant); - } - catch (Exception ex) - { - Logger.LogError(ex, - "Seeding data for recently activated tenant (with demo data: {IsDemoTenant}) {TenantId} failed", - tenantCreated.IsDemoTenant, - tenantCreated.TenantId); - } - })); + _assemblies = assemblies; + } + + public void Register(ICompositionRoot compositionRoot) + { + ServiceDescriptor[] serviceDescriptors = _assemblies + .GetImplementingTypes(typeof(IDataGenerator)) + .Select(t => new ServiceDescriptor(typeof(IDataGenerator), t, ServiceLifetime.Scoped)) + .ToArray(); + + compositionRoot.RegisterServiceDescriptors(serviceDescriptors); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs index 0d5035ae..3129edcf 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs @@ -4,6 +4,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.DataGeneration @@ -39,13 +40,13 @@ public void SeedDataForTenant(TenantId tenantId, bool isDemoTenant) { using (Logger.LogInformationDuration($"Seeding data for tenant {tenantId.Value}")) { - Type[] dataGeneratorTypesToRun = GetDataGeneratorTypes(_compositionRoot, isDemoTenant); + Type[] dataGeneratorTypesToRun = GetDataGeneratorTypes(_compositionRoot.ServiceProvider, isDemoTenant); foreach (Type dataGeneratorTypeToRun in dataGeneratorTypesToRun) { - _invoker.Invoke(instanceProvider => + _invoker.Invoke(serviceProvider => { - IDataGenerator dataGenerator = instanceProvider - .GetInstances() + IDataGenerator dataGenerator = serviceProvider + .GetServices() .Single(dg => dg.GetType() == dataGeneratorTypeToRun); dataGenerator.Generate(); }, new SystemIdentity(), tenantId); @@ -54,13 +55,13 @@ public void SeedDataForTenant(TenantId tenantId, bool isDemoTenant) } } - private static Type[] GetDataGeneratorTypes(ICompositionRoot compositionRoot, bool includeDemoDataGenerators) + private static Type[] GetDataGeneratorTypes(IServiceProvider serviceProvider, bool includeDemoDataGenerators) { - using (IInjectionScope scope = compositionRoot.BeginScope()) + using (IServiceScope scope = serviceProvider.CreateScope()) { var dataGenerators = scope - .InstanceProvider - .GetInstances() + .ServiceProvider + .GetServices() .OrderBy(dg => dg.Priority) .Select(dg => dg.GetType()); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs index 22a2de4c..16bf5bb4 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs @@ -1,13 +1,9 @@ using System; -using System.Security.Principal; +using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -34,18 +30,17 @@ public interface IBackendFxApplication : IDisposable IBackendFxApplicationInvoker Invoker { get; } /// - /// The message bus to send and receive event messages + /// The global exception logger of this application /// - IMessageBus MessageBus { get; } + IExceptionLogger ExceptionLogger { get; } + + Assembly[] Assemblies { get; } /// /// allows synchronously awaiting application startup /// bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default); - [Obsolete("Use BootAsync()")] - Task Boot(CancellationToken cancellationToken = default); - /// /// Initializes and starts the application (async) /// @@ -63,50 +58,43 @@ public class BackendFxApplication : IBackendFxApplication /// Initializes the application's runtime instance /// /// The composition root of the dependency injection framework - /// The message bus implementation used by this application instance /// - public BackendFxApplication(ICompositionRoot compositionRoot, IMessageBus messageBus, IExceptionLogger exceptionLogger) + public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger exceptionLogger, params Assembly[] assemblies) { var invoker = new BackendFxApplicationInvoker(compositionRoot); AsyncInvoker = new ExceptionLoggingAsyncInvoker(exceptionLogger, invoker); Invoker = new ExceptionLoggingInvoker(exceptionLogger, invoker); - MessageBus = messageBus; - MessageBus.ProvideInvoker(new SequentializingBackendFxApplicationInvoker( - new WaitForBootInvoker(this, - new ExceptionLoggingAndHandlingInvoker(exceptionLogger, Invoker)))); + CompositionRoot = compositionRoot; - CompositionRoot.InfrastructureModule.RegisterScoped(); - CompositionRoot.InfrastructureModule.RegisterScoped, CurrentCorrelationHolder>(); - CompositionRoot.InfrastructureModule.RegisterScoped, CurrentIdentityHolder>(); - CompositionRoot.InfrastructureModule.RegisterScoped, CurrentTenantIdHolder>(); - CompositionRoot.InfrastructureModule.RegisterScoped(); - CompositionRoot.InfrastructureModule.RegisterScoped(() => new DomainEventAggregator(compositionRoot)); - CompositionRoot.InfrastructureModule.RegisterScoped( - () => new MessageBusScope( - MessageBus, - compositionRoot.InstanceProvider.GetInstance>(), - compositionRoot.InstanceProvider.GetInstance>())); + ExceptionLogger = exceptionLogger; + Assemblies = assemblies.Concat(new[] {typeof(BackendFxApplication).Assembly}).Distinct().ToArray(); + CompositionRoot.RegisterModules(new DomainModule(Assemblies)); } + public Assembly[] Assemblies { get; } + public IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } public ICompositionRoot CompositionRoot { get; } + public IExceptionLogger ExceptionLogger { get; } + public IBackendFxApplicationInvoker Invoker { get; } - public IMessageBus MessageBus { get; } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); - public Task BootAsync(CancellationToken cancellationToken = default) { Logger.LogInformation("Booting application"); CompositionRoot.Verify(); - MessageBus.Connect(); _isBooted.Set(); return Task.CompletedTask; } + public TBackendFxApplication As() + where TBackendFxApplication : class, IBackendFxApplication + { + return this as TBackendFxApplication; + } + public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default) { return _isBooted.Wait(timeoutMilliSeconds, cancellationToken); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs new file mode 100644 index 00000000..d36c5423 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs @@ -0,0 +1,54 @@ +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Logging; + +namespace Backend.Fx.Patterns.DependencyInjection +{ + public abstract class BackendFxApplicationDecorator : IBackendFxApplication + { + private readonly IBackendFxApplication _application; + + protected BackendFxApplicationDecorator(IBackendFxApplication application) + { + _application = application; + } + + public Assembly[] Assemblies => _application.Assemblies; + + public IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; + + public ICompositionRoot CompositionRoot => _application.CompositionRoot; + + public IExceptionLogger ExceptionLogger => _application.ExceptionLogger; + + public IBackendFxApplicationInvoker Invoker => _application.Invoker; + + public virtual bool WaitForBoot( + int timeoutMilliSeconds = int.MaxValue, + CancellationToken cancellationToken = default) + { + return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); + } + + public virtual Task BootAsync(CancellationToken cancellationToken = default) + { + return _application.BootAsync(cancellationToken); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _application?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs index bdb09522..93042bff 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs @@ -7,6 +7,7 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.EventAggregation.Integration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -18,7 +19,7 @@ public interface IBackendFxApplicationAsyncInvoker /// The acting identity /// The targeted tenant id /// The correlation id, when it was continued - Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null); + Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null); } @@ -28,7 +29,7 @@ public interface IBackendFxApplicationInvoker /// The acting identity /// The targeted tenant id /// The correlation id, when it was continued - void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null); + void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null); } @@ -43,19 +44,19 @@ public BackendFxApplicationInvoker(ICompositionRoot compositionRoot) } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { Logger.LogInformation("Invoking synchronous action as {Identity} in {TenantId}", identity, tenantId); - using (IInjectionScope injectionScope = BeginScope(identity, tenantId, correlationId)) + using (IServiceScope serviceScope = BeginScope(identity, tenantId, correlationId)) { - using (UseDurationLogger(injectionScope)) + using (UseDurationLogger(serviceScope)) { - var operation = injectionScope.InstanceProvider.GetInstance(); + var operation = serviceScope.ServiceProvider.GetRequiredService(); try { operation.Begin(); - action.Invoke(injectionScope.InstanceProvider); - injectionScope.InstanceProvider.GetInstance().RaiseEvents(); + action.Invoke(serviceScope.ServiceProvider); + serviceScope.ServiceProvider.GetRequiredService().RaiseEvents(); operation.Complete(); } catch @@ -63,26 +64,23 @@ public void Invoke(Action action, IIdentity identity, TenantI operation.Cancel(); throw; } - - var messageBusScope = injectionScope.InstanceProvider.GetInstance(); - AsyncHelper.RunSync(() => messageBusScope.RaiseEvents()); } } } - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { Logger.LogInformation("Invoking asynchronous action as {Identity} in {TenantId}", identity, tenantId); - using (IInjectionScope injectionScope = BeginScope(identity, tenantId, correlationId)) + using (IServiceScope serviceScope = BeginScope(identity, tenantId, correlationId)) { - using (UseDurationLogger(injectionScope)) + using (UseDurationLogger(serviceScope)) { - var operation = injectionScope.InstanceProvider.GetInstance(); + var operation = serviceScope.ServiceProvider.GetRequiredService(); try { operation.Begin(); - await awaitableAsyncAction.Invoke(injectionScope.InstanceProvider); - injectionScope.InstanceProvider.GetInstance().RaiseEvents(); + await awaitableAsyncAction.Invoke(serviceScope.ServiceProvider).ConfigureAwait(false); + serviceScope.ServiceProvider.GetRequiredService().RaiseEvents(); operation.Complete(); } catch @@ -91,38 +89,38 @@ public async Task InvokeAsync(Func awaitableAsyncAction throw; } - await injectionScope.InstanceProvider.GetInstance().RaiseEvents(); + await serviceScope.ServiceProvider.GetRequiredService().RaiseEvents().ConfigureAwait(false); } } } - private IInjectionScope BeginScope(IIdentity identity, TenantId tenantId, Guid? correlationId) + private IServiceScope BeginScope(IIdentity identity, TenantId tenantId, Guid? correlationId) { - IInjectionScope injectionScope = _compositionRoot.BeginScope(); + IServiceScope serviceScope = _compositionRoot.BeginScope(); tenantId = tenantId ?? new TenantId(null); - injectionScope.InstanceProvider.GetInstance>().ReplaceCurrent(tenantId); + serviceScope.ServiceProvider.GetRequiredService>().ReplaceCurrent(tenantId); identity = identity ?? new AnonymousIdentity(); - injectionScope.InstanceProvider.GetInstance>().ReplaceCurrent(identity); + serviceScope.ServiceProvider.GetRequiredService>().ReplaceCurrent(identity); if (correlationId.HasValue) { - injectionScope.InstanceProvider.GetInstance>().Current.Resume(correlationId.Value); + serviceScope.ServiceProvider.GetRequiredService>().Current.Resume(correlationId.Value); } - return injectionScope; + return serviceScope; } - private static IDisposable UseDurationLogger(IInjectionScope injectionScope) + private static IDisposable UseDurationLogger(IServiceScope serviceScope) { - IIdentity identity = injectionScope.InstanceProvider.GetInstance>().Current; - TenantId tenantId = injectionScope.InstanceProvider.GetInstance>().Current; - Correlation correlation = injectionScope.InstanceProvider.GetInstance>().Current; + IIdentity identity = serviceScope.ServiceProvider.GetRequiredService>().Current; + TenantId tenantId = serviceScope.ServiceProvider.GetRequiredService>().Current; + Correlation correlation = serviceScope.ServiceProvider.GetRequiredService>().Current; return Logger.LogInformationDuration( - $"Starting scope {injectionScope.SequenceNumber} (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}", - $"Ended scope {injectionScope.SequenceNumber} (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}"); + $"Starting scope (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}", + $"Ended scope (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}"); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs new file mode 100644 index 00000000..10491739 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs @@ -0,0 +1,140 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Security.Principal; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Extensions; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Backend.Fx.Patterns.DependencyInjection +{ + /// + /// Wires all public domain services to be injected as scoped instances provided by the array of domain assemblies: + /// - s + /// - s + /// - s + /// - s + /// + public class DomainModule : IModule + { + private static readonly ILogger Logger = Log.Create(); + private readonly Assembly[] _assemblies; + private readonly string _assembliesForLogging; + + public DomainModule(params Assembly[] assemblies) + { + _assemblies = assemblies; + _assembliesForLogging = string.Join(",", _assemblies.Select(ass => ass.GetName().Name)); + } + + public void Register(ICompositionRoot compositionRoot) + { + RegisterDomainInfrastructureServices(compositionRoot); + + RegisterDomainAndApplicationServices(compositionRoot); + + RegisterAuthorization(compositionRoot); + + RegisterDomainEventHandlers(compositionRoot); + } + + private static void RegisterDomainInfrastructureServices(ICompositionRoot compositionRoot) + { + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(typeof(IClock), _ => new WallClock(), ServiceLifetime.Scoped)); + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(typeof(IOperation), _ => new Operation(), ServiceLifetime.Scoped)); + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentCorrelationHolder(), + ServiceLifetime.Scoped)); + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentIdentityHolder(), + ServiceLifetime.Scoped)); + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentTenantIdHolder(), + ServiceLifetime.Scoped)); + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(typeof(IDomainEventAggregator), sp => new DomainEventAggregator(sp), + ServiceLifetime.Scoped)); + } + + private void RegisterDomainAndApplicationServices(ICompositionRoot container) + { + Logger.LogDebug("Registering domain and application services from {Assemblies}", _assembliesForLogging); + + var serviceDescriptors = _assemblies.GetImplementingTypes(typeof(IDomainService)) + .Concat(_assemblies.GetImplementingTypes(typeof(IApplicationService))) + .SelectMany(type => + type.GetTypeInfo() + .ImplementedInterfaces + .Where(i => typeof(IDomainService) != i + && typeof(IApplicationService) != i + && (i.Namespace != null && i.Namespace.StartsWith("Backend") + || _assemblies.Contains(i.GetTypeInfo().Assembly))) + .Select(service => new ServiceDescriptor(service, type, ServiceLifetime.Scoped))); + + + foreach (var serviceDescriptor in serviceDescriptors) + { + Logger.LogDebug("Registering scoped service {ServiceType} with implementation {ImplementationType}", + serviceDescriptor.ServiceType.Name, + serviceDescriptor.ImplementationType.Name); + container.RegisterServiceDescriptor(serviceDescriptor); + } + } + + /// + /// Auto registering all aggregate authorization classes + /// + private void RegisterAuthorization(ICompositionRoot compositionRoot) + { + Logger.LogDebug("Registering authorization services from {Assemblies}", _assembliesForLogging); + var aggregateRootAuthorizationTypes = + _assemblies.GetImplementingTypes(typeof(IAggregateAuthorization<>)).ToArray(); + + foreach (Type aggregateAuthorizationType in aggregateRootAuthorizationTypes) + { + var serviceTypes = aggregateAuthorizationType + .GetTypeInfo() + .ImplementedInterfaces + .Where(i => i.GetTypeInfo().IsGenericType + && i.GenericTypeArguments.Length == 1 + && typeof(AggregateRoot).GetTypeInfo() + .IsAssignableFrom(i.GenericTypeArguments[0].GetTypeInfo())); + + foreach (Type serviceType in serviceTypes) + { + Logger.LogDebug( + "Registering scoped authorization service {ServiceType} with implementation {ImplementationType}", + serviceType.Name, + aggregateAuthorizationType.Name); + compositionRoot.RegisterServiceDescriptor(new ServiceDescriptor(serviceType, + aggregateAuthorizationType, ServiceLifetime.Scoped)); + } + } + } + + private void RegisterDomainEventHandlers(ICompositionRoot compositionRoot) + { + foreach (Type domainEventType in _assemblies.GetImplementingTypes(typeof(IDomainEvent))) + { + Type handlerTypeForThisDomainEventType = typeof(IDomainEventHandler<>).MakeGenericType(domainEventType); + + var serviceDescriptors = _assemblies + .GetImplementingTypes(handlerTypeForThisDomainEventType) + .Select(t => new ServiceDescriptor(handlerTypeForThisDomainEventType, t, ServiceLifetime.Scoped)) + .ToArray(); + + compositionRoot.RegisterServiceDescriptors(serviceDescriptors); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs index 2616f0aa..1e3539f9 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs @@ -16,7 +16,7 @@ public ExceptionLoggingAndHandlingInvoker(IExceptionLogger exceptionLogger, IBac _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { try { diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs index f87ede34..733d5414 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs @@ -17,11 +17,11 @@ public ExceptionLoggingAsyncInvoker(IExceptionLogger exceptionLogger, IBackendFx _invoker = invoker; } - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { try { - await _invoker.InvokeAsync(awaitableAsyncAction, identity, tenantId, correlationId); + await _invoker.InvokeAsync(awaitableAsyncAction, identity, tenantId, correlationId).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs index 04097ea4..fb91a301 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs @@ -16,7 +16,7 @@ public ExceptionLoggingInvoker(IExceptionLogger exceptionLogger, IBackendFxAppli _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { try { diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs index 2dcfa214..c32de752 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs @@ -1,5 +1,6 @@ using System; -using Backend.Fx.Patterns.EventAggregation.Domain; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.Patterns.DependencyInjection { @@ -9,22 +10,21 @@ namespace Backend.Fx.Patterns.DependencyInjection /// the domain or application logic, this would result in the Service Locator anti pattern, described here: /// http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ /// - public interface ICompositionRoot : IDisposable, IDomainEventHandlerProvider + public interface ICompositionRoot : IDisposable { void Verify(); void RegisterModules(params IModule[] modules); + + void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor); + + void RegisterServiceDescriptors(IEnumerable serviceDescriptors); - IInjectionScope BeginScope(); + IServiceScope BeginScope(); /// /// Access to the container's resolution functionality /// - IInstanceProvider InstanceProvider { get; } - - /// - /// Access to the container's configuration functionality - /// - IInfrastructureModule InfrastructureModule { get; } + IServiceProvider ServiceProvider { get; } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs deleted file mode 100644 index f5af5265..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IInstanceProvider.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - public interface IInstanceProvider - { - /// - /// Gets a service instance valid for the scope by providing its type - /// - /// - /// - object GetInstance(Type serviceType); - - /// - /// Gets all service instances valid for the scope by providing their type - /// - /// - /// - IEnumerable GetInstances(Type serviceType); - - /// - /// Gets a service instance valid for the scope by providing its type via generic type parameter - /// - /// - /// - T GetInstance() where T : class; - - /// - /// Gets all service instances valid for the scope by providing their type via generic type parameter - /// - /// - /// - IEnumerable GetInstances() where T : class; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs index 762f2042..3cacb956 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs @@ -1,4 +1,6 @@ -namespace Backend.Fx.Patterns.DependencyInjection +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Patterns.DependencyInjection { /// /// A logically cohesive bunch of services diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs deleted file mode 100644 index 8dc57a87..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InfrastructureModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Reflection; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - public interface IInfrastructureModule - { - void RegisterScoped() - where TImpl : class, TService - where TService : class; - - void RegisterScoped(Func factory) - where TService : class; - - void RegisterScoped(Type serviceType, Type implementationType); - void RegisterScoped(Type serviceType, Assembly[] assembliesToScan); - - void RegisterDecorator() where TService : class where TImpl : class, TService; - - void RegisterSingleton() where TService : class where TImpl : class, TService; - void RegisterInstance(TService instance) where TService : class; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs deleted file mode 100644 index 0cf52b1c..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/InjectionScope.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - /// - /// During a scope, services by default are singletons. Scopes may exist in parallel, providing totally separate singleton - /// instances for every scope. - /// - public interface IInjectionScope : IDisposable - { - int SequenceNumber { get; } - - IInstanceProvider InstanceProvider { get; } - } - - - public abstract class InjectionScope : IInjectionScope - { - protected InjectionScope(int sequenceNumber) - { - SequenceNumber = sequenceNumber; - } - - public int SequenceNumber { get; } - - public abstract IInstanceProvider InstanceProvider { get; } - - public abstract void Dispose(); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs index 893aac1b..c482961b 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs @@ -18,7 +18,7 @@ public SequentializingBackendFxApplicationInvoker(IBackendFxApplicationInvoker b } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { lock (_syncLock) { diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs index 3dcecb9f..07326cd3 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs @@ -15,7 +15,7 @@ public WaitForBootInvoker(IBackendFxApplication application, IBackendFxApplicati _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { _application.WaitForBoot(); _invoker.Invoke(action, identity, tenantId, correlationId); diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs index b6b7521b..aec2f398 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using Backend.Fx.Logging; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -23,12 +24,12 @@ public HandleAction(string domainEventName, string handlerTypeName, Action actio } private static readonly ILogger Logger = Log.Create(); - private readonly IDomainEventHandlerProvider _domainEventHandlerProvider; + private readonly IServiceProvider _serviceProvider; private readonly ConcurrentQueue _handleActions = new ConcurrentQueue(); - public DomainEventAggregator(IDomainEventHandlerProvider domainEventHandlerProvider) + public DomainEventAggregator(IServiceProvider serviceProvider) { - _domainEventHandlerProvider = domainEventHandlerProvider; + _serviceProvider = serviceProvider; } /// @@ -39,7 +40,7 @@ public DomainEventAggregator(IDomainEventHandlerProvider domainEventHandlerProvi /// public void PublishDomainEvent(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent { - foreach (var injectedHandler in _domainEventHandlerProvider.GetAllEventHandlers()) + foreach (var injectedHandler in _serviceProvider.GetServices>()) { var handleAction = new HandleAction( typeof(TDomainEvent).Name, diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs deleted file mode 100644 index c500524e..00000000 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandlerProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Backend.Fx.Patterns.EventAggregation.Domain -{ - using System.Collections.Generic; - - public interface IDomainEventHandlerProvider - { - /// - /// get all domain event handlers that want to handle a specific domain event - /// - /// - /// - IEnumerable> GetAllEventHandlers() where TDomainEvent : IDomainEvent; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs index c108f4b8..9a545064 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs @@ -1,10 +1,11 @@ using System; using Backend.Fx.Extensions; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; + namespace Backend.Fx.Patterns.EventAggregation.Integration { public class DynamicSubscription : ISubscription @@ -17,10 +18,10 @@ public DynamicSubscription(Type handlerType) _handlerType = handlerType; } - public void Process(IInstanceProvider instanceProvider, EventProcessingContext context) + public void Process(IServiceProvider serviceProvider, EventProcessingContext context) { Logger.LogInformation("Getting subscribed handler instance of type {HandlerTypeName}", _handlerType.Name); - object handlerInstance = instanceProvider.GetInstance(_handlerType); + object handlerInstance = serviceProvider.GetRequiredService(_handlerType); using (Logger.LogInformationDuration($"Invoking subscribed handler {_handlerType.GetDetailedTypeName()}")) { ((IIntegrationMessageHandler) handlerInstance).Handle(context.DynamicEvent); diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs index 91ca9a39..eb61d569 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs @@ -1,10 +1,10 @@ -using Backend.Fx.Patterns.DependencyInjection; +using System; namespace Backend.Fx.Patterns.EventAggregation.Integration { public interface ISubscription { - void Process(IInstanceProvider instanceProvider, EventProcessingContext context); + void Process(IServiceProvider serviceProvider, EventProcessingContext context); bool Matches(object handler); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs index 30d17f64..7e88a60f 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs @@ -21,7 +21,7 @@ public async Task FinishHandlingAllMessagesAsync() { while (_messageHandlingTasks.TryTake(out var messageHandlingTask)) { - await messageHandlingTask; + await messageHandlingTask.ConfigureAwait(false); } } diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs index 1698f4ca..0a5b1fd2 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs @@ -143,7 +143,7 @@ protected void Process(string eventName, EventProcessingContext context) try { _invoker.Invoke( - instanceProvider => subscription.Process(instanceProvider, context), + serviceProvider => subscription.Process(serviceProvider, context), new SystemIdentity(), context.TenantId, context.CorrelationId); diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs index 396513a5..40e90e42 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs @@ -46,7 +46,7 @@ public async Task RaiseEvents() { while (_integrationEvents.TryDequeue(out IIntegrationEvent integrationEvent)) { - await _messageBus.Publish(integrationEvent); + await _messageBus.Publish(integrationEvent).ConfigureAwait(false); } } } diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs index 62966b36..1210f2f4 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs @@ -1,5 +1,5 @@ -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using System; +using Backend.Fx.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.EventAggregation.Integration @@ -14,7 +14,7 @@ public SingletonSubscription(IIntegrationMessageHandler handler) _handler = handler; } - public void Process(IInstanceProvider instanceProvider, EventProcessingContext context) + public void Process(IServiceProvider serviceProvider, EventProcessingContext context) { using (Logger.LogInformationDuration($"Invoking subscribed handler {_handler.GetType().Name}")) { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs index 0aa27c8f..f802a63b 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs @@ -3,7 +3,7 @@ using System.Reflection; using Backend.Fx.Extensions; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -21,14 +21,14 @@ public TypedSubscription(Type handlerType, Type eventType) _eventType = eventType; } - public void Process(IInstanceProvider instanceProvider, EventProcessingContext context) + public void Process(IServiceProvider serviceProvider, EventProcessingContext context) { IIntegrationEvent integrationEvent = context.GetTypedEvent(_eventType); MethodInfo handleMethod = _handlerType.GetRuntimeMethod("Handle", new[] {_eventType}); Debug.Assert(handleMethod != null, $"No method with signature `Handle({_eventType.Name} event)` found on {_handlerType.Name}"); Logger.LogInformation("Getting subscribed handler instance of type {HandlerTypeName}", _handlerType.Name); - object handlerInstance = instanceProvider.GetInstance(_handlerType); + object handlerInstance = serviceProvider.GetRequiredService(_handlerType); using (Logger.LogInformationDuration($"Invoking subscribed handler {_handlerType.GetDetailedTypeName()}")) { diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs b/src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs new file mode 100644 index 00000000..ce62a31d --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs @@ -0,0 +1,14 @@ +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.Patterns.Jobs +{ + + public class JobApplication : BackendFxApplicationDecorator + { + public JobApplication(IBackendFxApplication application) + : base(application) + { + application.CompositionRoot.RegisterModules(new JobModule(application.Assemblies)); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs b/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs new file mode 100644 index 00000000..61a519db --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Patterns.Jobs +{ + public class JobModule : IModule + { + private readonly Assembly[] _assemblies; + + public JobModule(Assembly[] assemblies) + { + _assemblies = assemblies; + } + + public void Register(ICompositionRoot compositionRoot) + { + // all jobs are dynamically registered + foreach (Type jobType in _assemblies.GetImplementingTypes(typeof(IJob))) + { + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)); + } + } + } +} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs index da15bc23..9a629d69 100644 --- a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs +++ b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs @@ -32,7 +32,7 @@ public static void UseBackendFxApplication(th application.WaitForBoot(); // set the instance provider for the controller activator - context.SetCurrentInstanceProvider(application.CompositionRoot.InstanceProvider); + context.SetCurrentServiceProvider(application.CompositionRoot.ServiceProvider); // the ambient tenant id has been set before by a TenantMiddleware var tenantId = context.GetTenantId(); diff --git a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs index c8bfdf9e..07056d06 100644 --- a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/ErrorHandlingMiddleware.cs @@ -33,42 +33,42 @@ public async Task Invoke(HttpContext context) { await _next.Invoke(context); } - catch (TooManyRequestsException tmrex) + catch (TooManyRequestsException tooManyRequestsException) { - if (tmrex.RetryAfter > 0) + if (tooManyRequestsException.RetryAfter > 0) { - context.Response.Headers.Add("Retry-After", tmrex.RetryAfter.ToString(CultureInfo.InvariantCulture)); + context.Response.Headers.Add("Retry-After", tooManyRequestsException.RetryAfter.ToString(CultureInfo.InvariantCulture)); } - await HandleClientError(context, 429, "TooManyRequests", tmrex); + await HandleClientError(context, 429, "TooManyRequests", tooManyRequestsException); } catch (UnprocessableException uex) { await HandleClientError(context, 422, "Unprocessable", uex); } - catch (NotFoundException nfex) + catch (NotFoundException notFoundException) { - await HandleClientError(context, (int) HttpStatusCode.NotFound, HttpStatusCode.NotFound.ToString(), nfex); + await HandleClientError(context, (int) HttpStatusCode.NotFound, HttpStatusCode.NotFound.ToString(), notFoundException); } - catch (ConflictedException confex) + catch (ConflictedException conflictedException) { - await HandleClientError(context, (int) HttpStatusCode.Conflict, HttpStatusCode.Conflict.ToString(), confex); + await HandleClientError(context, (int) HttpStatusCode.Conflict, HttpStatusCode.Conflict.ToString(), conflictedException); } - catch (ForbiddenException uex) + catch (ForbiddenException forbiddenException) { - await HandleClientError(context, (int) HttpStatusCode.Forbidden, HttpStatusCode.Forbidden.ToString(), uex); + await HandleClientError(context, (int) HttpStatusCode.Forbidden, HttpStatusCode.Forbidden.ToString(), forbiddenException); } - catch (UnauthorizedException uex) + catch (UnauthorizedException unauthorizedException) { - await HandleClientError(context, (int) HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized.ToString(), uex); + await HandleClientError(context, (int) HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized.ToString(), unauthorizedException); } - catch (ClientException cex) + catch (ClientException clientException) { - await HandleClientError(context, (int) HttpStatusCode.BadRequest, HttpStatusCode.BadRequest.ToString(), cex); + await HandleClientError(context, (int) HttpStatusCode.BadRequest, HttpStatusCode.BadRequest.ToString(), clientException); } - catch (Exception ex) + catch (Exception exception) { - await HandleServerError(context, ex); + await HandleServerError(context, exception); } } else diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs index c6f4d4a4..ee5e2f76 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs @@ -1,15 +1,15 @@ using System; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.AspNetCore.Mvc.Activators { /// - /// This controller activator relies on an set before in the + /// This controller activator relies on an set before in the /// http context items dictionary. If non is to be found, the controller is activated /// using the default (without providing any ctor arguments). /// @@ -21,15 +21,15 @@ public virtual object Create(ControllerContext c) { var requestedControllerType = c.ActionDescriptor.ControllerTypeInfo.AsType(); - return c.HttpContext.TryGetInstanceProvider(out var ip) - ? CreateInstanceUsingInstanceProvider(ip, requestedControllerType) + return c.HttpContext.TryGetServiceProvider(out var ip) + ? CreateInstanceUsingServiceProvider(ip, requestedControllerType) : CreateInstanceUsingSystemActivator(requestedControllerType); } - private static object CreateInstanceUsingInstanceProvider(object ip, Type requestedControllerType) + private static object CreateInstanceUsingServiceProvider(object sp, Type requestedControllerType) { - Logger.LogDebug("Providing {ControllerTypeName} using {InstanceProvider}", requestedControllerType.Name, ip.GetType().Name); - return ((IInstanceProvider)ip).GetInstance(requestedControllerType); + Logger.LogDebug("Providing {ControllerTypeName} using {ServiceProvider}", requestedControllerType.Name, sp.GetType().Name); + return ((IServiceProvider)sp).GetRequiredService(requestedControllerType); } private static object CreateInstanceUsingSystemActivator(Type requestedControllerType) diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs index e34fb0b6..aa04fa25 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs @@ -2,6 +2,7 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -11,19 +12,18 @@ public class BackendFxApplicationHubActivator : IHubActivator where T : Hu { private readonly IBackendFxApplication _backendFxApplication; private static readonly ILogger Logger = Log.Create>(); - + public BackendFxApplicationHubActivator(IBackendFxApplication backendFxApplication) { _backendFxApplication = backendFxApplication; } - public T Create() { - var ip = _backendFxApplication.CompositionRoot.InstanceProvider; - Logger.LogDebug("Providing {HubTypeName} using {InstanceProvider}", typeof(T).Name, ip.GetType().Name); - return ip.GetInstance(); + var sp = _backendFxApplication.CompositionRoot.ServiceProvider; + Logger.LogDebug("Providing {HubTypeName} using {ServiceProvider}", typeof(T).Name, sp.GetType().Name); + return sp.GetRequiredService(); } public void Release(T hub) diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs index d9eb8efb..2bce7e08 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs @@ -1,7 +1,7 @@ using System; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.AspNetCore.Mvc.ViewComponents; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -15,20 +15,20 @@ public object Create(ViewComponentContext context) { var requestedViewComponentType = context.ViewComponentDescriptor.TypeInfo.AsType(); - return context.ViewContext.HttpContext.TryGetInstanceProvider(out var ip) - ? CreateInstanceUsingInstanceProvider(ip, requestedViewComponentType) + return context.ViewContext.HttpContext.TryGetServiceProvider(out var sp) + ? CreateInstanceUsingServiceProvider(sp, requestedViewComponentType) : CreateInstanceUsingSystemActivator(requestedViewComponentType); } - private static object CreateInstanceUsingInstanceProvider(object ip, Type requestedViewComponentType) + private static object CreateInstanceUsingServiceProvider(object sp, Type requestedViewComponentType) { - Logger.LogDebug("Providing {ViewComponentName} using {InstanceProvider}", requestedViewComponentType.Name, ip.GetType().Name); - return ((IInstanceProvider)ip).GetInstance(requestedViewComponentType); + Logger.LogDebug("Providing {ViewComponentName} using {ServiceProvider}", requestedViewComponentType.Name, sp.GetType().Name); + return ((IServiceProvider)sp).GetRequiredService(requestedViewComponentType); } private static object CreateInstanceUsingSystemActivator(Type requestedViewComponentType) { - Logger.LogDebug("Providing {ViewComponentName} using {InstanceProvider}", requestedViewComponentType.Name, nameof(Activator)); + Logger.LogDebug("Providing {ViewComponentName} using {ServiceProvider}", requestedViewComponentType.Name, nameof(Activator)); return Activator.CreateInstance(requestedViewComponentType); } diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs index 2691e7a2..315a9247 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs @@ -1,5 +1,6 @@ using Backend.Fx.Environment.Persistence; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.AspNetCore.Mvc.Execution { @@ -14,7 +15,7 @@ public void OnActionExecuting(ActionExecutingContext context) public void OnActionExecuted(ActionExecutedContext context) { - context.HttpContext.GetInstanceProvider().GetInstance().Flush(); + context.HttpContext.GetServiceProvider().GetRequiredService().Flush(); } } } \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs index e8185a3e..1b840ce2 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/HttpContextEx.cs @@ -1,42 +1,41 @@ using System; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.AspNetCore.Http; namespace Backend.Fx.AspNetCore.Mvc { public static class HttpContextEx { - private const string InstanceProvider = nameof(InstanceProvider); + private const string ServiceProvider = nameof(ServiceProvider); - public static void SetCurrentInstanceProvider(this HttpContext httpContext, IInstanceProvider tenantId) + public static void SetCurrentServiceProvider(this HttpContext httpContext, IServiceProvider serviceProvider) { - if (httpContext.Items.TryGetValue(InstanceProvider, out object untyped)) + if (httpContext.Items.TryGetValue(ServiceProvider, out _)) { - throw new InvalidOperationException("IInstanceProvider has been set already in this HttpContext"); + throw new InvalidOperationException("IServiceProvider has been set already in this HttpContext"); } - httpContext.Items[InstanceProvider] = tenantId; + httpContext.Items[ServiceProvider] = serviceProvider; } - public static IInstanceProvider GetInstanceProvider(this HttpContext httpContext) + public static IServiceProvider GetServiceProvider(this HttpContext httpContext) { - if (httpContext.Items.TryGetValue(InstanceProvider, out object untyped)) + if (httpContext.Items.TryGetValue(ServiceProvider, out object untyped)) { - return (IInstanceProvider) untyped; + return (IServiceProvider) untyped; } - throw new InvalidOperationException("No IInstanceProvider present in this HttpContext"); + throw new InvalidOperationException("No IServiceProvider present in this HttpContext"); } - public static bool TryGetInstanceProvider(this HttpContext httpContext, out IInstanceProvider instanceProvider) + public static bool TryGetServiceProvider(this HttpContext httpContext, out IServiceProvider serviceProvider) { - if (httpContext.Items.TryGetValue(InstanceProvider, out object untyped)) + if (httpContext.Items.TryGetValue(ServiceProvider, out object untyped)) { - instanceProvider = (IInstanceProvider) untyped; + serviceProvider = (IServiceProvider) untyped; return true; } - instanceProvider = null; + serviceProvider = null; return false; } } diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index c8db9817..a80e3e32 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -31,6 +31,14 @@ public EfCorePersistenceModule(IDbConnectionFactory dbConnectionFactory, IEntity _assemblies = assemblies; } + public virtual Func DbConnectionFactory => _ => _dbConnectionFactory.Create(); + public virtual Func OperationFactory => _ => new Operation(); + public virtual Func> CorrelationHolderFactory => _ => new CurrentCorrelationHolder(); + public virtual Func> IdentityHolderFactory => _ => new CurrentIdentityHolder(); + public virtual Func> TenantIdHolderFactory => _ => new CurrentTenantIdHolder(); + public virtual Func DomainEventAggregatorFactory => compositionRoot => new DomainEventAggregator(compositionRoot); + + public abstract void Register(ICompositionRoot compositionRoot); public void Register(ICompositionRoot compositionRoot) { // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs index c8db9817..36e79f49 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; @@ -8,66 +9,110 @@ using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Backend.Fx.EfCorePersistence.Bootstrapping { - public class EfCorePersistenceModule : IModule + public abstract class EfCorePersistenceModule : IModule where TDbContext : DbContext { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; private readonly IDbConnectionFactory _dbConnectionFactory; private readonly IEntityIdGenerator _entityIdGenerator; - private readonly Assembly[] _assemblies; + private readonly Type[] _aggregateRootTypes; + private readonly Type[] _entityTypes; + private readonly Dictionary _aggregateMappingTypes; - public EfCorePersistenceModule(IDbConnectionFactory dbConnectionFactory, IEntityIdGenerator entityIdGenerator, - ILoggerFactory loggerFactory, Action, IDbConnection> configure, params Assembly[] assemblies) + protected EfCorePersistenceModule( + IDbConnectionFactory dbConnectionFactory, + IEntityIdGenerator entityIdGenerator, + ILoggerFactory loggerFactory, + Action, IDbConnection> configure, + params Assembly[] assemblies) { _dbConnectionFactory = dbConnectionFactory; _entityIdGenerator = entityIdGenerator; _loggerFactory = loggerFactory; _configure = configure; - _assemblies = assemblies; + + _aggregateRootTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(AggregateRoot).IsAssignableFrom(t))) + .ToArray(); + + _entityTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(Entity).IsAssignableFrom(t))) + .ToArray(); + + _aggregateMappingTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(IAggregateMapping).IsAssignableFrom(t))) + .ToDictionary(t => t.GenericTypeArguments[0], t => t); } public void Register(ICompositionRoot compositionRoot) { - // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly - compositionRoot.InfrastructureModule.RegisterScoped(() => _dbConnectionFactory.Create()); - // singleton id generator - compositionRoot.InfrastructureModule.RegisterInstance(_entityIdGenerator); + RegisterServiceDescriptor(new ServiceDescriptor(typeof(IEntityIdGenerator), _entityIdGenerator)); + + // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly + RegisterServiceDescriptor(new ServiceDescriptor(typeof(IDbConnection), sp => _dbConnectionFactory.Create(), ServiceLifetime.Scoped)); // EF core requires us to flush frequently, because of a missing identity map - compositionRoot.InfrastructureModule.RegisterScoped(); + RegisterServiceDescriptor(new ServiceDescriptor(typeof(ICanFlush), typeof(EfFlush))); + + // DbContext is injected into repositories (not TDbContext!) + RegisterServiceDescriptor(new ServiceDescriptor(typeof(DbContext), typeof(TDbContext))); - // EF Repositories - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IRepository<>), typeof(EfRepository<>)); + // TDbContext ctor requires DbContextOptions + RegisterServiceDescriptor(new ServiceDescriptor(typeof(DbContextOptions), sp => + { + IDbConnection connection = sp.GetRequiredService(); + var dbContextOptionsBuilder = new DbContextOptionsBuilder(); + _configure.Invoke(dbContextOptionsBuilder, connection); + return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; + }, ServiceLifetime.Scoped)); - // IQueryable is supported, but should be use with caution, since it bypasses authorization - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IQueryable<>), typeof(EntityQueryable<>)); + foreach (var aggregateRootType in _aggregateRootTypes) + { + // EF Repository for this aggregate root + RegisterServiceDescriptor(new ServiceDescriptor( + typeof(IRepository<>).MakeGenericType(aggregateRootType), + typeof(EfRepository<>).MakeGenericType(aggregateRootType), + ServiceLifetime.Scoped)); - // DbContext is injected into repositories - compositionRoot.InfrastructureModule.RegisterScoped(() => CreateDbContextOptions(compositionRoot.InstanceProvider.GetInstance())); - compositionRoot.InfrastructureModule.RegisterScoped(); + // aggregate mapping definition + RegisterServiceDescriptor(new ServiceDescriptor( + typeof(IAggregateMapping<>).MakeGenericType(aggregateRootType), + sp => sp.GetService(_aggregateMappingTypes[aggregateRootType]), + ServiceLifetime.Scoped)); + } + + foreach (var entityType in _entityTypes) + { + // IQueryable is supported, but should be use with caution, since it bypasses authorization + RegisterServiceDescriptor(new ServiceDescriptor( + typeof(IQueryable<>).MakeGenericType(entityType), + typeof(EntityQueryable<>).MakeGenericType(entityType), + ServiceLifetime.Scoped)); + } // wrapping the operation: connection.open - transaction.begin - operation - (flush) - transaction.commit - connection.close compositionRoot.InfrastructureModule.RegisterDecorator(); compositionRoot.InfrastructureModule.RegisterDecorator(); compositionRoot.InfrastructureModule.RegisterDecorator(); - + // ensure everything dirty is flushed to the db before handling domain events compositionRoot.InfrastructureModule.RegisterDecorator(); - - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IAggregateMapping<>), _assemblies); - } - - protected virtual DbContextOptions CreateDbContextOptions(IDbConnection connection) - { - var dbContextOptionsBuilder = new DbContextOptionsBuilder(); - _configure.Invoke(dbContextOptionsBuilder, connection); - return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs new file mode 100644 index 00000000..ace4192c --- /dev/null +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.MicrosoftDependencyInjection +{ + public class MicrosoftCompositionRoot : ICompositionRoot + { + private readonly IServiceCollection _serviceCollection = new ServiceCollection(); + private readonly Lazy _serviceProvider; + public MicrosoftCompositionRoot() + { + _serviceProvider = new Lazy(() => _serviceCollection.BuildServiceProvider()); + } + public void Dispose() + { + } + + public void Verify() + { } + + public void RegisterModules(params IModule[] modules) + { + foreach (var module in modules) + { + module.Register(this); + } + } + + public void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) + { + if (_serviceProvider.IsValueCreated) + { + throw new InvalidOperationException("Service provider has been built and cannot be changed any more."); + } + _serviceCollection.Add(serviceDescriptor); + } + + public void RegisterServiceDescriptors(IEnumerable serviceDescriptors) + { + foreach (var serviceDescriptor in serviceDescriptors) + { + RegisterServiceDescriptor(serviceDescriptor); + } + } + + public IServiceScope BeginScope() + { + return ServiceProvider.CreateScope(); + } + + public IServiceProvider ServiceProvider => _serviceProvider.Value; + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs new file mode 100644 index 00000000..35b9f609 --- /dev/null +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using SimpleInjector; + +namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules +{ + public static class MicrosoftDependencyInjectionExtensions + { + public static Lifestyle Translate(this ServiceLifetime serviceLifetime) + { + switch (serviceLifetime) + { + case ServiceLifetime.Scoped: return Lifestyle.Scoped; + case ServiceLifetime.Singleton: return Lifestyle.Singleton; + case ServiceLifetime.Transient: return Lifestyle.Transient; + default: throw new ArgumentException($"Unknown ServiceLifetime {serviceLifetime}"); + } + } + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs deleted file mode 100644 index f7faa48b..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDataGenerationModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Reflection; -using Backend.Fx.Patterns.DataGeneration; -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - public class SimpleInjectorDataGenerationModule : IModule - { - private readonly Assembly[] _domainAssemblies; - - public SimpleInjectorDataGenerationModule(params Assembly[] domainAssemblies) - { - _domainAssemblies = domainAssemblies; - } - - public void Register(ICompositionRoot compositionRoot) - { - Container container = ((SimpleInjectorCompositionRoot) compositionRoot).Container; - container.Collection.Register(container.GetTypesToRegister(typeof(IDataGenerator), _domainAssemblies)); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs deleted file mode 100644 index b0e3edd6..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorDomainModule.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.DataGeneration; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.Patterns.Jobs; -using Microsoft.Extensions.Logging; -using SimpleInjector; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - /// - /// Wires all public domain services to be injected as scoped instances provided by the array of domain assemblies: - /// - s - /// - s - /// - s - /// - s - /// - s - /// - the collections of s - /// - 's - /// - public class SimpleInjectorDomainModule : SimpleInjectorModule - { - private static readonly ILogger Logger = Log.Create(); - private readonly Assembly[] _domainAssemblies; - private readonly string _domainAssembliesForLogging; - - public SimpleInjectorDomainModule(params Assembly[] domainAssemblies) - { - _domainAssemblies = domainAssemblies; - _domainAssembliesForLogging = string.Join(",", _domainAssemblies.Select(ass => ass.GetName().Name)); - } - - protected override void Register(Container container, ScopedLifestyle scopedLifestyle) - { - RegisterDomainAndApplicationServices(container); - - RegisterAuthorization(container); - - // all jobs are dynamically registered - foreach (Type jobType in container.GetTypesToRegister(typeof(IJob), _domainAssemblies)) - { - Logger.LogDebug("Registering {JobType}", jobType.Name); - container.Register(jobType); - } - - // domain event handlers - foreach (Type domainEventHandlerType in container.GetTypesToRegister(typeof(IDomainEventHandler<>), _domainAssemblies)) - { - Logger.LogDebug("Appending {DomainEventHandlerType} to list of IDomainEventHandler", domainEventHandlerType.Name); - container.Collection.Append(typeof(IDomainEventHandler<>), domainEventHandlerType); - } - - // integration message handlers - foreach (Type integrationMessageHandlerType in container.GetTypesToRegister(typeof(IIntegrationMessageHandler<>), _domainAssemblies)) - { - Logger.LogDebug("Registering {IntegrationEventHandler}", integrationMessageHandlerType.Name); - container.Register(integrationMessageHandlerType); - } - } - - private void RegisterDomainAndApplicationServices(Container container) - { - Logger.LogDebug("Registering domain and application services from {DomainAssemblies}", - string.Join(",", _domainAssemblies.Select(ass => ass.GetName().Name))); - var serviceRegistrations = container - .GetTypesToRegister(typeof(IDomainService), _domainAssemblies) - .Concat(container.GetTypesToRegister(typeof(IApplicationService), _domainAssemblies)) - .SelectMany(type => - type.GetTypeInfo() - .ImplementedInterfaces - .Where(i => typeof(IDomainService) != i - && typeof(IApplicationService) != i - && (i.Namespace != null && i.Namespace.StartsWith("Backend") - || _domainAssemblies.Contains(i.GetTypeInfo().Assembly))) - .Select(service => new - { - Service = service, - Implementation = type - }) - ); - foreach (var reg in serviceRegistrations) - { - Logger.LogDebug("Registering scoped service {ServiceType} with implementation {ImplementationType}", - reg.Service.Name, - reg.Implementation.Name); - container.Register(reg.Service, reg.Implementation); - } - } - - /// - /// Auto registering all aggregate authorization classes - /// - private void RegisterAuthorization(Container container) - { - Logger.LogDebug("Registering authorization services from {DomainAssemblies}", _domainAssembliesForLogging); - var aggregateRootAuthorizationTypes = container.GetTypesToRegister(typeof(IAggregateAuthorization<>), _domainAssemblies).ToArray(); - foreach (Type aggregateAuthorizationType in aggregateRootAuthorizationTypes) - { - var serviceTypes = aggregateAuthorizationType - .GetTypeInfo() - .ImplementedInterfaces - .Where(impif => impif.GetTypeInfo().IsGenericType - && impif.GenericTypeArguments.Length == 1 - && typeof(AggregateRoot).GetTypeInfo().IsAssignableFrom(impif.GenericTypeArguments[0].GetTypeInfo())); - - foreach (Type serviceType in serviceTypes) - { - Logger.LogDebug("Registering scoped authorization service {ServiceType} with implementation {ImplementationType}", - serviceType.Name, - aggregateAuthorizationType.Name); - container.Register(serviceType, aggregateAuthorizationType); - } - } - } - } -} diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs deleted file mode 100644 index e9331a84..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorInfrastructureModule.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Reflection; -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - public class SimpleInjectorInfrastructureModule : IInfrastructureModule - { - private readonly Container _container; - - public SimpleInjectorInfrastructureModule(Container container) - { - _container = container; - } - - public void RegisterScoped() where TService : class where TImpl : class, TService - { - _container.Register(); - } - - public void RegisterScoped(Func factory) where TService : class - { - _container.Register(factory); - } - - public void RegisterScoped(Type serviceType, Assembly[] assembliesToScan) - { - _container.Register(serviceType, assembliesToScan); - } - - public void RegisterDecorator() where TService : class where TImpl : class, TService - { - _container.RegisterDecorator(); - } - - public void RegisterScoped(Type serviceType, Type implementationType) - { - _container.Register(serviceType, implementationType); - } - - public void RegisterSingleton() where TService : class where TImpl : class, TService - { - _container.RegisterSingleton(); - } - - public void RegisterInstance(TService instance) where TService : class - { - _container.RegisterInstance(instance); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs deleted file mode 100644 index 151fc3a4..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Modules/SimpleInjectorModule.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.Logging; -using SimpleInjector; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules -{ - public abstract class SimpleInjectorModule : IModule - { - private static readonly ILogger Logger = Log.Create(); - - protected abstract void Register(Container container, ScopedLifestyle scopedLifestyle); - - public virtual void Register(ICompositionRoot compositionRoot) - { - Logger.LogDebug("Registering {ServiceType}", GetType().Name); - var simpleInjectorCompositionRoot = (SimpleInjectorCompositionRoot) compositionRoot; - Register(simpleInjectorCompositionRoot.Container, simpleInjectorCompositionRoot.ScopedLifestyle); - } - } -} diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index 3da55be7..e2a42f29 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -1,11 +1,10 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Threading; +using System.Linq; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.SimpleInjectorDependencyInjection.Modules; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SimpleInjector; using SimpleInjector.Advanced; @@ -21,7 +20,6 @@ public class SimpleInjectorCompositionRoot : ICompositionRoot { private static readonly ILogger Logger = Log.Create(); - private int _scopeSequenceNumber = 1; /// /// This constructor creates a composition root that prefers scoped lifestyle /// @@ -35,17 +33,15 @@ public SimpleInjectorCompositionRoot(ILifestyleSelectionBehavior lifestyleBehavi ScopedLifestyle = scopedLifestyle; Container.Options.LifestyleSelectionBehavior = lifestyleBehavior; Container.Options.DefaultScopedLifestyle = ScopedLifestyle; - InfrastructureModule = new SimpleInjectorInfrastructureModule(Container); - InstanceProvider = new SimpleInjectorInstanceProvider(Container); - - // SimpleInjector 5 needs this to resolve controllers - Container.Options.ResolveUnregisteredConcreteTypes = true; + ServiceProvider = Container; } public Container Container { get; } = new Container(); internal ScopedLifestyle ScopedLifestyle { get; } + #region ICompositionRoot implementation + public void RegisterModules(params IModule[] modules) { foreach (var module in modules) @@ -55,7 +51,50 @@ public void RegisterModules(params IModule[] modules) } } - #region ICompositionRoot implementation + public void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) + { + if (serviceDescriptor.ImplementationType != null) + { + Container.Register( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + else if (serviceDescriptor.ImplementationFactory != null) + { + Container.Register( + serviceDescriptor.ServiceType, + () => serviceDescriptor.ImplementationFactory(Container), + serviceDescriptor.Lifetime.Translate()); + } + else if (serviceDescriptor.ImplementationInstance != null && + serviceDescriptor.Lifetime == ServiceLifetime.Singleton) + { + Container.RegisterInstance(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationInstance); + } + else + { + throw new InvalidOperationException("Bad service descriptor"); + } + } + + public void RegisterServiceDescriptors(IEnumerable serviceDescriptors) + { + var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); + + if (serviceDescriptorArray.Select(sd => sd.ServiceType).Distinct().Count() > 1) + { + throw new InvalidOperationException("To register a collection of services they must implement the same service type"); + } + + foreach (var serviceDescriptor in serviceDescriptorArray) + { + Container.Collection.Append( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + } public void Verify() { @@ -71,36 +110,13 @@ public void Verify() } } - - public object GetInstance(Type serviceType) - { - return Container.GetInstance(serviceType); - } - - public IEnumerable GetInstances(Type serviceType) - { - return Container.GetAllInstances(serviceType); - } - - public T GetInstance() where T : class - { - return Container.GetInstance(); - } - - public IEnumerable GetInstances() where T : class - { - return Container.GetAllInstances(); - } - /// - public IInjectionScope BeginScope() + public IServiceScope BeginScope() { - return new SimpleInjectorInjectionScope(Interlocked.Increment(ref _scopeSequenceNumber), AsyncScopedLifestyle.BeginScope(Container)); + return new SimpleInjectorServiceScope(AsyncScopedLifestyle.BeginScope(Container)); } - public IInstanceProvider InstanceProvider { get; } - - public IInfrastructureModule InfrastructureModule { get; } + public IServiceProvider ServiceProvider { get; } public Scope GetCurrentScope() { @@ -108,15 +124,6 @@ public Scope GetCurrentScope() } #endregion - #region IEventHandlerProvider implementation - - /// - public IEnumerable> GetAllEventHandlers() where TDomainEvent : IDomainEvent - { - return Container.GetAllInstances>(); - } - #endregion - #region IDisposable implementation public void Dispose() { diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs deleted file mode 100644 index c22eedc3..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInjectionScope.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection -{ - public class SimpleInjectorInjectionScope : InjectionScope - { - private readonly Scope _scope; - - public SimpleInjectorInjectionScope(int sequenceNumber, Scope scope) : base(sequenceNumber) - { - _scope = scope; - InstanceProvider = new SimpleInjectorInstanceProvider(_scope.Container); - } - - public override IInstanceProvider InstanceProvider { get; } - - - public override void Dispose() - { - _scope.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs deleted file mode 100644 index 610dd71b..00000000 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorInstanceProvider.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Backend.Fx.Patterns.DependencyInjection; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection -{ - public class SimpleInjectorInstanceProvider : IInstanceProvider - { - private readonly Container _container; - - public SimpleInjectorInstanceProvider(Container container) - { - _container = container; - } - - public object GetInstance(Type serviceType) - { - return _container.GetInstance(serviceType); - } - - public IEnumerable GetInstances(Type serviceType) - { - return (IEnumerable) _container.GetInstance(typeof(IEnumerable<>).MakeGenericType(serviceType)); - } - - public T GetInstance() where T : class - { - return _container.GetInstance(); - } - - public IEnumerable GetInstances() where T : class - { - return _container.GetInstance>(); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScope.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScope.cs new file mode 100644 index 00000000..6f65de98 --- /dev/null +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScope.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using SimpleInjector; + +namespace Backend.Fx.SimpleInjectorDependencyInjection +{ + public class SimpleInjectorServiceScope : IServiceScope + { + private readonly Scope _scope; + + public SimpleInjectorServiceScope(Scope scope) + { + _scope = scope; + } + + public IServiceProvider ServiceProvider => _scope.Container; + + + public void Dispose() + { + _scope.Dispose(); + } + } +} \ No newline at end of file From 3ecb8a511e23b139a28be9e7f133fa71ef01ed37 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Mon, 13 Jun 2022 13:26:06 -0300 Subject: [PATCH 02/51] (fix)tests --- .tmp/build-attempt.log | 7 -- Backend.Fx.sln | 7 ++ .../Backend.Fx/Backend.Fx.csproj.DotSettings | 2 + .../BackendFxApplicationInvoker.cs | 2 - .../Integration/MessageBusApplication.cs | 25 ++++ .../Integration/MessageBusModule.cs | 57 +++++++++ ...end.Fx.MicrosoftDependencyInjection.csproj | 15 +++ .../SampleApp/Runtime/SampleApplication.cs | 10 +- .../SampleApplicationHostedService.cs | 2 +- .../TheRabbitMqMessageBus.cs | 6 +- .../TheSimpleInjectorCompositionRoot.cs | 1 - .../Backend.Fx.Tests/Backend.Fx.Tests.csproj | 2 + ...TheAllTenantBackendFxApplicationInvoker.cs | 4 +- .../MultiTenancy/TheTenantService.cs | 19 +-- .../TheDataGenerationContext.cs | 110 ++++++------------ .../TheGenerateDataOnBootDecorator.cs | 30 ++--- .../DependencyInjection/DITestFakes.cs | 36 +++--- .../TheBackendFxApplication.cs | 63 ++-------- .../TheBackendFxApplicationAsyncInvoker.cs | 32 ++--- .../TheBackendFxApplicationInvoker.cs | 30 ++--- .../TheBackendFxDbApplication.cs | 7 +- .../DependencyInjection/TheInjectionScope.cs | 46 -------- ...uentializingBackendFxApplicationInvoker.cs | 3 +- .../Domain/TheEventAggregator.cs | 20 ++-- .../Integration/TheMessageBus.cs | 16 +-- 25 files changed, 252 insertions(+), 300 deletions(-) delete mode 100644 .tmp/build-attempt.log create mode 100644 src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings create mode 100644 src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs create mode 100644 src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs diff --git a/.tmp/build-attempt.log b/.tmp/build-attempt.log deleted file mode 100644 index c70b1d97..00000000 --- a/.tmp/build-attempt.log +++ /dev/null @@ -1,7 +0,0 @@ -884bc421701d4c4e7e62c0eca741ed63 -Clean -Restore -Compile -Test -Pack -Publish diff --git a/Backend.Fx.sln b/Backend.Fx.sln index 8353da85..bf9b3ab1 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -58,6 +58,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCore6Persisten EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCore6Persistence.Tests", "tests\Backend.Fx.EfCore6Persistence.Tests\Backend.Fx.EfCore6Persistence.Tests.csproj", "{E50D7E8D-D012-4683-BA05-C877BAA25230}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.MicrosoftDependencyInjection", "src\implementations\Backend.Fx.MicrosoftDependencyInjection\Backend.Fx.MicrosoftDependencyInjection.csproj", "{B4791DB0-F8DD-4248-86CB-407E46F55B13}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -119,6 +121,10 @@ Global {E50D7E8D-D012-4683-BA05-C877BAA25230}.Debug|Any CPU.Build.0 = Debug|Any CPU {E50D7E8D-D012-4683-BA05-C877BAA25230}.Release|Any CPU.ActiveCfg = Release|Any CPU {E50D7E8D-D012-4683-BA05-C877BAA25230}.Release|Any CPU.Build.0 = Release|Any CPU + {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -145,6 +151,7 @@ Global {FF042FB5-BA44-4655-8903-2644FE549810} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} {38034961-CE3B-4286-A9EB-496DECA39632} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11} {E50D7E8D-D012-4683-BA05-C877BAA25230} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} + {B4791DB0-F8DD-4248-86CB-407E46F55B13} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969} diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings b/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings new file mode 100644 index 00000000..89316e41 --- /dev/null +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings @@ -0,0 +1,2 @@ + + Library \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs index 93042bff..7ec72ed2 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs @@ -88,8 +88,6 @@ public async Task InvokeAsync(Func awaitableAsyncAction, operation.Cancel(); throw; } - - await serviceScope.ServiceProvider.GetRequiredService().RaiseEvents().ConfigureAwait(false); } } } diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs new file mode 100644 index 00000000..c793a583 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -0,0 +1,25 @@ +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.Patterns.EventAggregation.Integration +{ + public class MessageBusApplication : BackendFxApplicationDecorator + { + private readonly IMessageBus _messageBus; + + public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication application) + : base(application) + { + application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); + _messageBus = messageBus; + } + + public override async Task BootAsync(CancellationToken cancellationToken = default) + { + await base.BootAsync(cancellationToken).ConfigureAwait(false); + _messageBus.ProvideInvoker(new SequentializingBackendFxApplicationInvoker(new ExceptionLoggingAndHandlingInvoker(ExceptionLogger, Invoker))); + _messageBus.Connect(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs new file mode 100644 index 00000000..0a7149c6 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Patterns.EventAggregation.Integration +{ + public class MessageBusModule : IModule + { + private readonly IMessageBus _messageBus; + private readonly IEnumerable _assemblies; + + public MessageBusModule(IMessageBus messageBus, IEnumerable assemblies) + { + _messageBus = messageBus; + _assemblies = assemblies; + } + + public void Register(ICompositionRoot compositionRoot) + { + // note tht there should be no reason to access the singleton message bus instance from the service provider + + // register the message bus scope + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor( + typeof(IMessageBusScope), + sp => new MessageBusScope( + _messageBus, + sp.GetRequiredService>(), + sp.GetRequiredService>()), + ServiceLifetime.Scoped)); + + // register typed handlers + foreach (Type integrationEventType in _assemblies.GetImplementingTypes(typeof(IIntegrationEvent))) + { + Type handlerServiceType = typeof(IIntegrationMessageHandler<>).MakeGenericType(integrationEventType); + foreach (var handlerImplementingType in _assemblies.GetImplementingTypes(handlerServiceType)) + { + compositionRoot.RegisterServiceDescriptor( + new ServiceDescriptor( + handlerServiceType, + handlerImplementingType, + ServiceLifetime.Scoped)); + } + } + + //todo refactoring decorate IOperation to raise integration events - sync AND async! + // var messageBusScope = serviceScope.ServiceProvider.GetRequiredService(); + // AsyncHelper.RunSync(() => messageBusScope.RaiseEvents()); + + // dynamic subscriptions should be registered as domain or application service + } + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj new file mode 100644 index 00000000..32788c68 --- /dev/null +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs index 6d78bef5..048badd5 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs @@ -5,9 +5,7 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.SimpleInjectorDependencyInjection; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; namespace Backend.Fx.AspNetCore.Tests.SampleApp.Runtime { @@ -22,8 +20,8 @@ public SampleApplication(ITenantIdProvider tenantIdProvider, IExceptionLogger ex _application = new BackendFxApplication( new SimpleInjectorCompositionRoot(), - new InMemoryMessageBus(), - exceptionLogger); + exceptionLogger, + new SimpleInjectorInfrastructureModule()); _application = new DataGeneratingApplication( tenantIdProvider, @@ -31,7 +29,7 @@ public SampleApplication(ITenantIdProvider tenantIdProvider, IExceptionLogger ex tenantWideMutexManager, _application); _application.CompositionRoot.RegisterModules( - new SimpleInjectorDomainModule(domainAssembly)); + new DomainModule(domainAssembly)); } public void Dispose() @@ -45,8 +43,6 @@ public void Dispose() public IBackendFxApplicationInvoker Invoker => _application.Invoker; - public IMessageBus MessageBus => _application.MessageBus; - public bool WaitForBoot(int timeoutMilliSeconds = 2147483647, CancellationToken cancellationToken = default) { return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs index 5a20a3ca..d69ac8a3 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs @@ -15,7 +15,7 @@ public class SampleApplicationHostedService : BackendFxApplicationHostedService public SampleApplicationHostedService(IExceptionLogger exceptionLogger) { IMessageBus messageBus = new InMemoryMessageBus(); - TenantService = new TenantService(messageBus, new InMemoryTenantRepository()); + TenantService = new TenantService(new InMemoryTenantRepository()); Application = new SampleApplication(TenantService.TenantIdProvider, exceptionLogger); } } diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index b26b7506..5b570fb5 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -26,10 +26,10 @@ public TheRabbitMqMessageBus(ITestOutputHelper testOutputHelper) : base(testOutp var fakeReceiverApplication = A.Fake(); _receiverInvoker = new BackendFxApplicationInvoker(fakeReceiverApplication.CompositionRoot); var fakeScope = A.Fake(); - var fakeInstanceProvider = A.Fake(); + var fakeServiceProvider = A.Fake(); A.CallTo(() => fakeReceiverApplication.CompositionRoot.BeginScope()).Returns(fakeScope); - A.CallTo(() => fakeScope.InstanceProvider).Returns(fakeInstanceProvider); - A.CallTo(() => fakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(TestIntegrationEventHandler)))) + A.CallTo(() => fakeScope.ServiceProvider).Returns(fakeServiceProvider); + A.CallTo(() => fakeServiceProvider.GetService(A.That.IsEqualTo(typeof(TestIntegrationEventHandler)))) .Returns(new TestIntegrationEventHandler(_received)); } diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs index 1c2c4406..302c246a 100644 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs +++ b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; diff --git a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj index c9499d22..3d9ed56d 100644 --- a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj +++ b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj @@ -11,6 +11,7 @@ + @@ -23,6 +24,7 @@ + diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs index af13ae27..8737d884 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs @@ -32,13 +32,13 @@ public void InvokesActionForAllTenants() foreach (TenantId tenantId in demoTenantIds) { - A.CallTo(() => _invoker.Invoke(A>._, A._, A.That.IsSameAs(tenantId), A._)) + A.CallTo(() => _invoker.Invoke(A>._, A._, A.That.IsSameAs(tenantId), A._)) .MustHaveHappenedOnceExactly(); } foreach (TenantId tenantId in prodTenantIds) { - A.CallTo(() => _invoker.Invoke(A>._, A._, A.That.IsSameAs(tenantId), A._)) + A.CallTo(() => _invoker.Invoke(A>._, A._, A.That.IsSameAs(tenantId), A._)) .MustHaveHappenedOnceExactly(); } } diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs index a168ae4c..494477c5 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs @@ -16,7 +16,7 @@ public class TheTenantService : TestWithLogging { public TheTenantService(ITestOutputHelper output): base(output) { - _sut = new TenantService(_messageBus, _tenantRepository); + _sut = new TenantService(_tenantRepository); } private readonly ITenantService _sut; @@ -61,13 +61,14 @@ public void GetsProductiveTenantIds() Assert.Equal(demoTenantIds, _sut.TenantIdProvider.GetActiveDemonstrationTenantIds()); } - [Fact] - public void RaisesTenantActivatedEvent() - { - var ev = new ManualResetEvent(false); - A.CallTo(() => _messageBus.Publish(A._)).Invokes(() => ev.Set()); - Task.Run(() => _sut.CreateTenant("prod", "unit test created", false)); - Assert.True(ev.WaitOne(Debugger.IsAttached ? int.MaxValue : 10000)); - } + //todo refactoring + // [Fact] + // public void RaisesTenantActivatedEvent() + // { + // var ev = new ManualResetEvent(false); + // A.CallTo(() => _messageBus.Publish(A._)).Invokes(() => ev.Set()); + // Task.Run(() => _sut.CreateTenant("prod", "unit test created", false)); + // Assert.True(ev.WaitOne(Debugger.IsAttached ? int.MaxValue : 10000)); + // } } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index acc37c8f..5a5284c9 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -1,10 +1,10 @@ -using System.Linq; -using System.Threading; +using System.Collections.Generic; +using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Logging; +using Backend.Fx.MicrosoftDependencyInjection; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.Tests.Patterns.DependencyInjection; using FakeItEasy; using Xunit; using Xunit.Abstractions; @@ -17,39 +17,27 @@ public class TheDataGenerationContext : TestWithLogging public TheDataGenerationContext(ITestOutputHelper output) : base(output) { - var fakes = new DiTestFakes(); - A.CallTo(() => fakes.InstanceProvider.GetInstances()) - .Returns(_demoDataGenerators.Concat(_prodDataGenerators.Cast()).ToArray()); - - var application = A.Fake(); - A.CallTo(() => application.Invoker).Returns(fakes.Invoker); - A.CallTo(() => application.WaitForBoot(A._, A._)).Returns(true); - - var messageBus = new InMemoryMessageBus(); - messageBus.ProvideInvoker(application.Invoker); - var tenantIdProvider = A.Fake(); A.CallTo(() => tenantIdProvider.GetActiveDemonstrationTenantIds()).Returns(_demoTenants); A.CallTo(() => tenantIdProvider.GetActiveProductionTenantIds()).Returns(_prodTenants); - _sut = new DataGenerationContext(fakes.CompositionRoot, - fakes.Invoker, - _tenantWideMutexManager); - } + var backendFxApplication = + new BackendFxApplication(new MicrosoftCompositionRoot(), A.Fake(), + GetType().Assembly); - private readonly DataGenerationContext _sut; + _sut = new DataGeneratingApplication(tenantIdProvider, _tenantWideMutexManager, backendFxApplication); + TestDataGenerator.Calls.Clear(); + } - private readonly IDemoDataGenerator[] _demoDataGenerators = - {new DemoDataGenerator1(), new DemoDataGenerator2()}; + private readonly DataGeneratingApplication _sut; - private readonly IProductiveDataGenerator[] _prodDataGenerators = {new ProdDataGenerator1()}; private readonly TenantId[] _demoTenants = {new TenantId(1), new TenantId(2)}; private readonly TenantId[] _prodTenants = {new TenantId(11), new TenantId(12)}; [Theory] [InlineData(true)] [InlineData(false)] - public void CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTenant) + public async Task CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTenant) { ITenantWideMutex disposable = A.Fake(); ITenantWideMutex m; @@ -63,23 +51,28 @@ public void CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTenant) .Returns(true) .AssignsOutAndRefParameters(disposable); - _sut.SeedDataForTenant(new TenantId(123), isDemoTenant); + await _sut.BootAsync(); + _sut.DataGenerationContext.SeedDataForTenant(new TenantId(123), isDemoTenant); - foreach (IProductiveDataGenerator dataGenerator in _prodDataGenerators) - A.CallTo(() => ((ProdDataGenerator) dataGenerator).Impl.Generate()).MustHaveHappenedOnceExactly(); + Assert.Contains(nameof(ProdDataGenerator1), TestDataGenerator.Calls); - foreach (IDemoDataGenerator dataGenerator in _demoDataGenerators) - if (isDemoTenant) - A.CallTo(() => ((DemoDataGenerator) dataGenerator).Impl.Generate()).MustHaveHappenedOnceExactly(); - else - A.CallTo(() => ((DemoDataGenerator) dataGenerator).Impl.Generate()).MustNotHaveHappened(); + if (isDemoTenant) + { + Assert.Contains(nameof(DemoDataGenerator1), TestDataGenerator.Calls); + Assert.Contains(nameof(DemoDataGenerator2), TestDataGenerator.Calls); + } + else + { + Assert.DoesNotContain(nameof(DemoDataGenerator1), TestDataGenerator.Calls); + Assert.DoesNotContain(nameof(DemoDataGenerator2), TestDataGenerator.Calls); + } tryAcquireCall.MustHaveHappenedOnceExactly(); A.CallTo(() => disposable.Dispose()).MustHaveHappenedOnceExactly(); } - + [Fact] - public void DoesNothingWhenCannotAcquireTenantWideMutex() + public async Task DoesNothingWhenCannotAcquireTenantWideMutex() { ITenantWideMutex m; var tryAcquireCall = A.CallTo(() => @@ -90,65 +83,36 @@ public void DoesNothingWhenCannotAcquireTenantWideMutex() out m)); tryAcquireCall.Returns(false); - _sut.SeedDataForTenant(new TenantId(123), false); + await _sut.BootAsync(); + _sut.DataGenerationContext.SeedDataForTenant(new TenantId(123), false); - foreach (IProductiveDataGenerator dataGenerator in _prodDataGenerators) - { - A.CallTo(() => ((ProdDataGenerator) dataGenerator).Impl.Generate()).MustNotHaveHappened(); - } - - foreach (IDemoDataGenerator dataGenerator in _demoDataGenerators) - { - A.CallTo(() => ((DemoDataGenerator) dataGenerator).Impl.Generate()).MustNotHaveHappened(); - } + Assert.Empty(TestDataGenerator.Calls); tryAcquireCall.MustHaveHappenedOnceExactly(); } - private abstract class DemoDataGenerator : IDemoDataGenerator + private class DemoDataGenerator1 : TestDataGenerator, IDemoDataGenerator { - public readonly IDemoDataGenerator Impl; - - protected DemoDataGenerator() - { - Impl = A.Fake(o => o.Named(GetType().Name)); - } - - public int Priority => Impl.Priority; - - public void Generate() - { - Impl.Generate(); - } } - private class DemoDataGenerator1 : DemoDataGenerator + private class DemoDataGenerator2 : TestDataGenerator, IDemoDataGenerator { } - private class DemoDataGenerator2 : DemoDataGenerator + private class ProdDataGenerator1 : TestDataGenerator, IProductiveDataGenerator { } - private abstract class ProdDataGenerator : IProductiveDataGenerator + private abstract class TestDataGenerator { - public readonly IProductiveDataGenerator Impl; - - protected ProdDataGenerator() - { - Impl = A.Fake(o => o.Named(GetType().Name)); - } + public static List Calls = new List(); - public int Priority => Impl.Priority; + public int Priority => 0; public void Generate() { - Impl.Generate(); + Calls.Add(GetType().Name); } } - - private class ProdDataGenerator1 : ProdDataGenerator - { - } } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs index dfcf4bbe..ce951941 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs @@ -3,7 +3,6 @@ using Backend.Fx.Hacking; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; using Xunit.Abstractions; @@ -15,33 +14,29 @@ public class TheGenerateDataOnBootDecorator : TestWithLogging public TheGenerateDataOnBootDecorator(ITestOutputHelper output) : base(output) { ITenantWideMutexManager tenantWideMutexManager = new InMemoryTenantWideMutexManager(); - _compositionRoot = A.Fake(); - _dataGenerationModule = A.Fake(); + A.Fake(); _dataGenerationContext = A.Fake(); _tenantIdProvider = A.Fake(); var backendFxApplication = A.Fake(); - A.CallTo(() => backendFxApplication.CompositionRoot).Returns(_compositionRoot); - _sut = new DataGeneratingApplication( - _tenantIdProvider, - _dataGenerationModule, - tenantWideMutexManager, backendFxApplication); + _sut = new DataGeneratingApplication(_tenantIdProvider, tenantWideMutexManager, backendFxApplication); _sut.SetPrivate(f => f.DataGenerationContext, _dataGenerationContext); } private readonly DataGeneratingApplication _sut; - private readonly IModule _dataGenerationModule; private readonly IDataGenerationContext _dataGenerationContext; - private readonly ICompositionRoot _compositionRoot; private readonly ITenantIdProvider _tenantIdProvider; [Fact] public void DelegatesAllOtherCalls() { var app = A.Fake(); - IBackendFxApplication sut = new DataGeneratingApplication(A.Fake(), A.Fake(), new InMemoryTenantWideMutexManager(), app); - + + IBackendFxApplication sut = new DataGeneratingApplication( + A.Fake(), + new InMemoryTenantWideMutexManager(), + app); // ReSharper disable UnusedVariable IBackendFxApplicationAsyncInvoker ai = sut.AsyncInvoker; @@ -55,20 +50,9 @@ public void DelegatesAllOtherCalls() IBackendFxApplicationInvoker i = sut.Invoker; A.CallTo(() => app.Invoker).MustHaveHappenedOnceOrMore(); - - IMessageBus mb = sut.MessageBus; - A.CallTo(() => app.MessageBus).MustHaveHappenedOnceExactly(); - // ReSharper restore UnusedVariable } - [Fact] - public async Task RegistersDataGenerationModuleOnBoot() - { - await _sut.BootAsync(); - A.CallTo(() => _compositionRoot.RegisterModules(A.That.Contains(_dataGenerationModule))) - .MustHaveHappenedOnceExactly(); - } [Fact] public async Task RunsDataGeneratorsOnBoot() diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs index 6198d8c1..d1955250 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs @@ -1,45 +1,43 @@ using System; using System.Security.Principal; +using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.RandomData; using FakeItEasy; +using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class DiTestFakes { - private readonly int _sequenceNumber = TestRandom.Next(100000); - public DiTestFakes() { - A.CallTo(() => InstanceProvider.GetInstance>()).Returns(CurrentCorrelationHolder); - A.CallTo(() => InstanceProvider.GetInstance>()).Returns(TenantIdHolder); - A.CallTo(() => InstanceProvider.GetInstance>()).Returns(IdentityHolder); - A.CallTo(() => InstanceProvider.GetInstance()).Returns(Operation); - - A.CallTo(() => InjectionScope.SequenceNumber).Returns(_sequenceNumber++); - A.CallTo(() => InjectionScope.InstanceProvider).Returns(InstanceProvider); + A.CallTo(() => ServiceProvider.GetService(A.That.IsEqualTo(typeof(IDomainEventAggregator)))).Returns(DomainEventAggregator); + A.CallTo(() => ServiceProvider.GetService(A.That.IsEqualTo(typeof(ICurrentTHolder)))).Returns(CurrentCorrelationHolder); + A.CallTo(() => ServiceProvider.GetService(A.That.IsEqualTo(typeof(ICurrentTHolder)))).Returns(TenantIdHolder); + A.CallTo(() => ServiceProvider.GetService(A.That.IsEqualTo(typeof(ICurrentTHolder)))).Returns(IdentityHolder); + A.CallTo(() => ServiceProvider.GetService(A.That.IsEqualTo(typeof(IOperation)))).Returns(Operation); + - A.CallTo(() => CompositionRoot.BeginScope()).Returns(InjectionScope); - A.CallTo(() => CompositionRoot.InfrastructureModule).Returns(InfrastructureModule); + A.CallTo(() => ServiceScope.ServiceProvider).Returns(ServiceProvider); - A.CallTo(() => Invoker.Invoke(A>._, A._, A._, A._)) - .Invokes((Action a, IIdentity i, TenantId t, Guid? g) => a.Invoke(InstanceProvider)); + A.CallTo(() => CompositionRoot.BeginScope()).Returns(ServiceScope); + + A.CallTo(() => Invoker.Invoke(A>._, A._, A._, A._)) + .Invokes((Action a, IIdentity i, TenantId t, Guid? g) => a.Invoke(ServiceProvider)); } public ICurrentTHolder TenantIdHolder { get; } = A.Fake>(); public ICurrentTHolder IdentityHolder { get; } = A.Fake>(); public IOperation Operation { get; } = A.Fake(); + public IDomainEventAggregator DomainEventAggregator { get; } = A.Fake(); public ICompositionRoot CompositionRoot { get; } = A.Fake(); public CurrentCorrelationHolder CurrentCorrelationHolder { get; } = new CurrentCorrelationHolder(); - public IInjectionScope InjectionScope { get; } = A.Fake(); - public IExceptionLogger ExceptionLogger { get; } = A.Fake(); - public IInstanceProvider InstanceProvider { get; } = A.Fake(); - public IMessageBus MessageBus { get; } = A.Fake(); - public IInfrastructureModule InfrastructureModule { get; } = A.Fake(); + public IServiceScope ServiceScope { get; } = A.Fake(); + public IServiceProvider ServiceProvider { get; } = A.Fake(); public IBackendFxApplicationInvoker Invoker { get; } = A.Fake(); } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index c8f27976..61b5070a 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -1,15 +1,12 @@ using System; using System.Diagnostics; -using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; +using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; @@ -17,27 +14,13 @@ namespace Backend.Fx.Tests.Patterns.DependencyInjection { public class TheBackendFxApplication : TestWithLogging { + private readonly IBackendFxApplication _sut; + private readonly DiTestFakes _fakes = new DiTestFakes(); + public TheBackendFxApplication(ITestOutputHelper output): base(output) { - _fakes = new DiTestFakes(); - - Func domainEventAggregatorFactory = () => null; - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)) - .Invokes((Func f) => domainEventAggregatorFactory = f); - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).ReturnsLazily(() => domainEventAggregatorFactory.Invoke()); - - Func messageBusScopeFactory = () => null; - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)) - .Invokes((Func f) => messageBusScopeFactory = f); - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).ReturnsLazily(() => messageBusScopeFactory.Invoke()); - - - _sut = new BackendFxApplication(_fakes.CompositionRoot, _fakes.MessageBus, A.Fake()); + _sut = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); } - - private readonly IBackendFxApplication _sut; - private readonly DiTestFakes _fakes; - [Fact] public void CanWaitForBoot() @@ -69,13 +52,14 @@ public void ProvidesExceptionLoggingAsyncInvoker() [Fact] public void ProvidesDomainEventAggregator() { - using (IInjectionScope scope = _sut.CompositionRoot.BeginScope()) + using (IServiceScope scope = _sut.CompositionRoot.BeginScope()) { - var domainEventAggregator = scope.InstanceProvider.GetInstance(); + var domainEventAggregator = scope.ServiceProvider.GetService(); Assert.NotNull(domainEventAggregator); } - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).MustHaveHappenedOnceExactly(); + A.CallTo(() => _fakes.ServiceProvider.GetService(A.That.IsEqualTo(typeof(IDomainEventAggregator)))) + .MustHaveHappenedOnceExactly(); } [Fact] @@ -84,34 +68,7 @@ public void ProvidesExceptionLoggingInvoker() Assert.IsType(_sut.Invoker); } - [Fact] - public void IntegratesWithMessageBus() - { - A.CallTo(() => _fakes.MessageBus.ProvideInvoker(A._)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void ProvidesMessageBusScope() - { - using (IInjectionScope scope = _sut.CompositionRoot.BeginScope()) - { - var messageBusScope = scope.InstanceProvider.GetInstance(); - Assert.NotNull(messageBusScope); - } - - A.CallTo(() => _fakes.InstanceProvider.GetInstance()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void RegistersInfrastructureModule() - { - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped, CurrentCorrelationHolder>()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped,CurrentIdentityHolder>()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped(A>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InfrastructureModule.RegisterScoped,CurrentTenantIdHolder>()).MustHaveHappenedOnceExactly(); - } - + [Fact] public void VerifiesCompositionRootOnBoot() { diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs index 0f58d88e..2af5a01b 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs @@ -24,23 +24,23 @@ public TheBackendFxApplicationAsyncInvoker(ITestOutputHelper output): base(outpu [Fact] public async Task BeginsNewScopeForEveryInvocation() { - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedOnceExactly(); + A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); - A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedTwiceExactly(); + A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedTwiceExactly(); } [Fact] public async Task BeginsNewOperationForEveryInvocation() { - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -49,7 +49,7 @@ public async Task BeginsNewOperationForEveryInvocation() public async Task DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -57,7 +57,7 @@ public async Task DoesNotCatchFrameworkExceptions() [Fact] public async Task DoesNotCatchOperationExceptions() { - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(ip => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(sp => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -66,7 +66,7 @@ public async Task DoesNotCatchOperationExceptions() public async Task MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); + await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -74,7 +74,7 @@ public async Task MaintainsCorrelationIdOnInvocation() public async Task MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - await _sut.InvokeAsync(ip => Task.CompletedTask, identity, new TenantId(123)); + await _sut.InvokeAsync(sp => Task.CompletedTask, identity, new TenantId(123)); A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); } @@ -82,20 +82,20 @@ public async Task MaintainsIdentityOnInvocation() public async Task MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - await _sut.InvokeAsync(ip => Task.CompletedTask, new AnonymousIdentity(), tenantId); + await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), tenantId); A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); } [Fact] - public async Task ProvidesInstanceProviderForInvocation() + public async Task ProvidesServiceProviderForInvocation() { - IInstanceProvider provided = null; - await _sut.InvokeAsync(ip => + IServiceProvider provided = null; + await _sut.InvokeAsync(sp => { - provided = ip; + provided = sp; return Task.CompletedTask; }, new AnonymousIdentity(), new TenantId(111)); - Assert.StrictEqual(_fakes.InstanceProvider, provided); + Assert.StrictEqual(_fakes.ServiceProvider, provided); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs index 88f4c218..fc4702e9 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs @@ -23,23 +23,23 @@ public TheBackendFxApplicationInvoker(ITestOutputHelper output): base(output) [Fact] public void BeginsNewScopeForEveryInvocation() { - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); - A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedOnceExactly(); + A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedOnceExactly(); - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); - A.CallTo(() => _fakes.InjectionScope.Dispose()).MustHaveHappenedTwiceExactly(); + A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedTwiceExactly(); } [Fact] public void BeginsNewOperationForEveryInvocation() { - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -48,7 +48,7 @@ public void BeginsNewOperationForEveryInvocation() public void DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - Assert.Throws(() => _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(111))); + Assert.Throws(() => _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -56,7 +56,7 @@ public void DoesNotCatchFrameworkExceptions() [Fact] public void DoesNotCatchOperationExceptions() { - Assert.Throws(() => _sut.Invoke(ip => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + Assert.Throws(() => _sut.Invoke(sp => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -65,7 +65,7 @@ public void DoesNotCatchOperationExceptions() public void MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - _sut.Invoke(ip => { }, new AnonymousIdentity(), new TenantId(123), correlationId); + _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(123), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -73,7 +73,7 @@ public void MaintainsCorrelationIdOnInvocation() public void MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - _sut.Invoke(ip => { }, identity, new TenantId(123)); + _sut.Invoke(sp => { }, identity, new TenantId(123)); A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); } @@ -81,17 +81,17 @@ public void MaintainsIdentityOnInvocation() public void MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - _sut.Invoke(ip => { }, new AnonymousIdentity(), tenantId); + _sut.Invoke(sp => { }, new AnonymousIdentity(), tenantId); A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); } [Fact] - public void ProvidesInstanceProviderForInvocation() + public void ProvidesServiceProviderForInvocation() { - IInstanceProvider provided = null; - _sut.Invoke(ip => provided = ip, new AnonymousIdentity(), new TenantId(111)); - Assert.StrictEqual(_fakes.InstanceProvider, provided); + IServiceProvider provided = null; + _sut.Invoke(sp => provided = sp, new AnonymousIdentity(), new TenantId(111)); + Assert.StrictEqual(_fakes.ServiceProvider, provided); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs index 055f9027..9e56f8c9 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs @@ -2,7 +2,6 @@ using Backend.Fx.Environment.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Xunit; using Xunit.Abstractions; @@ -13,7 +12,7 @@ public class TheBackendFxDbApplication : TestWithLogging { public TheBackendFxDbApplication(ITestOutputHelper output): base(output) { - IBackendFxApplication application = new BackendFxApplication(_fakes.CompositionRoot, _fakes.MessageBus, A.Fake()); + IBackendFxApplication application = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); _sut = new BackendFxDbApplication(_databaseBootstrapper, _databaseAvailabilityAwaiter, application); } @@ -52,10 +51,6 @@ public void DelegatesAllCalls() IBackendFxApplicationInvoker i = sut.Invoker; A.CallTo(() => application.Invoker).MustHaveHappenedOnceExactly(); - // ReSharper disable once UnusedVariable - IMessageBus mb = sut.MessageBus; - A.CallTo(() => application.MessageBus).MustHaveHappenedOnceExactly(); - sut.BootAsync(); A.CallTo(() => application.BootAsync(A._)).MustHaveHappenedOnceExactly(); diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs deleted file mode 100644 index 1b4419ac..00000000 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheInjectionScope.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Backend.Fx.Patterns.DependencyInjection; -using FakeItEasy; -using Xunit; -using Xunit.Abstractions; - -namespace Backend.Fx.Tests.Patterns.DependencyInjection -{ - public class TheInjectionScope : TestWithLogging - { - private readonly IInstanceProvider _instanceProvider = A.Fake(); - - private class TestInjectionScope : InjectionScope - { - public TestInjectionScope(int sequenceNumber, IInstanceProvider instanceProvider) : base(sequenceNumber) - { - InstanceProvider = instanceProvider; - } - - public override IInstanceProvider InstanceProvider { get; } - - public override void Dispose() - { - throw new NotImplementedException(); - } - } - - [Fact] - public void InitializesWithSequenceNumber() - { - var injectionScope = new TestInjectionScope(42, _instanceProvider); - Assert.Equal(42, injectionScope.SequenceNumber); - } - - [Fact] - public void KeepsInstanceProvider() - { - var injectionScope = new TestInjectionScope(42, _instanceProvider); - Assert.Equal(_instanceProvider, injectionScope.InstanceProvider); - } - - public TheInjectionScope(ITestOutputHelper output) : base(output) - { - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index 97e901cd..5ca8a3c3 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics; using System.Linq; using System.Threading; @@ -36,7 +37,7 @@ private async Task InvokeSomeActions(int count, IBackendFxApplicationInvoker inv await Task.WhenAll(tasks); } - private void DoTheAction(IInstanceProvider _) + private void DoTheAction(IServiceProvider _) { _output.WriteLine("start"); Thread.Sleep(_actionDuration); diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs index 2c13c1fd..0b834da7 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Backend.Fx.Patterns.EventAggregation.Domain; using FakeItEasy; using Xunit; @@ -13,16 +15,16 @@ public void CallsAllDomainEventHandlers() { var handler1 = new TestDomainEventHandler(); var handler2 = new TestDomainEventHandler(); - var fakeDomainEventHandlerProvider = A.Fake(); - A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()).Returns(new[] {handler1, handler2}); + var fakeServiceProvider = A.Fake(); + A.CallTo(() => fakeServiceProvider + .GetService(A.That.IsEqualTo(typeof(IEnumerable>)))) + .Returns(new[] {handler1, handler2}.AsEnumerable()); - IDomainEventAggregator sut = new DomainEventAggregator(fakeDomainEventHandlerProvider); + IDomainEventAggregator sut = new DomainEventAggregator(fakeServiceProvider); sut.PublishDomainEvent(new TestDomainEvent(4711)); sut.RaiseEvents(); - A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()).MustHaveHappenedOnceExactly(); - Assert.Single(handler1.Events); Assert.Equal(4711, handler1.Events[0].Id); @@ -36,10 +38,12 @@ public void CallsAllDomainEventHandlers() public void DoesNotSwallowExceptionOnDomainEventHandling() { IDomainEventHandler handler = new FailingDomainEventHandler(); - var fakeDomainEventHandlerProvider = A.Fake(); - A.CallTo(() => fakeDomainEventHandlerProvider.GetAllEventHandlers()).Returns(new[] {handler}); + var fakeServiceProvider = A.Fake(); + A.CallTo(() => fakeServiceProvider + .GetService(A.That.IsEqualTo(typeof(IEnumerable>)))) + .Returns(new[] {handler}.AsEnumerable()); - IDomainEventAggregator sut = new DomainEventAggregator(fakeDomainEventHandlerProvider); + IDomainEventAggregator sut = new DomainEventAggregator(fakeServiceProvider); sut.PublishDomainEvent(new TestDomainEvent(444)); Assert.Throws(() => sut.RaiseEvents()); } diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs index 17c804d8..e30313bc 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs @@ -70,29 +70,29 @@ public TestInvoker() A.CallTo(() => TypedHandler.Handle(A._)).Invokes((TestIntegrationEvent e) => IntegrationEvent.TypedProcessed.Set()); A.CallTo(() => DynamicHandler.Handle(new object())).WithAnyArguments().Invokes((object e) => IntegrationEvent.DynamicProcessed.Set()); - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(TypedMessageHandler)))) + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(TypedMessageHandler)))) .Returns(new TypedMessageHandler(TypedHandler)); - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(LongRunningMessageHandler)))) + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(LongRunningMessageHandler)))) .Returns(new LongRunningMessageHandler(TypedHandler)); - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(ThrowingTypedMessageHandler)))) + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(ThrowingTypedMessageHandler)))) .Returns(new ThrowingTypedMessageHandler(TypedHandler)); - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(DynamicMessageHandler)))) + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(DynamicMessageHandler)))) .Returns(new DynamicMessageHandler(DynamicHandler)); - A.CallTo(() => FakeInstanceProvider.GetInstance(A.That.IsEqualTo(typeof(ThrowingDynamicMessageHandler)))) + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(ThrowingDynamicMessageHandler)))) .Returns(new ThrowingDynamicMessageHandler(DynamicHandler)); } public IIntegrationMessageHandler TypedHandler { get; } = A.Fake>(); public IIntegrationMessageHandler DynamicHandler { get; } = A.Fake(); - public IInstanceProvider FakeInstanceProvider { get; } = A.Fake(); + public IServiceProvider FakeServiceProvider { get; } = A.Fake(); - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) { - action(FakeInstanceProvider); + action(FakeServiceProvider); } } From 5124396d035fe2c690cc2bc7599709be89b3a781 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Mon, 13 Jun 2022 13:44:44 -0300 Subject: [PATCH 03/51] refactor(composition root) --- .../DependencyInjection/ICompositionRoot.cs | 42 ++++++++++- .../MicrosoftCompositionRoot.cs | 27 +++---- .../SimpleInjectorCompositionRoot.cs | 72 +++++++++---------- 3 files changed, 81 insertions(+), 60 deletions(-) diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs index c32de752..973a17b0 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; +using Backend.Fx.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.DependencyInjection { @@ -15,16 +18,49 @@ public interface ICompositionRoot : IDisposable void Verify(); void RegisterModules(params IModule[] modules); - + void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor); - + void RegisterServiceDescriptors(IEnumerable serviceDescriptors); IServiceScope BeginScope(); - + /// /// Access to the container's resolution functionality /// IServiceProvider ServiceProvider { get; } } + + + public abstract class CompositionRoot : ICompositionRoot + { + private static readonly ILogger Logger = Log.Create(); + + public abstract IServiceProvider ServiceProvider { get; } + + public abstract void Verify(); + + public virtual void RegisterModules(params IModule[] modules) + { + foreach (var module in modules) + { + Logger.LogInformation("Registering {Module}", module.GetType().Name); + module.Register(this); + } + } + + public abstract void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor); + + public abstract void RegisterServiceDescriptors(IEnumerable serviceDescriptors); + + public abstract IServiceScope BeginScope(); + + protected abstract void Dispose(bool disposing); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs index ace4192c..863399a2 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -5,30 +5,22 @@ namespace Backend.Fx.MicrosoftDependencyInjection { - public class MicrosoftCompositionRoot : ICompositionRoot + public class MicrosoftCompositionRoot : CompositionRoot { private readonly IServiceCollection _serviceCollection = new ServiceCollection(); private readonly Lazy _serviceProvider; + public MicrosoftCompositionRoot() { _serviceProvider = new Lazy(() => _serviceCollection.BuildServiceProvider()); } - public void Dispose() - { - } - public void Verify() + public override IServiceProvider ServiceProvider => _serviceProvider.Value; + + public override void Verify() { } - public void RegisterModules(params IModule[] modules) - { - foreach (var module in modules) - { - module.Register(this); - } - } - - public void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) + public override void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) { if (_serviceProvider.IsValueCreated) { @@ -37,7 +29,7 @@ public void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) _serviceCollection.Add(serviceDescriptor); } - public void RegisterServiceDescriptors(IEnumerable serviceDescriptors) + public override void RegisterServiceDescriptors(IEnumerable serviceDescriptors) { foreach (var serviceDescriptor in serviceDescriptors) { @@ -45,11 +37,12 @@ public void RegisterServiceDescriptors(IEnumerable serviceDes } } - public IServiceScope BeginScope() + public override IServiceScope BeginScope() { return ServiceProvider.CreateScope(); } - public IServiceProvider ServiceProvider => _serviceProvider.Value; + protected override void Dispose(bool disposing) + { } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index e2a42f29..6317781d 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -16,9 +16,11 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection /// /// Provides a reusable composition root assuming Simple Injector as container /// - public class SimpleInjectorCompositionRoot : ICompositionRoot + public class SimpleInjectorCompositionRoot : CompositionRoot { private static readonly ILogger Logger = Log.Create(); + private readonly Container _container = new Container(); + private readonly ScopedLifestyle _scopedLifestyle; /// /// This constructor creates a composition root that prefers scoped lifestyle @@ -30,47 +32,34 @@ public SimpleInjectorCompositionRoot() public SimpleInjectorCompositionRoot(ILifestyleSelectionBehavior lifestyleBehavior, ScopedLifestyle scopedLifestyle) { Logger.LogInformation("Initializing SimpleInjector"); - ScopedLifestyle = scopedLifestyle; - Container.Options.LifestyleSelectionBehavior = lifestyleBehavior; - Container.Options.DefaultScopedLifestyle = ScopedLifestyle; - ServiceProvider = Container; + _scopedLifestyle = scopedLifestyle; + _container.Options.LifestyleSelectionBehavior = lifestyleBehavior; + _container.Options.DefaultScopedLifestyle = _scopedLifestyle; } - public Container Container { get; } = new Container(); - - internal ScopedLifestyle ScopedLifestyle { get; } - + #region ICompositionRoot implementation - public void RegisterModules(params IModule[] modules) - { - foreach (var module in modules) - { - Logger.LogInformation("Registering {Module}", module.GetType().Name); - module.Register(this); - } - } - - public void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) + public override void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) { if (serviceDescriptor.ImplementationType != null) { - Container.Register( + _container.Register( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, serviceDescriptor.Lifetime.Translate()); } else if (serviceDescriptor.ImplementationFactory != null) { - Container.Register( + _container.Register( serviceDescriptor.ServiceType, - () => serviceDescriptor.ImplementationFactory(Container), + () => serviceDescriptor.ImplementationFactory(_container), serviceDescriptor.Lifetime.Translate()); } else if (serviceDescriptor.ImplementationInstance != null && serviceDescriptor.Lifetime == ServiceLifetime.Singleton) { - Container.RegisterInstance(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationInstance); + _container.RegisterInstance(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationInstance); } else { @@ -78,7 +67,7 @@ public void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) } } - public void RegisterServiceDescriptors(IEnumerable serviceDescriptors) + public override void RegisterServiceDescriptors(IEnumerable serviceDescriptors) { var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); @@ -89,19 +78,19 @@ public void RegisterServiceDescriptors(IEnumerable serviceDes foreach (var serviceDescriptor in serviceDescriptorArray) { - Container.Collection.Append( + _container.Collection.Append( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, serviceDescriptor.Lifetime.Translate()); } } - public void Verify() + public override void Verify() { Logger.LogInformation("container is being verified"); try { - Container.Verify(VerificationOption.VerifyAndDiagnose); + _container.Verify(VerificationOption.VerifyAndDiagnose); } catch (Exception ex) { @@ -111,26 +100,18 @@ public void Verify() } /// - public IServiceScope BeginScope() + public override IServiceScope BeginScope() { - return new SimpleInjectorServiceScope(AsyncScopedLifestyle.BeginScope(Container)); + return new SimpleInjectorServiceScope(AsyncScopedLifestyle.BeginScope(_container)); } - public IServiceProvider ServiceProvider { get; } + public override IServiceProvider ServiceProvider => _container; public Scope GetCurrentScope() { - return ScopedLifestyle.GetCurrentScope(Container); - } - #endregion - - #region IDisposable implementation - public void Dispose() - { - Container?.Dispose(); + return _scopedLifestyle.GetCurrentScope(_container); } - #endregion - + /// /// A behavior that defaults to scoped life style for injected instances /// @@ -141,5 +122,16 @@ public Lifestyle SelectLifestyle(Type implementationType) return Lifestyle.Scoped; } } + #endregion + + #region IDisposable implementation + protected override void Dispose(bool disposing) + { + if (disposing) + { + _container?.Dispose(); + } + } + #endregion } } \ No newline at end of file From e68d9b59dd0de6590239d7d7488f990734cf679d Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Tue, 14 Jun 2022 18:29:32 -0300 Subject: [PATCH 04/51] (refactor)tests --- Backend.Fx.sln | 7 - .../AllTenantBackendFxApplicationInvoker.cs | 2 +- .../MultiTenancy/SingleTenantApplication.cs | 9 +- ...pplication.cs => PersistentApplication.cs} | 6 +- .../Backend.Fx/Extensions/ReflectionEx.cs | 12 +- .../Authorization/AuthorizingApplication.cs | 71 ++++ .../DataGeneratingApplication.cs | 26 +- .../DataGeneration/DataGenerationContext.cs | 7 +- .../DataGeneration/DataGenerationModule.cs | 26 ++ .../BackendFxApplication.cs | 22 +- .../BackendFxApplicationDecorator.cs | 8 +- .../BackendFxApplicationInvoker.cs | 2 - .../DependencyInjection/DomainModule.cs | 56 ++- .../DependencyInjection/ICompositionRoot.cs | 13 +- .../Patterns/DependencyInjection/IModule.cs | 4 +- .../ServiceDescriptorEx.cs | 38 +++ .../Integration/MessageBusApplication.cs | 16 + .../Integration/MessageBusModule.cs | 10 +- .../RaiseIntegrationEventsInvokerDecorator.cs | 58 ++++ ...uentializingBackendFxApplicationInvoker.cs | 3 +- ...bApplication.cs => ApplicationWithJobs.cs} | 5 +- .../Backend.Fx/Patterns/Jobs/JobModule.cs | 2 +- .../Bootstrapping/AspNetCoreApplication.cs | 12 + .../Bootstrapping/AspNetCoreModule.cs | 38 +++ ...BackendFxApplicationControllerActivator.cs | 2 +- .../AggregateMapping.cs | 2 +- .../Backend.Fx.EfCore6Persistence.csproj | 2 +- .../DbContextTransactionOperationDecorator.cs | 2 +- .../Bootstrapping/EfCorePersistenceModule.cs | 173 +++++++--- .../Bootstrapping/IDbConnectionFactory.cs | 2 +- .../DbContextExtensions.cs | 3 +- .../Backend.Fx.EfCore6Persistence/EfFlush.cs | 5 +- .../EfRepository.cs | 2 +- .../EntityQueryable.cs | 2 +- .../IAggregateMapping.cs | 2 +- .../Mssql/MsSqlSequence.cs | 10 +- .../Oracle/OracleSequence.cs | 10 +- .../PlainAggregateMapping.cs | 2 +- .../Postgres/PostgresSequence.cs | 10 +- .../Bootstrapping/EfCorePersistenceModule.cs | 126 +++++-- .../DbContextExtensions.cs | 1 - .../Backend.Fx.EfCorePersistence/EfFlush.cs | 3 +- ...end.Fx.MicrosoftDependencyInjection.csproj | 1 + .../MicrosoftCompositionRoot.cs | 52 ++- .../MicrosoftDependencyInjectionExtensions.cs | 2 +- .../SimpleInjectorCompositionRoot.cs | 161 ++++++--- .../SimpleInjectorServiceScopeFactory.cs | 20 ++ .../Backend.Fx.AspNetCore.Tests.csproj | 4 + .../SampleApp/Runtime/SampleApplication.cs | 57 ---- .../SampleApplicationHostedService.cs | 22 +- .../TheBackendFxMvcApplication.cs | 2 +- ...Backend.Fx.EfCore6Persistence.Tests.csproj | 31 +- .../DbConnectionEx.cs | 2 +- .../DummyImpl/Domain/Blog.cs | 2 +- .../DummyImpl/Domain/BlogAuthorization.cs | 2 +- .../DummyImpl/Domain/Blogger.cs | 2 +- .../DummyImpl/Domain/BloggerAuthorization.cs | 10 + .../DummyImpl/Domain/Post.cs | 2 +- .../DummyImpl/Persistence/BlogMapping.cs | 4 +- .../DummyImpl/Persistence/BloggerMapping.cs | 4 +- .../DummyImpl/Persistence/TestDbContext.cs | 4 +- .../Persistence/TestDbContextFactory.cs | 2 +- .../Fixtures/DatabaseFixture.cs | 4 +- .../Fixtures/SqlServerDatabaseFixture.cs | 4 +- .../Fixtures/SqliteDatabaseFixture.cs | 4 +- .../Fixtures/TestDbSession.cs | 4 +- .../20190624150947_Initial.Designer.cs | 2 +- .../Migrations/TestDbContextModelSnapshot.cs | 2 +- .../TheDbApplicationWithEfCore.cs | 319 ++++++++++++++++++ .../TheDbContext.cs | 7 +- .../TheRepositoryOfComposedAggregate.cs | 8 +- .../TheRepositoryOfPlainAggregate.cs | 8 +- .../Backend.Fx.EfCorePersistence.Tests.csproj | 27 +- .../DummyImpl/Domain/BloggerAuthorization.cs | 10 + .../TheDbApplicationWithEfCore.cs | 314 +++++++++++++++++ .../TheDbContext.cs | 1 - .../TheRabbitMqMessageBus.cs | 3 +- ...leInjectorDependencyInjection.Tests.csproj | 27 -- .../ASimpleDomain/ADemoAggregateGenerator.cs | 32 -- .../DummyImpl/ASimpleDomain/ADomainEvent.cs | 8 - .../ASimpleDomain/ADomainEventHandler1.cs | 11 - .../ASimpleDomain/ADomainEventHandler2.cs | 11 - .../ASimpleDomain/ADomainEventHandler3.cs | 11 - .../DummyImpl/ASimpleDomain/ADomainModule.cs | 21 -- .../DummyImpl/ASimpleDomain/ADomainService.cs | 13 - .../DummyImpl/ASimpleDomain/AJob.cs | 12 - .../ASimpleDomain/AProdAggregateGenerator.cs | 32 -- .../ASimpleDomain/ASingletonService.cs | 9 - .../DummyImpl/ASimpleDomain/AnAggregate.cs | 14 - .../ASimpleDomain/AnAggregateAuthorization.cs | 6 - .../ASimpleDomain/AnApplicationService.cs | 10 - .../ASimpleDomain/AnIntegrationEvent.cs | 16 - .../DummyImpl/ASimpleDomain/SomeState.cs | 7 - .../TestConfig.cs | 15 - ...configuredSimpleInjectorCompositionRoot.cs | 43 --- .../TheSimpleInjectorCompositionRoot.cs | 169 ---------- .../Backend.Fx.Tests/Backend.Fx.Tests.csproj | 2 + ...TheAllTenantBackendFxApplicationInvoker.cs | 1 + .../TheSingleTenantApplication.cs | 12 +- .../MultiTenancy/TheTenantService.cs | 3 - .../TheAuthorizingApplication.cs | 49 +++ .../TheDataGenerationContext.cs | 56 +-- .../CompositionRootType.cs | 8 + .../DependencyInjection/DITestFakes.cs | 3 - .../TheBackendFxApplication.cs | 8 +- .../TheBackendFxDbApplication.cs | 4 +- .../DependencyInjection/TheDomainModule.cs | 145 ++++++++ ...uentializingBackendFxApplicationInvoker.cs | 1 + .../Integration/TheMessageBusApplication.cs | 33 ++ .../Patterns/Jobs/TheApplicationWithJobs.cs | 108 ++++++ tests/Backend.Fx.Tests/TestHelpers.cs | 21 ++ tests/Backend.Fx.Tests/TestWithLogging.cs | 1 - 112 files changed, 1915 insertions(+), 922 deletions(-) rename src/abstractions/Backend.Fx/Environment/Persistence/{BackendFxDbApplication.cs => PersistentApplication.cs} (82%) create mode 100644 src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs rename src/abstractions/Backend.Fx/Patterns/{DependencyInjection => EventAggregation/Integration}/SequentializingBackendFxApplicationInvoker.cs (90%) rename src/abstractions/Backend.Fx/Patterns/Jobs/{JobApplication.cs => ApplicationWithJobs.cs} (61%) create mode 100644 src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs create mode 100644 src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs create mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScopeFactory.cs delete mode 100644 tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs create mode 100644 tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs create mode 100644 tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs delete mode 100644 tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs create mode 100644 tests/Backend.Fx.Tests/TestHelpers.cs diff --git a/Backend.Fx.sln b/Backend.Fx.sln index bf9b3ab1..3cb362d4 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -33,8 +33,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.SimpleInjectorDe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.AspNetCore", "src\environments\Backend.Fx.AspNetCore\Backend.Fx.AspNetCore.csproj", "{25746028-5116-4600-A0C4-35DE0C468A8F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.SimpleInjectorDependencyInjection.Tests", "tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests\Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj", "{D98AED23-ABB8-4130-9612-54AEFE9D2272}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.InMemoryPersistence", "src\implementations\Backend.Fx.InMemoryPersistence\Backend.Fx.InMemoryPersistence.csproj", "{0B8F13CA-1347-4655-9D41-AED21B1AFAC4}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.RabbitMq.Tests", "tests\Backend.Fx.RabbitMq.Tests\Backend.Fx.RabbitMq.Tests.csproj", "{6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A}" @@ -97,10 +95,6 @@ Global {25746028-5116-4600-A0C4-35DE0C468A8F}.Debug|Any CPU.Build.0 = Debug|Any CPU {25746028-5116-4600-A0C4-35DE0C468A8F}.Release|Any CPU.ActiveCfg = Release|Any CPU {25746028-5116-4600-A0C4-35DE0C468A8F}.Release|Any CPU.Build.0 = Release|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D98AED23-ABB8-4130-9612-54AEFE9D2272}.Release|Any CPU.Build.0 = Release|Any CPU {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B8F13CA-1347-4655-9D41-AED21B1AFAC4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -137,7 +131,6 @@ Global {739A7296-579F-4D9A-BC73-DCECD260D7A0} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092} {581DCC00-9246-4A2E-AE31-206742B2746A} = {A742F814-725A-44ED-95E6-98E142738E9D} {25746028-5116-4600-A0C4-35DE0C468A8F} = {56ACAE69-F7F0-4FF2-BEE6-4B079481CF9A} - {D98AED23-ABB8-4130-9612-54AEFE9D2272} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {6D0A5E9D-2FA5-4CC9-96B0-C2C871335E3A} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {DF40E1E8-FB19-455E-9CED-212C544AA8BC} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {16EBBF6D-EA66-4E14-BE2D-1900CBC747F7} = {6B64354E-D95B-4711-BAF6-B32049C90CD9} diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs index 9b4abccc..dda74153 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs @@ -23,7 +23,7 @@ public AllTenantBackendFxApplicationInvoker(ITenantIdProvider tenantIdProvider, public void Invoke(Action action) { var correlationId = Guid.NewGuid(); - TenantId[] tenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds().Concat(_tenantIdProvider.GetActiveProductionTenantIds()).ToArray(); + TenantId[] tenantIds = _tenantIdProvider.GetActiveTenantIds(); Logger.LogDebug("Action will be called in tenants: {TenantIds}", string.Join(",", tenantIds.Select(t => t.ToString()))); foreach (TenantId tenantId in tenantIds) { diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs index 6c28a0e5..95fc06a9 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs @@ -1,11 +1,11 @@ using System.Linq; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Patterns.DependencyInjection; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Environment.MultiTenancy { - public class SingleTenantApplication + public class SingleTenantApplication : BackendFxApplicationDecorator { private static readonly ILogger Logger = Log.Create(); private readonly ITenantService _tenantService; @@ -13,9 +13,10 @@ public class SingleTenantApplication private readonly object _padlock = new object(); public SingleTenantApplication( - IMessageBus messageBus, ITenantRepository tenantRepository, - bool singleTenantIsDemoTenant) + bool singleTenantIsDemoTenant, + IBackendFxApplication application) + : base(application) { _tenantService = new TenantService(tenantRepository); _singleTenantIsDemoTenant = singleTenantIsDemoTenant; diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs b/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs similarity index 82% rename from src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs rename to src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs index 6bd88112..1cf8501a 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/BackendFxDbApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs @@ -7,14 +7,14 @@ namespace Backend.Fx.Environment.Persistence { - public class BackendFxDbApplication : BackendFxApplicationDecorator + public class PersistentApplication : BackendFxApplicationDecorator { - private static readonly ILogger Logger = Log.Create(); + private static readonly ILogger Logger = Log.Create(); private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; private readonly IDatabaseBootstrapper _databaseBootstrapper; - public BackendFxDbApplication(IDatabaseBootstrapper databaseBootstrapper, + public PersistentApplication(IDatabaseBootstrapper databaseBootstrapper, IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, IBackendFxApplication application) : base(application) { diff --git a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs index 378ad0b6..a862570f 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs @@ -7,15 +7,19 @@ namespace Backend.Fx.Extensions { public static class ReflectionEx { + public static IEnumerable GetImplementingTypes(this IEnumerable assemblies) + { + return assemblies.GetImplementingTypes(typeof(TService)); + } + public static IEnumerable GetImplementingTypes(this IEnumerable assemblies, Type serviceType) { return assemblies .Distinct() .Where(assembly => !assembly.IsDynamic) - .SelectMany(assembly => assembly.GetTypes(), (assembly, type) => new {assembly, type}) - .Where(t => t.type.IsClass && !t.type.IsAbstract) - .Where(t => serviceType.IsAssignableFrom(t.type)) - .Select(t => t.type); + .SelectMany(assembly => assembly.GetTypes()) + .Where(t => t.IsClass && !t.IsAbstract) + .Where(serviceType.IsAssignableFrom); } public static bool IsImplementationOfOpenGenericInterface(this Type t, Type openGenericInterface) diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs b/src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs new file mode 100644 index 00000000..a4932f46 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Reflection; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Extensions; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Backend.Fx.Patterns.Authorization +{ + public class AuthorizingApplication : BackendFxApplicationDecorator + { + public AuthorizingApplication(IBackendFxApplication application) : base(application) + { + application.CompositionRoot.RegisterModules(new AuthorizationModule(Assemblies)); + } + } + + public class AuthorizationModule : IModule + { + private static readonly ILogger Logger = Log.Create(); + private readonly Assembly[] _assemblies; + private readonly string _assembliesForLogging; + + public AuthorizationModule(Assembly[] assemblies) + { + _assemblies = assemblies; + _assembliesForLogging = string.Join(",", _assemblies.Select(ass => ass.GetName().Name)); + } + + public void Register(ICompositionRoot compositionRoot) + { + Logger.LogDebug("Registering authorization services from {Assemblies}", _assembliesForLogging); + + var aggregateRootTypes = _assemblies.GetImplementingTypes(typeof(AggregateRoot)).ToArray(); + foreach (var aggregateRootType in aggregateRootTypes) + { + var aggregateAuthorizationTypes = _assemblies + .GetImplementingTypes(typeof(IAggregateAuthorization<>).MakeGenericType(aggregateRootType)) + .ToArray(); + + foreach (Type aggregateAuthorizationType in aggregateAuthorizationTypes) + { + var serviceTypes = aggregateAuthorizationType + .GetTypeInfo() + .ImplementedInterfaces + .Where(i => i.GetTypeInfo().IsGenericType + && i.GenericTypeArguments.Length == 1 + && typeof(AggregateRoot).GetTypeInfo() + .IsAssignableFrom(i.GenericTypeArguments[0].GetTypeInfo())); + + foreach (Type serviceType in serviceTypes) + { + Logger.LogDebug( + "Registering scoped authorization service {ServiceType} with implementation {ImplementationType}", + serviceType.Name, + aggregateAuthorizationType.Name); + compositionRoot.Register( + new ServiceDescriptor( + serviceType, + aggregateAuthorizationType, + ServiceLifetime.Scoped)); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs index 25f7d82f..3e6f6216 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs @@ -1,13 +1,9 @@ -using System.Linq; -using System.Reflection; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.DataGeneration @@ -91,24 +87,4 @@ private void SeedDataForAllActiveTenants() // })); // } } - - public class DataGenerationModule : IModule - { - private readonly Assembly[] _assemblies; - - public DataGenerationModule(Assembly[] assemblies) - { - _assemblies = assemblies; - } - - public void Register(ICompositionRoot compositionRoot) - { - ServiceDescriptor[] serviceDescriptors = _assemblies - .GetImplementingTypes(typeof(IDataGenerator)) - .Select(t => new ServiceDescriptor(typeof(IDataGenerator), t, ServiceLifetime.Scoped)) - .ToArray(); - - compositionRoot.RegisterServiceDescriptors(serviceDescriptors); - } - } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs index 3129edcf..1306758a 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs @@ -63,14 +63,15 @@ private static Type[] GetDataGeneratorTypes(IServiceProvider serviceProvider, bo .ServiceProvider .GetServices() .OrderBy(dg => dg.Priority) - .Select(dg => dg.GetType()); + .Select(dg => dg.GetType()) + .ToArray(); if (!includeDemoDataGenerators) { - dataGenerators = dataGenerators.Where(dg => !typeof(IDemoDataGenerator).IsAssignableFrom(dg)); + dataGenerators = dataGenerators.Where(dg => !typeof(IDemoDataGenerator).IsAssignableFrom(dg)).ToArray(); } - return dataGenerators.ToArray(); + return dataGenerators; } } } diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs new file mode 100644 index 00000000..7f920e59 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs @@ -0,0 +1,26 @@ +using System.Linq; +using System.Reflection; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Patterns.DataGeneration +{ + public class DataGenerationModule : IModule + { + private readonly Assembly[] _assemblies; + + public DataGenerationModule(Assembly[] assemblies) + { + _assemblies = assemblies; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.RegisterCollection( + _assemblies + .GetImplementingTypes(typeof(IDataGenerator)) + .Select(t => new ServiceDescriptor(typeof(IDataGenerator), t, ServiceLifetime.Scoped))); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs index 16bf5bb4..c1c62968 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -59,6 +58,7 @@ public class BackendFxApplication : IBackendFxApplication /// /// The composition root of the dependency injection framework /// + /// public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger exceptionLogger, params Assembly[] assemblies) { var invoker = new BackendFxApplicationInvoker(compositionRoot); @@ -67,7 +67,7 @@ public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger e CompositionRoot = compositionRoot; ExceptionLogger = exceptionLogger; - Assemblies = assemblies.Concat(new[] {typeof(BackendFxApplication).Assembly}).Distinct().ToArray(); + Assemblies = assemblies; CompositionRoot.RegisterModules(new DomainModule(Assemblies)); } @@ -89,29 +89,15 @@ public Task BootAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } - public TBackendFxApplication As() - where TBackendFxApplication : class, IBackendFxApplication - { - return this as TBackendFxApplication; - } - public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default) { return _isBooted.Wait(timeoutMilliSeconds, cancellationToken); } - protected void Dispose(bool disposing) - { - if (disposing) - { - Logger.LogInformation("Application shut down initialized"); - CompositionRoot?.Dispose(); - } - } - public void Dispose() { - Dispose(true); + Logger.LogInformation("Application shut down initialized"); + CompositionRoot?.Dispose(); GC.SuppressFinalize(this); } } diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs index d36c5423..c447df7c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs @@ -17,13 +17,13 @@ protected BackendFxApplicationDecorator(IBackendFxApplication application) public Assembly[] Assemblies => _application.Assemblies; - public IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; + public virtual IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; - public ICompositionRoot CompositionRoot => _application.CompositionRoot; + public virtual ICompositionRoot CompositionRoot => _application.CompositionRoot; - public IExceptionLogger ExceptionLogger => _application.ExceptionLogger; + public virtual IExceptionLogger ExceptionLogger => _application.ExceptionLogger; - public IBackendFxApplicationInvoker Invoker => _application.Invoker; + public virtual IBackendFxApplicationInvoker Invoker => _application.Invoker; public virtual bool WaitForBoot( int timeoutMilliSeconds = int.MaxValue, diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs index 7ec72ed2..f972dbbe 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs @@ -3,10 +3,8 @@ using System.Threading.Tasks; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; using Backend.Fx.Logging; using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs index 10491739..f396f76c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs @@ -17,11 +17,10 @@ namespace Backend.Fx.Patterns.DependencyInjection { /// - /// Wires all public domain services to be injected as scoped instances provided by the array of domain assemblies: + /// Wires all public services to be injected as scoped instances provided by the array of domain assemblies: /// - s /// - s /// - s - /// - s /// public class DomainModule : IModule { @@ -41,27 +40,27 @@ public void Register(ICompositionRoot compositionRoot) RegisterDomainAndApplicationServices(compositionRoot); - RegisterAuthorization(compositionRoot); + RegisterPermissiveAuthorization(compositionRoot); RegisterDomainEventHandlers(compositionRoot); } private static void RegisterDomainInfrastructureServices(ICompositionRoot compositionRoot) { - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(typeof(IClock), _ => new WallClock(), ServiceLifetime.Scoped)); - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(typeof(IOperation), _ => new Operation(), ServiceLifetime.Scoped)); - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentCorrelationHolder(), ServiceLifetime.Scoped)); - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentIdentityHolder(), ServiceLifetime.Scoped)); - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentTenantIdHolder(), ServiceLifetime.Scoped)); - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(typeof(IDomainEventAggregator), sp => new DomainEventAggregator(sp), ServiceLifetime.Scoped)); } @@ -87,38 +86,23 @@ private void RegisterDomainAndApplicationServices(ICompositionRoot container) Logger.LogDebug("Registering scoped service {ServiceType} with implementation {ImplementationType}", serviceDescriptor.ServiceType.Name, serviceDescriptor.ImplementationType.Name); - container.RegisterServiceDescriptor(serviceDescriptor); + container.Register(serviceDescriptor); } } /// - /// Auto registering all aggregate authorization classes + /// Auto registering an "allow all" authorization for each aggregate root type /// - private void RegisterAuthorization(ICompositionRoot compositionRoot) + private void RegisterPermissiveAuthorization(ICompositionRoot compositionRoot) { - Logger.LogDebug("Registering authorization services from {Assemblies}", _assembliesForLogging); - var aggregateRootAuthorizationTypes = - _assemblies.GetImplementingTypes(typeof(IAggregateAuthorization<>)).ToArray(); - - foreach (Type aggregateAuthorizationType in aggregateRootAuthorizationTypes) + var aggregateRootTypes = _assemblies.GetImplementingTypes(typeof(AggregateRoot)).ToArray(); + foreach (var aggregateRootType in aggregateRootTypes) { - var serviceTypes = aggregateAuthorizationType - .GetTypeInfo() - .ImplementedInterfaces - .Where(i => i.GetTypeInfo().IsGenericType - && i.GenericTypeArguments.Length == 1 - && typeof(AggregateRoot).GetTypeInfo() - .IsAssignableFrom(i.GenericTypeArguments[0].GetTypeInfo())); - - foreach (Type serviceType in serviceTypes) - { - Logger.LogDebug( - "Registering scoped authorization service {ServiceType} with implementation {ImplementationType}", - serviceType.Name, - aggregateAuthorizationType.Name); - compositionRoot.RegisterServiceDescriptor(new ServiceDescriptor(serviceType, - aggregateAuthorizationType, ServiceLifetime.Scoped)); - } + compositionRoot.Register( + new ServiceDescriptor( + typeof(IAggregateAuthorization<>).MakeGenericType(aggregateRootType), + typeof(AllowAll<>).MakeGenericType(aggregateRootType), + ServiceLifetime.Singleton)); } } @@ -132,8 +116,8 @@ private void RegisterDomainEventHandlers(ICompositionRoot compositionRoot) .GetImplementingTypes(handlerTypeForThisDomainEventType) .Select(t => new ServiceDescriptor(handlerTypeForThisDomainEventType, t, ServiceLifetime.Scoped)) .ToArray(); - - compositionRoot.RegisterServiceDescriptors(serviceDescriptors); + + compositionRoot.RegisterCollection(serviceDescriptors); } } } diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs index 973a17b0..915109fc 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs @@ -19,9 +19,11 @@ public interface ICompositionRoot : IDisposable void RegisterModules(params IModule[] modules); - void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor); + void Register(ServiceDescriptor serviceDescriptor); - void RegisterServiceDescriptors(IEnumerable serviceDescriptors); + void RegisterDecorator(ServiceDescriptor serviceDescriptor); + + void RegisterCollection(IEnumerable serviceDescriptors); IServiceScope BeginScope(); @@ -49,9 +51,10 @@ public virtual void RegisterModules(params IModule[] modules) } } - public abstract void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor); - - public abstract void RegisterServiceDescriptors(IEnumerable serviceDescriptors); + public abstract void Register(ServiceDescriptor serviceDescriptor); + public abstract void RegisterDecorator(ServiceDescriptor serviceDescriptor); + + public abstract void RegisterCollection(IEnumerable serviceDescriptors); public abstract IServiceScope BeginScope(); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs index 3cacb956..762f2042 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.Patterns.DependencyInjection { /// /// A logically cohesive bunch of services diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs new file mode 100644 index 00000000..dcf78123 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs @@ -0,0 +1,38 @@ +using Backend.Fx.Extensions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Patterns.DependencyInjection +{ + public static class ServiceDescriptorEx + { + public static void LogDetails(this ServiceDescriptor serviceDescriptor, ILogger logger, string prefix = null) + { + logger.LogDebug("{Prefix} {Lifetime} registration for {ServiceType}: {ImplementationType}", + prefix, + serviceDescriptor.Lifetime.ToString(), + serviceDescriptor.ServiceType.GetDetailedTypeName(), + serviceDescriptor.GetImplementationTypeDescription()); + } + + public static string GetImplementationTypeDescription(this ServiceDescriptor serviceDescriptor) + { + if (serviceDescriptor.ImplementationFactory != null) + { + return serviceDescriptor.ImplementationFactory.GetType().GetDetailedTypeName(); + } + + if (serviceDescriptor.ImplementationType != null) + { + return serviceDescriptor.ImplementationType.GetDetailedTypeName(); + } + + if (serviceDescriptor.ImplementationInstance != null) + { + return serviceDescriptor.ImplementationInstance.GetType().GetDetailedTypeName(); + } + + return "Unknown"; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs index c793a583..cb3a28c1 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -7,12 +7,15 @@ namespace Backend.Fx.Patterns.EventAggregation.Integration public class MessageBusApplication : BackendFxApplicationDecorator { private readonly IMessageBus _messageBus; + private readonly IBackendFxApplicationInvoker _invoker; public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication application) : base(application) { application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); _messageBus = messageBus; + Invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot.ServiceProvider, base.Invoker); + AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot.ServiceProvider, base.AsyncInvoker); } public override async Task BootAsync(CancellationToken cancellationToken = default) @@ -21,5 +24,18 @@ public override async Task BootAsync(CancellationToken cancellationToken = defau _messageBus.ProvideInvoker(new SequentializingBackendFxApplicationInvoker(new ExceptionLoggingAndHandlingInvoker(ExceptionLogger, Invoker))); _messageBus.Connect(); } + + public override IBackendFxApplicationInvoker Invoker { get; } + + public override IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _messageBus.Dispose(); + } + base.Dispose(disposing); + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs index 0a7149c6..9632dbac 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs @@ -24,7 +24,7 @@ public void Register(ICompositionRoot compositionRoot) // note tht there should be no reason to access the singleton message bus instance from the service provider // register the message bus scope - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor( typeof(IMessageBusScope), sp => new MessageBusScope( @@ -39,19 +39,13 @@ public void Register(ICompositionRoot compositionRoot) Type handlerServiceType = typeof(IIntegrationMessageHandler<>).MakeGenericType(integrationEventType); foreach (var handlerImplementingType in _assemblies.GetImplementingTypes(handlerServiceType)) { - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor( handlerServiceType, handlerImplementingType, ServiceLifetime.Scoped)); } } - - //todo refactoring decorate IOperation to raise integration events - sync AND async! - // var messageBusScope = serviceScope.ServiceProvider.GetRequiredService(); - // AsyncHelper.RunSync(() => messageBusScope.RaiseEvents()); - - // dynamic subscriptions should be registered as domain or application service } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs new file mode 100644 index 00000000..567c86c9 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs @@ -0,0 +1,58 @@ +using System; +using System.Security.Principal; +using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Patterns.EventAggregation.Integration +{ + public class RaiseIntegrationEventsInvokerDecorator : IBackendFxApplicationInvoker + { + private readonly IServiceProvider _serviceProvider; + private readonly IBackendFxApplicationInvoker _invoker; + + public RaiseIntegrationEventsInvokerDecorator( + IServiceProvider serviceProvider, + IBackendFxApplicationInvoker invoker) + { + _serviceProvider = serviceProvider; + _invoker = invoker; + } + + public void Invoke( + Action action, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) + { + _invoker.Invoke(action, identity, tenantId, correlationId); + AsyncHelper.RunSync(() => _serviceProvider.GetRequiredService().RaiseEvents()); + } + } + + public class RaiseIntegrationEventsAsyncInvokerDecorator : IBackendFxApplicationAsyncInvoker + { + private readonly IServiceProvider _serviceProvider; + private readonly IBackendFxApplicationAsyncInvoker _invoker; + + public RaiseIntegrationEventsAsyncInvokerDecorator( + IServiceProvider serviceProvider, + IBackendFxApplicationAsyncInvoker invoker) + { + _serviceProvider = serviceProvider; + _invoker = invoker; + } + + public async Task InvokeAsync( + Func awaitableAsyncAction, + IIdentity identity, + TenantId tenantId, + Guid? correlationId = null) + { + await _invoker.InvokeAsync(awaitableAsyncAction, identity, tenantId, correlationId).ConfigureAwait(false); + await _serviceProvider.GetRequiredService().RaiseEvents().ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SequentializingBackendFxApplicationInvoker.cs similarity index 90% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs rename to src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SequentializingBackendFxApplicationInvoker.cs index c482961b..e56a4572 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/SequentializingBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SequentializingBackendFxApplicationInvoker.cs @@ -1,8 +1,9 @@ using System; using System.Security.Principal; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.Patterns.EventAggregation.Integration { /// /// Decorates the to prevent parallel invocation. diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs b/src/abstractions/Backend.Fx/Patterns/Jobs/ApplicationWithJobs.cs similarity index 61% rename from src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs rename to src/abstractions/Backend.Fx/Patterns/Jobs/ApplicationWithJobs.cs index ce62a31d..d060d98a 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/JobApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/Jobs/ApplicationWithJobs.cs @@ -2,10 +2,9 @@ namespace Backend.Fx.Patterns.Jobs { - - public class JobApplication : BackendFxApplicationDecorator + public abstract class ApplicationWithJobs : BackendFxApplicationDecorator { - public JobApplication(IBackendFxApplication application) + protected ApplicationWithJobs(IBackendFxApplication application) : base(application) { application.CompositionRoot.RegisterModules(new JobModule(application.Assemblies)); diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs b/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs index 61a519db..8d881a2b 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs @@ -20,7 +20,7 @@ public void Register(ICompositionRoot compositionRoot) // all jobs are dynamically registered foreach (Type jobType in _assemblies.GetImplementingTypes(typeof(IJob))) { - compositionRoot.RegisterServiceDescriptor( + compositionRoot.Register( new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)); } } diff --git a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs new file mode 100644 index 00000000..e7656117 --- /dev/null +++ b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs @@ -0,0 +1,12 @@ +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.AspNetCore.Bootstrapping +{ + public class AspNetCoreApplication : BackendFxApplicationDecorator + { + public AspNetCoreApplication(IBackendFxApplication application) : base(application) + { + application.CompositionRoot.RegisterModules(new AspNetCoreModule(Assemblies)); + } + } +} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs new file mode 100644 index 00000000..b59ad1e1 --- /dev/null +++ b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Reflection; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.AspNetCore.Bootstrapping +{ + public class AspNetCoreModule : IModule + { + private readonly IEnumerable _assemblies; + + public AspNetCoreModule(IEnumerable assemblies) + { + _assemblies = assemblies; + } + public void Register(ICompositionRoot compositionRoot) + { + RegisterAllImplementationsOf(compositionRoot); + RegisterAllImplementationsOf(compositionRoot); + RegisterAllImplementationsOf(compositionRoot); + } + + private void RegisterAllImplementationsOf(ICompositionRoot compositionRoot) + { + foreach (var controllerType in _assemblies.GetImplementingTypes()) + { + compositionRoot.Register( + new ServiceDescriptor( + controllerType, + controllerType, + ServiceLifetime.Scoped)); + } + } + } +} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs index ee5e2f76..02209493 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators { /// - /// This controller activator relies on an set before in the + /// This controller activator relies on an set before in the /// http context items dictionary. If non is to be found, the controller is activated /// using the default (without providing any ctor arguments). /// diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs index a7ce812b..46d130ff 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs @@ -4,7 +4,7 @@ using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public abstract class AggregateMapping : IAggregateMapping where T : AggregateRoot { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj b/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj index f8dc3d71..1a9aecf1 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj @@ -14,7 +14,7 @@ Marc Wittke anic GmbH All rights reserved. Distributed under the terms of the MIT License. - Persistence implementation for Backend.Fx using Entity Framework Core 6 + Persistence implementation for Backend.Fx using Entity Framework Core 2 False MIT https://github.com/marcwittke/Backend.Fx diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs index 6dedd0be..616d5d6c 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs @@ -4,7 +4,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCore6Persistence.Bootstrapping { public class DbContextTransactionOperationDecorator : DbTransactionOperationDecorator { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index a80e3e32..a3337416 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -1,16 +1,17 @@ using System; +using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCore6Persistence.Bootstrapping { public class EfCorePersistenceModule : IModule where TDbContext : DbContext @@ -19,63 +20,155 @@ public class EfCorePersistenceModule : IModule private readonly Action, IDbConnection> _configure; private readonly IDbConnectionFactory _dbConnectionFactory; private readonly IEntityIdGenerator _entityIdGenerator; - private readonly Assembly[] _assemblies; + private readonly Type[] _aggregateRootTypes; + private readonly Type[] _entityTypes; + private readonly Dictionary _aggregateMappingTypes; - public EfCorePersistenceModule(IDbConnectionFactory dbConnectionFactory, IEntityIdGenerator entityIdGenerator, - ILoggerFactory loggerFactory, Action, IDbConnection> configure, params Assembly[] assemblies) + public EfCorePersistenceModule( + IDbConnectionFactory dbConnectionFactory, + IEntityIdGenerator entityIdGenerator, + ILoggerFactory loggerFactory, + Action, IDbConnection> configure, + params Assembly[] assemblies) { _dbConnectionFactory = dbConnectionFactory; _entityIdGenerator = entityIdGenerator; _loggerFactory = loggerFactory; _configure = configure; - _assemblies = assemblies; - } - public virtual Func DbConnectionFactory => _ => _dbConnectionFactory.Create(); - public virtual Func OperationFactory => _ => new Operation(); - public virtual Func> CorrelationHolderFactory => _ => new CurrentCorrelationHolder(); - public virtual Func> IdentityHolderFactory => _ => new CurrentIdentityHolder(); - public virtual Func> TenantIdHolderFactory => _ => new CurrentTenantIdHolder(); - public virtual Func DomainEventAggregatorFactory => compositionRoot => new DomainEventAggregator(compositionRoot); + _aggregateRootTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(AggregateRoot).IsAssignableFrom(t))) + .ToArray(); + + _entityTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(Entity).IsAssignableFrom(t))) + .ToArray(); + + _aggregateMappingTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(IAggregateMapping).IsAssignableFrom(t))) + .ToDictionary( + t => t + .GetInterfaces() + .Single(i => i.GenericTypeArguments.Length == 1 + && typeof(AggregateRoot).IsAssignableFrom(i.GenericTypeArguments[0])) + .GenericTypeArguments[0], + t => t); + } - public abstract void Register(ICompositionRoot compositionRoot); public void Register(ICompositionRoot compositionRoot) { - // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly - compositionRoot.InfrastructureModule.RegisterScoped(() => _dbConnectionFactory.Create()); - // singleton id generator - compositionRoot.InfrastructureModule.RegisterInstance(_entityIdGenerator); + compositionRoot.Register( + new ServiceDescriptor( + typeof(IEntityIdGenerator), + _entityIdGenerator)); + + // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly + compositionRoot.Register( + new ServiceDescriptor( + typeof(IDbConnection), + sp => _dbConnectionFactory.Create(), + ServiceLifetime.Scoped)); // EF core requires us to flush frequently, because of a missing identity map - compositionRoot.InfrastructureModule.RegisterScoped(); + compositionRoot.Register( + new ServiceDescriptor( + typeof(ICanFlush), + typeof(EfFlush), + ServiceLifetime.Scoped)); - // EF Repositories - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IRepository<>), typeof(EfRepository<>)); + // DbContext is injected into repositories (not TDbContext!) + compositionRoot.Register( + new ServiceDescriptor( + typeof(DbContext), + typeof(TDbContext), + ServiceLifetime.Scoped)); - // IQueryable is supported, but should be use with caution, since it bypasses authorization - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IQueryable<>), typeof(EntityQueryable<>)); + // TDbContext ctor requires DbContextOptions, which is configured to use a container managed db connection + compositionRoot.Register( + new ServiceDescriptor(typeof(DbContextOptions), + sp => + { + var dbContextOptionsBuilder = new DbContextOptionsBuilder(); + var dbConnection = sp.GetRequiredService(); + _configure.Invoke(dbContextOptionsBuilder, dbConnection); - // DbContext is injected into repositories - compositionRoot.InfrastructureModule.RegisterScoped(() => CreateDbContextOptions(compositionRoot.InstanceProvider.GetInstance())); - compositionRoot.InfrastructureModule.RegisterScoped(); + return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; + }, ServiceLifetime.Scoped)); - // wrapping the operation: connection.open - transaction.begin - operation - (flush) - transaction.commit - connection.close - compositionRoot.InfrastructureModule.RegisterDecorator(); - compositionRoot.InfrastructureModule.RegisterDecorator(); - compositionRoot.InfrastructureModule.RegisterDecorator(); - - // ensure everything dirty is flushed to the db before handling domain events - compositionRoot.InfrastructureModule.RegisterDecorator(); + // loop through aggregate root types to... + foreach (var aggregateRootType in _aggregateRootTypes) + { + // ... register the Entity Framework implementation of IRepository + var genericRepositoryInterface = typeof(IRepository<>).MakeGenericType(aggregateRootType); + var genericRepositoryImplementation = typeof(EfRepository<>).MakeGenericType(aggregateRootType); + compositionRoot.Register( + new ServiceDescriptor( + genericRepositoryInterface, + genericRepositoryImplementation, + ServiceLifetime.Scoped)); - compositionRoot.InfrastructureModule.RegisterScoped(typeof(IAggregateMapping<>), _assemblies); - } + // ... register the aggregate mapping definition (singleton) + var genericAggregateMappingInterface = typeof(IAggregateMapping<>).MakeGenericType(aggregateRootType); + var aggregateMappingImplementation = _aggregateMappingTypes[aggregateRootType]; + compositionRoot.Register( + new ServiceDescriptor( + genericAggregateMappingInterface, + aggregateMappingImplementation, + ServiceLifetime.Singleton)); + } - protected virtual DbContextOptions CreateDbContextOptions(IDbConnection connection) - { - var dbContextOptionsBuilder = new DbContextOptionsBuilder(); - _configure.Invoke(dbContextOptionsBuilder, connection); - return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; + // loop through entity types ... + foreach (var entityType in _entityTypes) + { + // to register the Entity Framework implementation of IQueryable + compositionRoot.Register(new ServiceDescriptor( + typeof(IQueryable<>).MakeGenericType(entityType), + typeof(EntityQueryable<>).MakeGenericType(entityType), + ServiceLifetime.Scoped)); + } + + // wrapping the operation: + // invoke -> connection.open -> transaction.begin ---+ + // | + // v + // operation + // | + // v + // flush + // | + // end invoke <- connection.close <- transaction.commit <-+ + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IOperation), + typeof(FlushOperationDecorator), + ServiceLifetime.Scoped)); + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IOperation), + typeof(DbContextTransactionOperationDecorator), + ServiceLifetime.Scoped)); + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IOperation), + typeof(DbConnectionOperationDecorator), + ServiceLifetime.Scoped)); + + // // ensure everything dirty is flushed to the db before handling domain events + // compositionRoot.Register( + // new ServiceDescriptor( + // typeof(IDomainEventAggregator), + // typeof(FlushDomainEventAggregatorDecorator), + // ServiceLifetime.Scoped)); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/IDbConnectionFactory.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/IDbConnectionFactory.cs index df953115..3b451e94 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/IDbConnectionFactory.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/IDbConnectionFactory.cs @@ -1,6 +1,6 @@ using System.Data; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCore6Persistence.Bootstrapping { public interface IDbConnectionFactory { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs index 35668e25..44fdb0d0 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public static class DbContextExtensions { @@ -20,7 +20,6 @@ public static void DisableChangeTracking(this DbContext dbContext) { Logger.LogDebug("Disabling change tracking on {DbContextTypeName} instance", dbContext.GetType().Name); dbContext.ChangeTracker.AutoDetectChangesEnabled = false; - dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } public static void RegisterRowVersionProperty(this ModelBuilder modelBuilder) diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs index c9b25368..0e5a9c8d 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs @@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public class EfFlush : ICanFlush { @@ -28,8 +28,7 @@ public class EfFlush : ICanFlush public EfFlush(DbContext dbContext, ICurrentTHolder identityHolder, IClock clock) { DbContext = dbContext; - Logger.LogInformation("Disabling auto detect changes on this DbContext. Changes will be detected explicitly when flushing"); - DbContext.ChangeTracker.AutoDetectChangesEnabled = false; + DbContext.DisableChangeTracking(); IdentityHolder = identityHolder; Clock = clock; } diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs index 03258217..774b9899 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs @@ -17,7 +17,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public class EfRepository : Repository, IAsyncRepository where TAggregateRoot : AggregateRoot { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs index 48d4a959..c5d60b9b 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs @@ -7,7 +7,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public class EntityQueryable : IQueryable where TEntity : Entity { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs index 76dd9fd5..905220fe 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs @@ -4,7 +4,7 @@ using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public interface IAggregateMapping { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs index 078d172d..1642e25b 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs @@ -1,21 +1,23 @@ using System; using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence.Mssql +namespace Backend.Fx.EfCore6Persistence.Mssql { public abstract class MsSqlSequence : ISequence { private static readonly ILogger Logger = Log.Create(); private readonly IDbConnectionFactory _dbConnectionFactory; + private readonly int _startWith; - protected MsSqlSequence(IDbConnectionFactory dbConnectionFactory) + protected MsSqlSequence(IDbConnectionFactory dbConnectionFactory, int startWith = 1) { _dbConnectionFactory = dbConnectionFactory; + _startWith = startWith; } protected abstract string SequenceName { get; } @@ -43,7 +45,7 @@ public void EnsureSequence() Logger.LogInformation("Sequence {SchemaName}.{SequenceName} does not exist yet and will be created now", SchemaName, SequenceName); using (IDbCommand cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"CREATE SEQUENCE [{SchemaName}].[{SequenceName}] START WITH 1 INCREMENT BY {Increment}"; + cmd.CommandText = $"CREATE SEQUENCE [{SchemaName}].[{SequenceName}] START WITH {_startWith} INCREMENT BY {Increment}"; cmd.ExecuteNonQuery(); Logger.LogInformation("Sequence {SchemaName}.{SequenceName} created", SchemaName, SequenceName); } diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs index 7aa7e248..30ed350c 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs @@ -1,21 +1,23 @@ using System; using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence.Oracle +namespace Backend.Fx.EfCore6Persistence.Oracle { public abstract class OracleSequence : ISequence { private static readonly ILogger Logger = Log.Create(); private readonly IDbConnectionFactory _dbConnectionFactory; + private readonly int _startWith; - protected OracleSequence(IDbConnectionFactory dbConnectionFactory) + protected OracleSequence(IDbConnectionFactory dbConnectionFactory, int startWith = 1) { _dbConnectionFactory = dbConnectionFactory; + _startWith = startWith; } protected abstract string SequenceName { get; } @@ -56,7 +58,7 @@ public void EnsureSequence() SequenceName); using (IDbCommand cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"CREATE SEQUENCE {SchemaPrefix}{SequenceName} START WITH 1 INCREMENT BY {Increment}"; + cmd.CommandText = $"CREATE SEQUENCE {SchemaPrefix}{SequenceName} START WITH {_startWith} INCREMENT BY {Increment}"; cmd.ExecuteNonQuery(); Logger.LogInformation("Oracle sequence {SchemaPrefix}.{SequenceName} created", SchemaPrefix, SequenceName); } diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs index 2bf4efec..880177df 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs @@ -4,7 +4,7 @@ using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore6Persistence { public class PlainAggregateMapping : AggregateMapping where TAggregateRoot : AggregateRoot diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs index 83e54535..cda6b248 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs @@ -1,21 +1,23 @@ using System; using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence.Postgres +namespace Backend.Fx.EfCore6Persistence.Postgres { public abstract class PostgresSequence : ISequence { private static readonly ILogger Logger = Log.Create(); private readonly IDbConnectionFactory _dbConnectionFactory; + private readonly int _startWith; - protected PostgresSequence(IDbConnectionFactory dbConnectionFactory) + protected PostgresSequence(IDbConnectionFactory dbConnectionFactory, int startWith = 1) { _dbConnectionFactory = dbConnectionFactory; + _startWith = startWith; } protected abstract string SequenceName { get; } @@ -47,7 +49,7 @@ public void EnsureSequence() SequenceName); using (IDbCommand cmd = dbConnection.CreateCommand()) { - cmd.CommandText = $"CREATE SEQUENCE {SchemaName}.{SequenceName} START WITH 1 INCREMENT BY {Increment}"; + cmd.CommandText = $"CREATE SEQUENCE {SchemaName}.{SequenceName} START WITH {_startWith} INCREMENT BY {Increment}"; cmd.ExecuteNonQuery(); Logger.LogInformation("Sequence {SchemaName}.{SequenceName} created", SchemaName, SequenceName); } diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs index 36e79f49..593f6b0e 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs @@ -6,7 +6,6 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +13,7 @@ namespace Backend.Fx.EfCorePersistence.Bootstrapping { - public abstract class EfCorePersistenceModule : IModule + public class EfCorePersistenceModule : IModule where TDbContext : DbContext { private readonly ILoggerFactory _loggerFactory; @@ -25,7 +24,7 @@ public abstract class EfCorePersistenceModule : IModule private readonly Type[] _entityTypes; private readonly Dictionary _aggregateMappingTypes; - protected EfCorePersistenceModule( + public EfCorePersistenceModule( IDbConnectionFactory dbConnectionFactory, IEntityIdGenerator entityIdGenerator, ILoggerFactory loggerFactory, @@ -56,63 +55,120 @@ protected EfCorePersistenceModule( .GetExportedTypes() .Where(t => !t.IsAbstract && t.IsClass) .Where(t => typeof(IAggregateMapping).IsAssignableFrom(t))) - .ToDictionary(t => t.GenericTypeArguments[0], t => t); + .ToDictionary( + t => t + .GetInterfaces() + .Single(i => i.GenericTypeArguments.Length == 1 + && typeof(AggregateRoot).IsAssignableFrom(i.GenericTypeArguments[0])) + .GenericTypeArguments[0], + t => t); } public void Register(ICompositionRoot compositionRoot) { // singleton id generator - RegisterServiceDescriptor(new ServiceDescriptor(typeof(IEntityIdGenerator), _entityIdGenerator)); - + compositionRoot.Register( + new ServiceDescriptor( + typeof(IEntityIdGenerator), + _entityIdGenerator)); + // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly - RegisterServiceDescriptor(new ServiceDescriptor(typeof(IDbConnection), sp => _dbConnectionFactory.Create(), ServiceLifetime.Scoped)); + compositionRoot.Register( + new ServiceDescriptor( + typeof(IDbConnection), + sp => _dbConnectionFactory.Create(), + ServiceLifetime.Scoped)); // EF core requires us to flush frequently, because of a missing identity map - RegisterServiceDescriptor(new ServiceDescriptor(typeof(ICanFlush), typeof(EfFlush))); + compositionRoot.Register( + new ServiceDescriptor( + typeof(ICanFlush), + typeof(EfFlush), + ServiceLifetime.Scoped)); // DbContext is injected into repositories (not TDbContext!) - RegisterServiceDescriptor(new ServiceDescriptor(typeof(DbContext), typeof(TDbContext))); + compositionRoot.Register( + new ServiceDescriptor( + typeof(DbContext), + typeof(TDbContext), + ServiceLifetime.Scoped)); - // TDbContext ctor requires DbContextOptions - RegisterServiceDescriptor(new ServiceDescriptor(typeof(DbContextOptions), sp => - { - IDbConnection connection = sp.GetRequiredService(); - var dbContextOptionsBuilder = new DbContextOptionsBuilder(); - _configure.Invoke(dbContextOptionsBuilder, connection); - return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; - }, ServiceLifetime.Scoped)); + // TDbContext ctor requires DbContextOptions, which is configured to use a container managed db connection + compositionRoot.Register( + new ServiceDescriptor(typeof(DbContextOptions), + sp => + { + var dbContextOptionsBuilder = new DbContextOptionsBuilder(); + var dbConnection = sp.GetRequiredService(); + _configure.Invoke(dbContextOptionsBuilder, dbConnection); + + return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; + }, ServiceLifetime.Scoped)); + // loop through aggregate root types to... foreach (var aggregateRootType in _aggregateRootTypes) { - // EF Repository for this aggregate root - RegisterServiceDescriptor(new ServiceDescriptor( - typeof(IRepository<>).MakeGenericType(aggregateRootType), - typeof(EfRepository<>).MakeGenericType(aggregateRootType), - ServiceLifetime.Scoped)); + // ... register the Entity Framework implementation of IRepository + var genericRepositoryInterface = typeof(IRepository<>).MakeGenericType(aggregateRootType); + var genericRepositoryImplementation = typeof(EfRepository<>).MakeGenericType(aggregateRootType); + compositionRoot.Register( + new ServiceDescriptor( + genericRepositoryInterface, + genericRepositoryImplementation, + ServiceLifetime.Scoped)); - // aggregate mapping definition - RegisterServiceDescriptor(new ServiceDescriptor( - typeof(IAggregateMapping<>).MakeGenericType(aggregateRootType), - sp => sp.GetService(_aggregateMappingTypes[aggregateRootType]), - ServiceLifetime.Scoped)); + // ... register the aggregate mapping definition (singleton) + var genericAggregateMappingInterface = typeof(IAggregateMapping<>).MakeGenericType(aggregateRootType); + var aggregateMappingImplementation = _aggregateMappingTypes[aggregateRootType]; + compositionRoot.Register( + new ServiceDescriptor( + genericAggregateMappingInterface, + aggregateMappingImplementation, + ServiceLifetime.Singleton)); } + // loop through entity types ... foreach (var entityType in _entityTypes) { - // IQueryable is supported, but should be use with caution, since it bypasses authorization - RegisterServiceDescriptor(new ServiceDescriptor( + // to register the Entity Framework implementation of IQueryable + compositionRoot.Register(new ServiceDescriptor( typeof(IQueryable<>).MakeGenericType(entityType), typeof(EntityQueryable<>).MakeGenericType(entityType), ServiceLifetime.Scoped)); } - // wrapping the operation: connection.open - transaction.begin - operation - (flush) - transaction.commit - connection.close - compositionRoot.InfrastructureModule.RegisterDecorator(); - compositionRoot.InfrastructureModule.RegisterDecorator(); - compositionRoot.InfrastructureModule.RegisterDecorator(); + // wrapping the operation: + // invoke -> connection.open -> transaction.begin ---+ + // | + // v + // operation + // | + // v + // flush + // | + // end invoke <- connection.close <- transaction.commit <-+ + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IOperation), + typeof(FlushOperationDecorator), + ServiceLifetime.Scoped)); + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IOperation), + typeof(DbContextTransactionOperationDecorator), + ServiceLifetime.Scoped)); + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IOperation), + typeof(DbConnectionOperationDecorator), + ServiceLifetime.Scoped)); - // ensure everything dirty is flushed to the db before handling domain events - compositionRoot.InfrastructureModule.RegisterDecorator(); + // // ensure everything dirty is flushed to the db before handling domain events + // compositionRoot.Register( + // new ServiceDescriptor( + // typeof(IDomainEventAggregator), + // typeof(FlushDomainEventAggregatorDecorator), + // ServiceLifetime.Scoped)); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs index 35668e25..065d0ad3 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs @@ -20,7 +20,6 @@ public static void DisableChangeTracking(this DbContext dbContext) { Logger.LogDebug("Disabling change tracking on {DbContextTypeName} instance", dbContext.GetType().Name); dbContext.ChangeTracker.AutoDetectChangesEnabled = false; - dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } public static void RegisterRowVersionProperty(this ModelBuilder modelBuilder) diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs index c9b25368..eb3f828d 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs @@ -28,8 +28,7 @@ public class EfFlush : ICanFlush public EfFlush(DbContext dbContext, ICurrentTHolder identityHolder, IClock clock) { DbContext = dbContext; - Logger.LogInformation("Disabling auto detect changes on this DbContext. Changes will be detected explicitly when flushing"); - DbContext.ChangeTracker.AutoDetectChangesEnabled = false; + DbContext.DisableChangeTracking(); IdentityHolder = identityHolder; Clock = clock; } diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj index 32788c68..ea478ef4 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/Backend.Fx.MicrosoftDependencyInjection.csproj @@ -10,6 +10,7 @@ + diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs index 863399a2..a8b24ac9 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -1,39 +1,72 @@ using System; using System.Collections.Generic; +using System.Linq; +using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.MicrosoftDependencyInjection { public class MicrosoftCompositionRoot : CompositionRoot { + private static readonly ILogger Logger = Log.Create(); private readonly IServiceCollection _serviceCollection = new ServiceCollection(); private readonly Lazy _serviceProvider; - + public MicrosoftCompositionRoot() { - _serviceProvider = new Lazy(() => _serviceCollection.BuildServiceProvider()); + _serviceProvider = new Lazy(() => + { + Logger.LogInformation("Building Microsoft ServiceProvider"); + return _serviceCollection.BuildServiceProvider(); + }); } public override IServiceProvider ServiceProvider => _serviceProvider.Value; - + public override void Verify() - { } + { + // ensure creation of lazy service provider + var unused = _serviceProvider.Value; + } - public override void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) + public override void Register(ServiceDescriptor serviceDescriptor) { if (_serviceProvider.IsValueCreated) { throw new InvalidOperationException("Service provider has been built and cannot be changed any more."); } - _serviceCollection.Add(serviceDescriptor); + + var existingRegistration = _serviceCollection + .SingleOrDefault(sd => sd.ServiceType == serviceDescriptor.ServiceType); + + if (existingRegistration == null) + { + serviceDescriptor.LogDetails(Logger, "Adding"); + _serviceCollection.Add(serviceDescriptor); + } + else + { + serviceDescriptor.LogDetails(Logger, "Replacing"); + _serviceCollection.Replace(serviceDescriptor); + } } - public override void RegisterServiceDescriptors(IEnumerable serviceDescriptors) + public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) + { + serviceDescriptor.LogDetails(Logger, "Adding decorator"); + _serviceCollection.Decorate(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType); + } + + public override void RegisterCollection(IEnumerable serviceDescriptors) { foreach (var serviceDescriptor in serviceDescriptors) { - RegisterServiceDescriptor(serviceDescriptor); + serviceDescriptor.LogDetails(Logger, "Adding"); + _serviceCollection.Add(serviceDescriptor); } } @@ -43,6 +76,7 @@ public override IServiceScope BeginScope() } protected override void Dispose(bool disposing) - { } + { + } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs index 35b9f609..f72e71bd 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using SimpleInjector; -namespace Backend.Fx.SimpleInjectorDependencyInjection.Modules +namespace Backend.Fx.SimpleInjectorDependencyInjection { public static class MicrosoftDependencyInjectionExtensions { diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index 6317781d..80c8257b 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -3,7 +3,6 @@ using System.Linq; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SimpleInjector; @@ -13,76 +12,147 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection { + /// /// Provides a reusable composition root assuming Simple Injector as container /// public class SimpleInjectorCompositionRoot : CompositionRoot { private static readonly ILogger Logger = Log.Create(); - private readonly Container _container = new Container(); + private readonly Lazy _container; private readonly ScopedLifestyle _scopedLifestyle; - + private readonly IList _services = new List(); + private readonly IList _decorators = new List(); + private readonly IList _serviceCollections = new List(); + /// /// This constructor creates a composition root that prefers scoped lifestyle /// - public SimpleInjectorCompositionRoot() + public SimpleInjectorCompositionRoot() : this(new ScopedLifestyleBehavior(), new AsyncScopedLifestyle()) - {} + { + } - public SimpleInjectorCompositionRoot(ILifestyleSelectionBehavior lifestyleBehavior, ScopedLifestyle scopedLifestyle) + public SimpleInjectorCompositionRoot( + ILifestyleSelectionBehavior lifestyleBehavior, + ScopedLifestyle scopedLifestyle) { Logger.LogInformation("Initializing SimpleInjector"); _scopedLifestyle = scopedLifestyle; - _container.Options.LifestyleSelectionBehavior = lifestyleBehavior; - _container.Options.DefaultScopedLifestyle = _scopedLifestyle; + _container = new Lazy(() => + { + Logger.LogInformation("Building SimpleInjector Container"); + var container = new Container(); + container.Options.LifestyleSelectionBehavior = lifestyleBehavior; + container.Options.DefaultScopedLifestyle = _scopedLifestyle; + + foreach (var serviceDescriptor in _services) + { + serviceDescriptor.LogDetails(Logger, "Adding"); + + if (serviceDescriptor.ImplementationType != null) + { + container.Register( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + else if (serviceDescriptor.ImplementationFactory != null) + { + container.Register( + serviceDescriptor.ServiceType, + () => serviceDescriptor.ImplementationFactory(container), + serviceDescriptor.Lifetime.Translate()); + } + else if (serviceDescriptor.ImplementationInstance != null && + serviceDescriptor.Lifetime == ServiceLifetime.Singleton) + { + container.RegisterInstance(serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationInstance); + } + else + { + throw new InvalidOperationException("Bad service descriptor"); + } + } + + foreach (var serviceDescriptor in _decorators) + { + serviceDescriptor.LogDetails(Logger, "Adding decorator"); + + container.RegisterDecorator( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + + foreach (var serviceDescriptors in _serviceCollections) + { + Logger.Debug("Adding {Lifetime} collection registration: {ServiceType}: {ImplementationType}", + serviceDescriptors[0].Lifetime.ToString(), + serviceDescriptors[0].ServiceType.Name, + $"[{string.Join(",", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))}]"); + + foreach (var serviceDescriptor in serviceDescriptors) + { + container.Collection.Append( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + } + + // needed to support extension method IServiceProvider.CreateScope() + container.RegisterInstance(new SimpleInjectorServiceScopeFactory(container)); + + return container; + }); } - + #region ICompositionRoot implementation - public override void RegisterServiceDescriptor(ServiceDescriptor serviceDescriptor) + public override void Register(ServiceDescriptor serviceDescriptor) { - if (serviceDescriptor.ImplementationType != null) - { - _container.Register( - serviceDescriptor.ServiceType, - serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); - } - else if (serviceDescriptor.ImplementationFactory != null) + if (_container.IsValueCreated) { - _container.Register( - serviceDescriptor.ServiceType, - () => serviceDescriptor.ImplementationFactory(_container), - serviceDescriptor.Lifetime.Translate()); + throw new InvalidOperationException("Container has been built and cannot be changed any more."); } - else if (serviceDescriptor.ImplementationInstance != null && - serviceDescriptor.Lifetime == ServiceLifetime.Singleton) + + foreach (var descriptor in _services.Where(sd => sd.ServiceType == serviceDescriptor.ServiceType).ToArray()) { - _container.RegisterInstance(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationInstance); + _services.Remove(descriptor); } - else + + _services.Add(serviceDescriptor); + } + + public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) + { + if (_container.IsValueCreated) { - throw new InvalidOperationException("Bad service descriptor"); + throw new InvalidOperationException("Container has been built and cannot be changed any more."); } + + _decorators.Add(serviceDescriptor); } - public override void RegisterServiceDescriptors(IEnumerable serviceDescriptors) + public override void RegisterCollection(IEnumerable serviceDescriptors) { - var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); - - if (serviceDescriptorArray.Select(sd => sd.ServiceType).Distinct().Count() > 1) + if (_container.IsValueCreated) { - throw new InvalidOperationException("To register a collection of services they must implement the same service type"); + throw new InvalidOperationException("Container has been built and cannot be changed any more."); } + + var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); - foreach (var serviceDescriptor in serviceDescriptorArray) + if (serviceDescriptorArray.Select(sd => sd.ServiceType).Distinct().Count() > 1) { - _container.Collection.Append( - serviceDescriptor.ServiceType, - serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); + throw new InvalidOperationException( + "To register a collection of services they must implement the same service type"); } + + _serviceCollections.Add(serviceDescriptorArray); } public override void Verify() @@ -90,7 +160,7 @@ public override void Verify() Logger.LogInformation("container is being verified"); try { - _container.Verify(VerificationOption.VerifyAndDiagnose); + _container.Value.Verify(VerificationOption.VerifyAndDiagnose); } catch (Exception ex) { @@ -102,16 +172,16 @@ public override void Verify() /// public override IServiceScope BeginScope() { - return new SimpleInjectorServiceScope(AsyncScopedLifestyle.BeginScope(_container)); + return _container.Value.CreateScope(); } - public override IServiceProvider ServiceProvider => _container; + public override IServiceProvider ServiceProvider => _container.Value; public Scope GetCurrentScope() { - return _scopedLifestyle.GetCurrentScope(_container); + return _scopedLifestyle.GetCurrentScope(_container.Value); } - + /// /// A behavior that defaults to scoped life style for injected instances /// @@ -122,16 +192,19 @@ public Lifestyle SelectLifestyle(Type implementationType) return Lifestyle.Scoped; } } + #endregion #region IDisposable implementation + protected override void Dispose(bool disposing) { - if (disposing) + if (disposing && _container.IsValueCreated) { - _container?.Dispose(); + _container.Value.Dispose(); } } + #endregion } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScopeFactory.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScopeFactory.cs new file mode 100644 index 00000000..8bfa8f86 --- /dev/null +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorServiceScopeFactory.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using SimpleInjector; +using SimpleInjector.Lifestyles; + +namespace Backend.Fx.SimpleInjectorDependencyInjection +{ + public class SimpleInjectorServiceScopeFactory : IServiceScopeFactory + { + private readonly Container _container; + + public SimpleInjectorServiceScopeFactory(Container container) + { + _container = container; + } + public IServiceScope CreateScope() + { + return new SimpleInjectorServiceScope(AsyncScopedLifestyle.BeginScope(_container)); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj b/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj index 3068b0ae..088a30d3 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj +++ b/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj @@ -35,4 +35,8 @@ + + + + diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs deleted file mode 100644 index 048badd5..00000000 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Runtime/SampleApplication.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DataGeneration; -using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.SimpleInjectorDependencyInjection; - -namespace Backend.Fx.AspNetCore.Tests.SampleApp.Runtime -{ - public class SampleApplication : IBackendFxApplication - { - private readonly IBackendFxApplication _application; - - public SampleApplication(ITenantIdProvider tenantIdProvider, IExceptionLogger exceptionLogger) - { - ITenantWideMutexManager tenantWideMutexManager = new InMemoryTenantWideMutexManager(); - Assembly domainAssembly = GetType().Assembly; - - _application = new BackendFxApplication( - new SimpleInjectorCompositionRoot(), - exceptionLogger, - new SimpleInjectorInfrastructureModule()); - - _application = new DataGeneratingApplication( - tenantIdProvider, - new SimpleInjectorDataGenerationModule(domainAssembly), - tenantWideMutexManager, _application); - - _application.CompositionRoot.RegisterModules( - new DomainModule(domainAssembly)); - } - - public void Dispose() - { - _application.Dispose(); - } - - public IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; - - public ICompositionRoot CompositionRoot => _application.CompositionRoot; - - public IBackendFxApplicationInvoker Invoker => _application.Invoker; - - public bool WaitForBoot(int timeoutMilliSeconds = 2147483647, CancellationToken cancellationToken = default) - { - return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - - public Task Boot(CancellationToken cancellationToken = default) => BootAsync(cancellationToken); - public Task BootAsync(CancellationToken cancellationToken = default) - { - return _application.BootAsync(cancellationToken); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs index d69ac8a3..227492f7 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs @@ -1,22 +1,28 @@ -using Backend.Fx.AspNetCore.Tests.SampleApp.Runtime; +using Backend.Fx.AspNetCore.Bootstrapping; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.SimpleInjectorDependencyInjection; namespace Backend.Fx.AspNetCore.Tests.SampleApp { public class SampleApplicationHostedService : BackendFxApplicationHostedService { - public ITenantService TenantService { get; } - public override IBackendFxApplication Application { get; } - public SampleApplicationHostedService(IExceptionLogger exceptionLogger) { - IMessageBus messageBus = new InMemoryMessageBus(); - TenantService = new TenantService(new InMemoryTenantRepository()); - Application = new SampleApplication(TenantService.TenantIdProvider, exceptionLogger); + IBackendFxApplication application = new BackendFxApplication( + new SimpleInjectorCompositionRoot(), + exceptionLogger, + typeof(SampleApplicationHostedService).Assembly); + + application = new AspNetCoreApplication(application); + + Application = application; } + + public ITenantService TenantService { get; } = new TenantService(new InMemoryTenantRepository()); + + public override IBackendFxApplication Application { get; } } } \ No newline at end of file diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs index 5107f7d5..711dbd90 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs @@ -16,7 +16,7 @@ public class TheBackendFxMvcApplication : TestWithLogging public TheBackendFxMvcApplication(ITestOutputHelper output) : base(output) { - _factory = new SampleAppWebApplicationFactory(base.Logger); + _factory = new SampleAppWebApplicationFactory(Logger); } [Fact] diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj index 0187d99b..edd2e02f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj @@ -5,33 +5,34 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - + + + + - + diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs index 499d8a51..7933cb5b 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs @@ -3,7 +3,7 @@ using System.Data; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore6Persistence.Tests { public static class DbConnectionEx { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs index 27c8be41..9e923b5c 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs @@ -3,7 +3,7 @@ using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain { public class Blog : AggregateRoot { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs index 958e27a8..b71d0a05 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs @@ -1,7 +1,7 @@ using Backend.Fx.Patterns.Authorization; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain { [UsedImplicitly] public class BlogAuthorization : AllowAll diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs index a21db5ab..39db43c6 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs @@ -1,7 +1,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain { public class Blogger : AggregateRoot { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs new file mode 100644 index 00000000..8e7d9b78 --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs @@ -0,0 +1,10 @@ +using Backend.Fx.Patterns.Authorization; +using JetBrains.Annotations; + +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain +{ + [UsedImplicitly] + public class BloggerAuthorization : AllowAll + { + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs index e71875c0..7f609269 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs @@ -2,7 +2,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain { public class Post : Entity { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs index 4f21dddb..9d2265c7 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence { public class BlogMapping : AggregateMapping { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs index 2659bcd9..2c26a7e7 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs @@ -1,6 +1,6 @@ -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence { public class BloggerMapping : PlainAggregateMapping { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs index bd40f2f5..aa223153 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs @@ -1,9 +1,9 @@ -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; using Backend.Fx.Environment.MultiTenancy; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence { public sealed class TestDbContext : DbContext { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs index cefd3713..5d0bfe26 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence { [UsedImplicitly] [Obsolete("Only for migration support at design time")] diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs index 59578f8c..87192608 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -1,12 +1,12 @@ using System.Data; using System.Security.Principal; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures { public abstract class DatabaseFixture { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 5f57d5b2..f48d2d8e 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -1,12 +1,12 @@ using System; using System.Data; using System.Data.SqlClient; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures { [Obsolete("Not supported on build agents")] public class SqlServerDatabaseFixture : DatabaseFixture diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 18440c2c..9912f84f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,12 +1,12 @@ using System.Data; using System.IO; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures { public class SqliteDatabaseFixture : DatabaseFixture { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs index 8fe47f7f..c12d2cb8 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs @@ -1,12 +1,12 @@ using System; using System.Data; using System.Security.Principal; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures { public class TestDbSession : ICanFlush, IDisposable { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs index 1c1cdd8b..2f7f600e 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs index e76dd8b8..9e58463a 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs new file mode 100644 index 00000000..1e5754f6 --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -0,0 +1,319 @@ +using System; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Threading.Tasks; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.EfCore6Persistence.Bootstrapping; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Tests; +using Backend.Fx.Tests.Patterns.DependencyInjection; +using FakeItEasy; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; +using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; + +namespace Backend.Fx.EfCore6Persistence.Tests +{ + public class TheDbApplicationWithEfCore : TestWithLogging + { + public TheDbApplicationWithEfCore(ITestOutputHelper output) : base(output) + { + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveIdGenerator(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + var entityIdGenerator = sut.CompositionRoot.ServiceProvider.GetRequiredService(); + Assert.StrictEqual(sut.EntityIdGenerator, entityIdGenerator); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveDbConnection(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var dbConnection = sp.GetRequiredService(); + Assert.IsType(dbConnection); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveICanFlush(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var canFlush = sp.GetRequiredService(); + Assert.IsType(canFlush); + canFlush.Flush(); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveDbContext(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var dbContext = sp.GetRequiredService(); + Assert.IsType(dbContext); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveAggregateMapping(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var aggregateMapping = sp.GetRequiredService>(); + Assert.IsType(aggregateMapping); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveEfRepositoryForAggregateRoot(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + Assert.IsType>(repo); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + }, + new SystemIdentity(), + new TenantId(333)); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + Assert.Equal(4, repo.GetAll().Length); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task DoesNotCommitChangesOnException(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + Assert.Throws( + () => sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + throw new Exception("intentionally thrown for a test case"); + }, + new SystemIdentity(), + new TenantId(333))); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + Assert.Empty(repo.GetAll()); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task DoesFlushBeforeRaisingDomainEvents(CompositionRootType compositionRootType) + { + using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + var order = new Order(sut.EntityIdGenerator.NextId(), "domain event test"); + repo.Add(order); + sp.GetRequiredService().PublishDomainEvent(new OrderCreated(order.Id)); + OrderCreatedHandler.ExpectedInvocationCount++; + }, + new SystemIdentity(), + new TenantId(333)); + + Assert.Equal(OrderCreatedHandler.ExpectedInvocationCount, OrderCreatedHandler.InvocationCount); + } + + + private class EfCoreTestApplication : PersistentApplication + { + private int _nextId = 1; + private static readonly IDatabaseBootstrapper DbBootstrapper = A.Fake(); + public readonly IEntityIdGenerator EntityIdGenerator = A.Fake(); + + public EfCoreTestApplication(CompositionRootType compositionRootType, string connectionString) + : base( + DbBootstrapper, + A.Fake(), + new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + typeof(TheDbApplicationWithEfCore).Assembly)) + { + A.CallTo(() => DbBootstrapper.EnsureDatabaseExistence()).Invokes(() => + { + var dbContext = new EfCoreTestDbContext(new DbContextOptionsBuilder() + .UseSqlite(connectionString).Options); + dbContext.Database.EnsureCreated(); + }); + + var dbConnectionFactory = A.Fake(); + A.CallTo(() => dbConnectionFactory.Create()).Returns(new SqliteConnection(connectionString)); + + A.CallTo(() => EntityIdGenerator.NextId()) + .ReturnsLazily(() => _nextId++); + + var loggerFactory = A.Fake(); + + CompositionRoot.RegisterModules( + new EfCorePersistenceModule( + dbConnectionFactory, + EntityIdGenerator, + loggerFactory, + (builder, connection) => builder.UseSqlite((DbConnection)connection), + Assemblies + )); + } + } + } + + public class EfCoreTestDbContext : DbContext + { + public EfCoreTestDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Orders { get; set; } + } + + public class Order : AggregateRoot + { + public string Recipient { get; private set; } + + public Order(int id, string recipient) : base(id) + { + Recipient = recipient; + } + } + + public class OrderMapping : PlainAggregateMapping + { + } + + public class OrderAuthorization : AllowAll + { + } + + public class OrderCreated : IDomainEvent + { + public int OrderId { get; } + + public OrderCreated(int orderId) + { + OrderId = orderId; + } + } + + public class OrderCreatedHandler : IDomainEventHandler + { + private readonly DbContext _dbContext; + public static int InvocationCount = 0; + public static int ExpectedInvocationCount = 0; + + public OrderCreatedHandler(DbContext dbContext) + { + _dbContext = dbContext; + } + + public void Handle(OrderCreated domainEvent) + { + InvocationCount++; + Assert.NotNull(_dbContext.Set().Find(domainEvent.OrderId)); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs index 2374a566..00c7a615 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs @@ -1,13 +1,12 @@ using System.Linq; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.Tests; using Microsoft.EntityFrameworkCore; -using Serilog.Formatting.Display; using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore6Persistence.Tests { public class TheDbContext: TestWithLogging { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs index a8ebb6a0..8c3756be 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; @@ -15,7 +15,7 @@ using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore6Persistence.Tests { public class TheRepositoryOfComposedAggregate : TestWithLogging { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs index a77f4b57..69a092c3 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; @@ -11,7 +11,7 @@ using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore6Persistence.Tests { public class TheRepositoryOfPlainAggregate: TestWithLogging { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj b/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj index 2d7f4b3c..98d43617 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj +++ b/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj @@ -5,33 +5,34 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - + + + + - + diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs b/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs new file mode 100644 index 00000000..fe223d0c --- /dev/null +++ b/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs @@ -0,0 +1,10 @@ +using Backend.Fx.Patterns.Authorization; +using JetBrains.Annotations; + +namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +{ + [UsedImplicitly] + public class BloggerAuthorization : AllowAll + { + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs new file mode 100644 index 00000000..ee35c8bb --- /dev/null +++ b/tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs @@ -0,0 +1,314 @@ +using System; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Threading.Tasks; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Tests; +using Backend.Fx.Tests.Patterns.DependencyInjection; +using FakeItEasy; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; + +namespace Backend.Fx.EfCorePersistence.Tests +{ + public class TheDbApplicationWithEfCore + { + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveIdGenerator(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + var entityIdGenerator = sut.CompositionRoot.ServiceProvider.GetRequiredService(); + Assert.StrictEqual(sut.EntityIdGenerator, entityIdGenerator); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveDbConnection(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var dbConnection = sp.GetRequiredService(); + Assert.IsType(dbConnection); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveICanFlush(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var canFlush = sp.GetRequiredService(); + Assert.IsType(canFlush); + canFlush.Flush(); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveDbContext(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var dbContext = sp.GetRequiredService(); + Assert.IsType(dbContext); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveAggregateMapping(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var aggregateMapping = sp.GetRequiredService>(); + Assert.IsType(aggregateMapping); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveEfRepositoryForAggregateRoot(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + Assert.IsType>(repo); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + }, + new SystemIdentity(), + new TenantId(333)); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + Assert.Equal(4, repo.GetAll().Length); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task DoesNotCommitChangesOnException(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + Assert.Throws( + () => sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); + repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + throw new Exception("intentionally thrown for a test case"); + }, + new SystemIdentity(), + new TenantId(333))); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + Assert.Empty(repo.GetAll()); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task DoesFlushBeforeRaisingDomainEvents(CompositionRootType compositionRootType) + { + var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var repo = sp.GetRequiredService>(); + var order = new Order(sut.EntityIdGenerator.NextId(), "domain event test"); + repo.Add(order); + sp.GetRequiredService().PublishDomainEvent(new OrderCreated(order.Id)); + OrderCreatedHandler.ExpectedInvocationCount++; + }, + new SystemIdentity(), + new TenantId(333)); + + Assert.Equal(OrderCreatedHandler.ExpectedInvocationCount, OrderCreatedHandler.InvocationCount); + } + + + private class EfCoreTestApplication : PersistentApplication + { + private int _nextId = 1; + private static readonly IDatabaseBootstrapper DbBootstrapper = A.Fake(); + public readonly IEntityIdGenerator EntityIdGenerator = A.Fake(); + + public EfCoreTestApplication(CompositionRootType compositionRootType, string connectionString) + : base( + DbBootstrapper, + A.Fake(), + new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + typeof(TheDbApplicationWithEfCore).Assembly)) + { + A.CallTo(() => DbBootstrapper.EnsureDatabaseExistence()).Invokes(() => + { + var dbContext = new EfCoreTestDbContext(new DbContextOptionsBuilder() + .UseSqlite(connectionString).Options); + dbContext.Database.EnsureCreated(); + }); + + var dbConnectionFactory = A.Fake(); + A.CallTo(() => dbConnectionFactory.Create()).Returns(new SqliteConnection(connectionString)); + + A.CallTo(() => EntityIdGenerator.NextId()) + .ReturnsLazily(() => _nextId++); + + var loggerFactory = A.Fake(); + + CompositionRoot.RegisterModules( + new EfCorePersistenceModule( + dbConnectionFactory, + EntityIdGenerator, + loggerFactory, + (builder, connection) => builder.UseSqlite((DbConnection)connection), + Assemblies + )); + } + } + } + + public class EfCoreTestDbContext : DbContext + { + public EfCoreTestDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Orders { get; set; } + } + + public class Order : AggregateRoot + { + public string Recipient { get; private set; } + + public Order(int id, string recipient) : base(id) + { + Recipient = recipient; + } + } + + public class OrderMapping : PlainAggregateMapping + { + } + + public class OrderAuthorization : AllowAll + { + } + + public class OrderCreated : IDomainEvent + { + public int OrderId { get; } + + public OrderCreated(int orderId) + { + OrderId = orderId; + } + } + + public class OrderCreatedHandler : IDomainEventHandler + { + private readonly DbContext _dbContext; + public static int InvocationCount = 0; + public static int ExpectedInvocationCount = 0; + + public OrderCreatedHandler(DbContext dbContext) + { + _dbContext = dbContext; + } + + public void Handle(OrderCreated domainEvent) + { + InvocationCount++; + Assert.NotNull(_dbContext.Set().Find(domainEvent.OrderId)); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs index 2374a566..d348b045 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs @@ -3,7 +3,6 @@ using Backend.Fx.EfCorePersistence.Tests.Fixtures; using Backend.Fx.Tests; using Microsoft.EntityFrameworkCore; -using Serilog.Formatting.Display; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index 5b570fb5..05923443 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -5,6 +5,7 @@ using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.Tests; using FakeItEasy; +using Microsoft.Extensions.DependencyInjection; using RabbitMQ.Client; using Xunit; using Xunit.Abstractions; @@ -25,7 +26,7 @@ public TheRabbitMqMessageBus(ITestOutputHelper testOutputHelper) : base(testOutp var fakeReceiverApplication = A.Fake(); _receiverInvoker = new BackendFxApplicationInvoker(fakeReceiverApplication.CompositionRoot); - var fakeScope = A.Fake(); + var fakeScope = A.Fake(); var fakeServiceProvider = A.Fake(); A.CallTo(() => fakeReceiverApplication.CompositionRoot.BeginScope()).Returns(fakeScope); A.CallTo(() => fakeScope.ServiceProvider).Returns(fakeServiceProvider); diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj deleted file mode 100644 index d47530f5..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - netcoreapp3.1 - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs deleted file mode 100644 index a43d9900..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADemoAggregateGenerator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Patterns.DataGeneration; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADemoAggregateGenerator : DataGenerator, IDemoDataGenerator - { - private static int _id = 457567; - public static string Name = "Demo record"; - - private readonly IRepository _repository; - public override int Priority => 1; - - public ADemoAggregateGenerator(IRepository repository) - { - _repository = repository; - } - - protected override void GenerateCore() - { - _repository.Add(new AnAggregate(_id++, Name)); - } - - protected override void Initialize() - { } - - protected override bool ShouldRun() - { - return true; - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs deleted file mode 100644 index 80e47e33..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainEvent : IDomainEvent - { - } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs deleted file mode 100644 index 6f1f7214..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler1.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainEventHandler1 : IDomainEventHandler - { - public void Handle(ADomainEvent domainEvent) - { - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs deleted file mode 100644 index e52b0fe5..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler2.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainEventHandler2 : IDomainEventHandler - { - public void Handle(ADomainEvent domainEvent) - { - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs deleted file mode 100644 index 72bb7c2b..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainEventHandler3.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainEventHandler3 : IDomainEventHandler - { - public void Handle(ADomainEvent domainEvent) - { - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs deleted file mode 100644 index 6dbb4c02..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainModule.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; -using SimpleInjector; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class ADomainModule : SimpleInjectorDomainModule - { - public ADomainModule(params Assembly[] domainAssemblies) - : base(domainAssemblies) - { - } - - protected override void Register(Container container, ScopedLifestyle scopedLifestyle) - { - base.Register(container, scopedLifestyle); - container.RegisterSingleton(); - container.Register(scopedLifestyle); - } - } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs deleted file mode 100644 index d2feebf0..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ADomainService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Backend.Fx.BuildingBlocks; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public interface ITestDomainService : IDomainService - { } - - public interface IAnotherTestDomainService : IDomainService - { } - - public class ADomainService : ITestDomainService, IAnotherTestDomainService - { } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs deleted file mode 100644 index fa1ac6d0..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AJob.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Backend.Fx.Patterns.Jobs; -using JetBrains.Annotations; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - [UsedImplicitly] - public class AJob : IJob - { - public void Run() - {} - } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs deleted file mode 100644 index d8d1e2cd..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AProdAggregateGenerator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Patterns.DataGeneration; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class AProdAggregateGenerator : DataGenerator, IProductiveDataGenerator - { - private static int _id = 2341234; - public static string Name = "Productive record"; - - private readonly IRepository _repository; - public override int Priority => 1; - - public AProdAggregateGenerator(IRepository repository) - { - _repository = repository; - } - - protected override void GenerateCore() - { - _repository.Add(new AnAggregate(_id++, Name)); - } - - protected override void Initialize() - { } - - protected override bool ShouldRun() - { - return true; - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs deleted file mode 100644 index 2ca6e09d..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/ASingletonService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public interface ISingletonService {} - - public class ASingletonService : ISingletonService - { - - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs deleted file mode 100644 index 592f21ba..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregate.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Backend.Fx.BuildingBlocks; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class AnAggregate : AggregateRoot - { - public AnAggregate(int id, string name) : base(id) - { - Name = name; - } - - public string Name { get; set; } - } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs deleted file mode 100644 index 7b43b787..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnAggregateAuthorization.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Backend.Fx.Patterns.Authorization; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class AnAggregateAuthorization : AllowAll { } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs deleted file mode 100644 index ee4fa719..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnApplicationService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Backend.Fx.BuildingBlocks; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public interface ITestApplicationService : IApplicationService - { } - - public class AnApplicationService : ITestApplicationService - { } -} diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs deleted file mode 100644 index 04e76118..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/AnIntegrationEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Integration; -using JetBrains.Annotations; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - [UsedImplicitly] - public class AnIntegrationEvent : IntegrationEvent - { - public AnIntegrationEvent(int tenantId, int whatever) : base() - { - Whatever = whatever; - } - - public int Whatever { [UsedImplicitly] get; } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs deleted file mode 100644 index a76d2934..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/DummyImpl/ASimpleDomain/SomeState.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain -{ - public class SomeState - { - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs deleted file mode 100644 index 6e64b497..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TestConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -// using Backend.Fx.NLogLogging; -// using Backend.Fx.SimpleInjectorDependencyInjection.Tests; -// using MarcWittke.Xunit.AssemblyFixture; -// -// [assembly: Xunit.TestFramework("MarcWittke.Xunit.AssemblyFixture.XunitTestFrameworkWithAssemblyFixture", "MarcWittke.Xunit.AssemblyFixture")] -// [assembly: AssemblyFixture(typeof(TestLoggingFixture))] -// -// namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests -// { -// public class TestLoggingFixture : LoggingFixture -// { -// public TestLoggingFixture() : base("Backend.Fx") -// { } -// } -// } \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs deleted file mode 100644 index fd4f355a..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheMisconfiguredSimpleInjectorCompositionRoot.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.SimpleInjectorDependencyInjection.Modules; -using Backend.Fx.Tests; -using SimpleInjector; -using Xunit; -using Xunit.Abstractions; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests -{ - public class TheMisconfiguredSimpleInjectorCompositionRoot : TestWithLogging - { - [Fact] - public void ThrowsOnValidation() - { - var sut = new SimpleInjectorCompositionRoot(); - sut.RegisterModules(new BadModule()); - Assert.Throws(() => sut.Verify()); - } - - public class UnresolvableService - { - public UnresolvableService(Entity e) - { - throw new Exception( - $"This constructor should never be called, since the Entity {e?.GetType().Name} cannot be resolved by the container"); - } - } - - public class BadModule : SimpleInjectorModule - { - protected override void Register(Container container, ScopedLifestyle scopedLifestyle) - { - // this registration should be recognized as unresolvable during validation - container.Register(); - } - } - - public TheMisconfiguredSimpleInjectorCompositionRoot(ITestOutputHelper output) : base(output) - { - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs b/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs deleted file mode 100644 index 302c246a..00000000 --- a/tests/Backend.Fx.SimpleInjectorDependencyInjection.Tests/TheSimpleInjectorCompositionRoot.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.SimpleInjectorDependencyInjection.Tests.DummyImpl.ASimpleDomain; -using Backend.Fx.Tests; -using SimpleInjector; -using Xunit; -using Xunit.Abstractions; - -namespace Backend.Fx.SimpleInjectorDependencyInjection.Tests -{ - public class TheSimpleInjectorCompositionRoot : TestWithLogging - { - private readonly SimpleInjectorCompositionRoot _sut; - - public TheSimpleInjectorCompositionRoot(ITestOutputHelper output) : base(output) - { - _sut = new SimpleInjectorCompositionRoot(); - Assembly domainAssembly = typeof(AnAggregate).GetTypeInfo().Assembly; - _sut.RegisterModules(new ADomainModule(domainAssembly)); - _sut.Verify(); - } - - [Fact] - public void ProvidesAutoRegisteredDomainServices() - { - using (_sut.BeginScope()) - { - var testDomainService = _sut.GetInstance(); - Assert.IsType(testDomainService); - } - } - - [Fact] - public void ProvidesAutoRegisteredDomainServicesThatImplementTwoInterfaces() - { - using (_sut.BeginScope()) - { - var testDomainService = _sut.GetInstance(); - Assert.IsType(testDomainService); - - var anotherTestDomainService = _sut.GetInstance(); - Assert.IsType(anotherTestDomainService); - - Assert.True(Equals(testDomainService, anotherTestDomainService)); - } - } - - [Fact] - public void ProvidesAutoRegisteredApplicationServices() - { - using (_sut.BeginScope()) - { - Assert.IsType(_sut.GetInstance()); - } - } - - - [Fact] - public void ProvidesScopedInstancesWhenScopeHasBeenStarted() - { - ITestDomainService scope1Instance; - ITestDomainService scope2Instance; - - using (_sut.BeginScope()) - { - scope1Instance = _sut.GetInstance(); - Assert.NotNull(scope1Instance); - } - - using (_sut.BeginScope()) - { - scope2Instance = _sut.GetInstance(); - Assert.NotNull(scope2Instance); - } - - Assert.NotEqual(scope1Instance, scope2Instance); - } - - [Fact] - public void ProvidesSingletonAndScopedInstancesAccordingly() - { - const int parallelScopeCount = 1000; - object[] scopedInstances = new object[parallelScopeCount]; - object[] singletonInstances = new object[parallelScopeCount]; - Task[] tasks = new Task[parallelScopeCount]; - - var waiter = new ManualResetEvent(false); - - // resolving a singleton service and a scoped service in a massive parallel scenario - for (int index = 0; index < parallelScopeCount; index++) - { - var indexClosure = index; - tasks[index] = Task.Factory.StartNew(() => - { - // using the reset event to enforce a maximum grade of parallelism - waiter.WaitOne(); - using (_sut.BeginScope()) - { - scopedInstances[indexClosure] = _sut.GetInstance(); - singletonInstances[indexClosure] = _sut.GetInstance(); - } - }); - } - - // let the show begin... - waiter.Set(); - Task.WaitAll(tasks); - - // asserting for equality: singleton instances must be equal, scoped instances must be unique - for (int index = 0; index < parallelScopeCount; index++) - { - Assert.NotNull(scopedInstances[index]); - Assert.NotNull(singletonInstances[index]); - - for (int indexComp = 0; indexComp < parallelScopeCount; indexComp++) - { - if (index != indexComp) - { - Assert.NotEqual(scopedInstances[index], scopedInstances[indexComp]); - } - - Assert.Equal(singletonInstances[index], singletonInstances[indexComp]); - } - } - } - - [Fact] - public void ThrowsWhenScopedInstanceIsRequestedOutsideScope() - { - Assert.Throws(() => _sut.GetInstance()); - Assert.Throws(() => _sut.GetInstance(typeof(ITestDomainService))); - Assert.Null(_sut.GetCurrentScope()); - - using (_sut.BeginScope()) - { - var sutInstance = _sut.GetInstance(); - var scopeInstance = _sut.GetInstance(); - Assert.NotNull(sutInstance); - Assert.NotNull(scopeInstance); - Assert.Equal(sutInstance, scopeInstance); - } - - Assert.Null(_sut.GetCurrentScope()); - Assert.Throws(() => _sut.GetInstance()); - Assert.Throws(() => _sut.GetInstance(typeof(ITestDomainService))); - } - - [Fact] - public void CanProvideEventHandlers() - { - using (_sut.BeginScope()) - { - var handlers = _sut.GetAllEventHandlers().ToArray(); - - // these three handlers should have been auto registered during boot by scanning the assembly - Assert.True(handlers.OfType().Any()); - Assert.True(handlers.OfType().Any()); - Assert.True(handlers.OfType().Any()); - } - } - - protected override void Dispose(bool disposing) - { - _sut.Dispose(); - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj index 3d9ed56d..d1d97e03 100644 --- a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj +++ b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj @@ -10,6 +10,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -25,6 +26,7 @@ + diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs index 8737d884..1673424c 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs @@ -27,6 +27,7 @@ public void InvokesActionForAllTenants() var prodTenantIds = Enumerable.Range(10, 10).Select(i => new TenantId(i)).ToArray(); A.CallTo(() => _tenantService.GetActiveDemonstrationTenantIds()).Returns(demoTenantIds); A.CallTo(() => _tenantService.GetActiveProductionTenantIds()).Returns(prodTenantIds); + A.CallTo(() => _tenantService.GetActiveTenantIds()).Returns(prodTenantIds.Concat(demoTenantIds).ToArray()); _sut.Invoke(_ => { }); diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs index 6e181b4a..9ee159c1 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs @@ -1,8 +1,9 @@ using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.EventAggregation.Integration; -using FakeItEasy; +using Backend.Fx.Logging; +using Backend.Fx.MicrosoftDependencyInjection; +using Backend.Fx.Patterns.DependencyInjection; using Xunit; using Xunit.Abstractions; @@ -12,11 +13,14 @@ public class TheSingleTenantApplication : TestWithLogging { private readonly SingleTenantApplication _sut; private readonly ITenantRepository _tenantRepository = new InMemoryTenantRepository(); - private readonly IMessageBus _messageBus = A.Fake(); public TheSingleTenantApplication(ITestOutputHelper output) : base(output) { - _sut = new SingleTenantApplication(_messageBus, _tenantRepository, false); + _sut = new SingleTenantApplication( + _tenantRepository, + false, + new BackendFxApplication(new MicrosoftCompositionRoot(), new ExceptionLoggers(), + typeof(TheSingleTenantApplication).Assembly)); } [Fact] diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs index 494477c5..299158de 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs @@ -1,8 +1,5 @@ using System; -using System.Diagnostics; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.EventAggregation.Integration; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs new file mode 100644 index 00000000..2014e6c1 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Tests.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Backend.Fx.Tests.Patterns.Authorization +{ + public class TheAuthorizingApplication : TestWithLogging + { + public TheAuthorizingApplication(ITestOutputHelper output) : base(output) + { + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveAuthorizationTypes(CompositionRootType compositionRootType) + { + var sut = new AuthorizingApplication( + new BackendFxApplication( + compositionRootType.Create(), + new ExceptionLoggers(), + typeof(TheAuthorizingApplication).Assembly)); + + await sut.BootAsync(); + + sut.Invoker.Invoke(sp => + { + var auth = sp.GetRequiredService>(); + Assert.IsType(auth); + }, + new SystemIdentity(), + new TenantId(345)); + } + } + + public class Auth : DenyAll + { + public override Expression> HasAccessExpression => bfa => true; + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index 5a5284c9..4fe69036 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -2,9 +2,9 @@ using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; -using Backend.Fx.MicrosoftDependencyInjection; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Tests.Patterns.DependencyInjection; using FakeItEasy; using Xunit; using Xunit.Abstractions; @@ -13,32 +13,35 @@ namespace Backend.Fx.Tests.Patterns.DataGeneration { public class TheDataGenerationContext : TestWithLogging { + private readonly TenantId[] _demoTenants = { new TenantId(1), new TenantId(2) }; + private readonly TenantId[] _prodTenants = { new TenantId(11), new TenantId(12) }; + private readonly ITenantIdProvider _tenantIdProvider = A.Fake(); private readonly ITenantWideMutexManager _tenantWideMutexManager = A.Fake(); public TheDataGenerationContext(ITestOutputHelper output) : base(output) { - var tenantIdProvider = A.Fake(); - A.CallTo(() => tenantIdProvider.GetActiveDemonstrationTenantIds()).Returns(_demoTenants); - A.CallTo(() => tenantIdProvider.GetActiveProductionTenantIds()).Returns(_prodTenants); + A.CallTo(() => _tenantIdProvider.GetActiveDemonstrationTenantIds()).Returns(_demoTenants); + A.CallTo(() => _tenantIdProvider.GetActiveProductionTenantIds()).Returns(_prodTenants); - var backendFxApplication = - new BackendFxApplication(new MicrosoftCompositionRoot(), A.Fake(), - GetType().Assembly); - - _sut = new DataGeneratingApplication(tenantIdProvider, _tenantWideMutexManager, backendFxApplication); TestDataGenerator.Calls.Clear(); } - private readonly DataGeneratingApplication _sut; - - private readonly TenantId[] _demoTenants = {new TenantId(1), new TenantId(2)}; - private readonly TenantId[] _prodTenants = {new TenantId(11), new TenantId(12)}; [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTenant) + [InlineData(CompositionRootType.Microsoft, true)] + [InlineData(CompositionRootType.Microsoft, false)] + [InlineData(CompositionRootType.SimpleInjector, true)] + [InlineData(CompositionRootType.SimpleInjector, false)] + public async Task CallsDataGeneratorWhenSeedingForSpecificTenant(CompositionRootType compositionRootType, bool isDemoTenant) { + var backendFxApplication = + new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + GetType().Assembly); + + using var sut = new DataGeneratingApplication(_tenantIdProvider, _tenantWideMutexManager, backendFxApplication); + ITenantWideMutex disposable = A.Fake(); ITenantWideMutex m; var tryAcquireCall = A.CallTo(() => @@ -51,8 +54,8 @@ public async Task CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTena .Returns(true) .AssignsOutAndRefParameters(disposable); - await _sut.BootAsync(); - _sut.DataGenerationContext.SeedDataForTenant(new TenantId(123), isDemoTenant); + await sut.BootAsync(); + sut.DataGenerationContext.SeedDataForTenant(new TenantId(123), isDemoTenant); Assert.Contains(nameof(ProdDataGenerator1), TestDataGenerator.Calls); @@ -71,8 +74,10 @@ public async Task CallsDataGeneratorWhenSeedingForSpecificTenant(bool isDemoTena A.CallTo(() => disposable.Dispose()).MustHaveHappenedOnceExactly(); } - [Fact] - public async Task DoesNothingWhenCannotAcquireTenantWideMutex() + [Theory] + [InlineData(CompositionRootType.SimpleInjector)] + [InlineData(CompositionRootType.Microsoft)] + public async Task DoesNothingWhenCannotAcquireTenantWideMutex(CompositionRootType compositionRootType) { ITenantWideMutex m; var tryAcquireCall = A.CallTo(() => @@ -83,8 +88,15 @@ public async Task DoesNothingWhenCannotAcquireTenantWideMutex() out m)); tryAcquireCall.Returns(false); - await _sut.BootAsync(); - _sut.DataGenerationContext.SeedDataForTenant(new TenantId(123), false); + var backendFxApplication = + new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + GetType().Assembly); + + using var sut = new DataGeneratingApplication(_tenantIdProvider, _tenantWideMutexManager, backendFxApplication); + await sut.BootAsync(); + sut.DataGenerationContext.SeedDataForTenant(new TenantId(123), false); Assert.Empty(TestDataGenerator.Calls); diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs new file mode 100644 index 00000000..a280d823 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs @@ -0,0 +1,8 @@ +namespace Backend.Fx.Tests.Patterns.DependencyInjection +{ + public enum CompositionRootType + { + Microsoft, + SimpleInjector + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs index d1955250..bfec3c0d 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs @@ -1,11 +1,8 @@ using System; using System.Security.Principal; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index 61b5070a..4af0f0f2 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -17,18 +17,18 @@ public class TheBackendFxApplication : TestWithLogging private readonly IBackendFxApplication _sut; private readonly DiTestFakes _fakes = new DiTestFakes(); - public TheBackendFxApplication(ITestOutputHelper output): base(output) + public TheBackendFxApplication(ITestOutputHelper output) : base(output) { _sut = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); } - + [Fact] public void CanWaitForBoot() { int bootTime = 200; A.CallTo(() => _fakes.CompositionRoot.Verify()).Invokes(() => Thread.Sleep(bootTime)); var sw = new Stopwatch(); - + Task.Factory.StartNew(() => _sut.BootAsync()); sw.Start(); Assert.True(_sut.WaitForBoot()); @@ -68,7 +68,7 @@ public void ProvidesExceptionLoggingInvoker() Assert.IsType(_sut.Invoker); } - + [Fact] public void VerifiesCompositionRootOnBoot() { diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs index 9e56f8c9..3df4a38a 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs @@ -13,7 +13,7 @@ public class TheBackendFxDbApplication : TestWithLogging public TheBackendFxDbApplication(ITestOutputHelper output): base(output) { IBackendFxApplication application = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); - _sut = new BackendFxDbApplication(_databaseBootstrapper, _databaseAvailabilityAwaiter, application); + _sut = new PersistentApplication(_databaseBootstrapper, _databaseAvailabilityAwaiter, application); } private readonly DiTestFakes _fakes = new DiTestFakes(); @@ -35,7 +35,7 @@ public void CallsDatabaseBootExtensionPointsOnBoot() public void DelegatesAllCalls() { var application =A.Fake(); - var sut = new BackendFxDbApplication(A.Fake(), + var sut = new PersistentApplication(A.Fake(), A.Fake(), application); diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs new file mode 100644 index 00000000..22675637 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs @@ -0,0 +1,145 @@ +using System.Security.Principal; +using System.Threading.Tasks; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Environment.Authentication; +using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; +using FakeItEasy; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Backend.Fx.Tests.Patterns.DependencyInjection +{ + public class TheDomainModule + { + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveInfrastructureServices(CompositionRootType compositionRootType) + { + var identity = new SystemIdentity(); + var tenantId = new TenantId(333); + + var sut = new BackendFxTestApplication(compositionRootType); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var clock = sp.GetRequiredService(); + Assert.IsType(clock); + + var correlationHolder = sp.GetRequiredService>(); + Assert.IsType(correlationHolder); + + var identityHolder = sp.GetRequiredService>(); + Assert.IsType(identityHolder); + Assert.Equal(identity, identityHolder.Current); + + var tenantIdHolder = sp.GetRequiredService>(); + Assert.IsType(tenantIdHolder); + Assert.Equal(tenantId, tenantIdHolder.Current); + + var domainEventAggregator = sp.GetRequiredService(); + Assert.IsType(domainEventAggregator); + }, + identity, + tenantId); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveAggregateAuthorization(CompositionRootType compositionRootType) + { + var sut = new BackendFxTestApplication(compositionRootType); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var authorization = sp.GetRequiredService>(); + Assert.IsType>(authorization); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveDomainService(CompositionRootType compositionRootType) + { + var sut = new BackendFxTestApplication(compositionRootType); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var service = sp.GetRequiredService(); + Assert.IsType(service); + }, + new SystemIdentity(), + new TenantId(333)); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task CanResolveApplicationService(CompositionRootType compositionRootType) + { + var sut = new BackendFxTestApplication(compositionRootType); + await sut.BootAsync(); + + sut.Invoker.Invoke( + sp => + { + var service = sp.GetRequiredService(); + Assert.IsType(service); + }, + new SystemIdentity(), + new TenantId(333)); + } + } + + public class BackendFxTestApplication : BackendFxApplication + { + public BackendFxTestApplication(CompositionRootType compositionRootType) + : base( + compositionRootType.Create(), + A.Fake(), + typeof(BackendFxTestApplication).Assembly) + { + } + } + + public class BackendFxAggregate : AggregateRoot + { + public string Name { get; set; } + + public BackendFxAggregate(int id, string name) : base(id) + { + Name = name; + } + } + + public interface IAnotherDomainService + { + } + + public class AnotherDomainService : IAnotherDomainService, IDomainService + { + } + + public interface IAnotherApplicationService + { + } + + public class AnotherApplicationService : IAnotherApplicationService, IDomainService + { + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index 5ca8a3c3..9ca44147 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -6,6 +6,7 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Integration; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs new file mode 100644 index 00000000..da2eba9a --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Tests.Patterns.DependencyInjection; +using FakeItEasy; +using Xunit; + +namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration +{ + public class TheMessageBusApplication + { + [Fact] + public async Task Boots() + { + var app = new MessageBusTestApplication(CompositionRootType.Microsoft); + await app.BootAsync(); + } + } + + public class MessageBusTestApplication : MessageBusApplication + { + public MessageBusTestApplication(CompositionRootType compositionRootType) + : base( + new InMemoryMessageBus(), + new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + typeof(MessageBusTestApplication).Assembly)) + { + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs new file mode 100644 index 00000000..edd20131 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.Jobs; +using Backend.Fx.Tests.Patterns.DependencyInjection; +using FakeItEasy; +using FluentScheduler; +using Microsoft.Extensions.DependencyInjection; +using Xunit; +using Xunit.Abstractions; +using IJob = Backend.Fx.Patterns.Jobs.IJob; + +namespace Backend.Fx.Tests.Patterns.Jobs +{ + public class TheApplicationWithJobs : TestWithLogging + { + private readonly ITenantIdProvider _tenantIdProvider = A.Fake(); + + public TheApplicationWithJobs(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + SayHelloJob.Invocations.Clear(); + A.CallTo(() => _tenantIdProvider.GetActiveTenantIds()).ReturnsLazily(() => new[] + { + new TenantId(123), + new TenantId(234), + new TenantId(345), + new TenantId(456), + }); + } + + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task RunsJobs(CompositionRootType compositionRootType) + { + using var sut = new FluentSchedulerApplication( + _tenantIdProvider, + new BackendFxApplication( + compositionRootType.Create(), + new ExceptionLoggers(), + typeof(TheApplicationWithJobs).Assembly)); + + await sut.BootAsync(); + + await Task.Delay(TimeSpan.FromSeconds(1)); + + sut.Dispose(); + + Assert.True(SayHelloJob.Invocations.Count > 35); + Assert.True(SayHelloJob.Invocations.Count < 45); + } + + private class FluentSchedulerApplication : ApplicationWithJobs + { + private readonly ITenantIdProvider _tenantIdProvider; + + public FluentSchedulerApplication(ITenantIdProvider tenantIdProvider, IBackendFxApplication application) + : base(application) + { + _tenantIdProvider = tenantIdProvider; + application.CompositionRoot.RegisterModules(new JobModule(Assemblies)); + } + + public override async Task BootAsync(CancellationToken cancellationToken = default) + { + await base.BootAsync(cancellationToken); + JobManager.RemoveAllJobs(); + JobManager.Initialize(new JobSchedule(this, _tenantIdProvider)); + } + } + + private class JobSchedule : Registry + { + private readonly AllTenantBackendFxApplicationInvoker _invoker; + + public JobSchedule(IBackendFxApplication application, ITenantIdProvider tenantIdProvider) + { + _invoker = new AllTenantBackendFxApplicationInvoker(tenantIdProvider, application.Invoker); + NonReentrantAsDefault(); + + Schedule(() => _invoker.Invoke(sp => sp.GetRequiredService().Run())) + .ToRunNow() + .AndEvery(interval: 111) + .Milliseconds(); + } + } + } + + public class SayHelloJob : IJob + { + private readonly ICurrentTHolder _tenantIdHolder; + public static List Invocations = new List(); + + public SayHelloJob(ICurrentTHolder tenantIdHolder) + { + _tenantIdHolder = tenantIdHolder; + } + + public void Run() + { + Invocations.Add($"Hello Tenant {_tenantIdHolder.Current}"); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/TestHelpers.cs b/tests/Backend.Fx.Tests/TestHelpers.cs new file mode 100644 index 00000000..cfced19c --- /dev/null +++ b/tests/Backend.Fx.Tests/TestHelpers.cs @@ -0,0 +1,21 @@ +using System; +using Backend.Fx.MicrosoftDependencyInjection; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.SimpleInjectorDependencyInjection; +using Backend.Fx.Tests.Patterns.DependencyInjection; + +namespace Backend.Fx.Tests +{ + public static class TestHelpers + { + public static ICompositionRoot Create(this CompositionRootType compositionRootType) + { + return compositionRootType switch + { + CompositionRootType.Microsoft => new MicrosoftCompositionRoot(), + CompositionRootType.SimpleInjector => new SimpleInjectorCompositionRoot(), + _ => throw new ArgumentException(nameof(compositionRootType)) + }; + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/TestWithLogging.cs b/tests/Backend.Fx.Tests/TestWithLogging.cs index ad386a42..8094c833 100644 --- a/tests/Backend.Fx.Tests/TestWithLogging.cs +++ b/tests/Backend.Fx.Tests/TestWithLogging.cs @@ -1,5 +1,4 @@ using System; -using Backend.Fx.Logging; using Serilog; using Serilog.Extensions.Logging; using Xunit.Abstractions; From 9372a1a576e68abc4ed64646f50a621a6f3e4cb2 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 10:02:13 -0300 Subject: [PATCH 05/51] TenantCreation vs. DataGeneration --- .../DataGeneratingApplication.cs | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs index 3e6f6216..f686177f 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs @@ -10,7 +10,7 @@ namespace Backend.Fx.Patterns.DataGeneration { /// /// Enriches the by calling all data generators for all tenants - /// on application start and when a tenant gets activated + /// on application start /// public class DataGeneratingApplication : BackendFxApplicationDecorator { @@ -40,7 +40,6 @@ public DataGeneratingApplication( public override async Task BootAsync(CancellationToken cancellationToken = default) { _application.CompositionRoot.RegisterModules(new DataGenerationModule(_application.Assemblies)); - //EnableDataGenerationForNewTenants(); await base.BootAsync(cancellationToken).ConfigureAwait(false); SeedDataForAllActiveTenants(); } @@ -62,29 +61,5 @@ private void SeedDataForAllActiveTenants() } } } - - // todo refactor - // private void EnableDataGenerationForNewTenants() - // { - // _application..Subscribe(new DelegateIntegrationMessageHandler(tenantCreated => - // { - // Logger.LogInformation( - // "Seeding data for recently activated tenant (with demo data: {IsDemoTenant}) {TenantId}", - // tenantCreated.IsDemoTenant, - // tenantCreated.TenantId); - // try - // { - // DataGenerationContext.SeedDataForTenant(new TenantId(tenantCreated.TenantId), - // tenantCreated.IsDemoTenant); - // } - // catch (Exception ex) - // { - // Logger.LogError(ex, - // "Seeding data for recently activated tenant (with demo data: {IsDemoTenant}) {TenantId} failed", - // tenantCreated.IsDemoTenant, - // tenantCreated.TenantId); - // } - // })); - // } } } \ No newline at end of file From f9f9797a2ec3418953d297d208fb0cd99a737421 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 11:01:23 -0300 Subject: [PATCH 06/51] renaming EfCore5 --- Backend.Fx.sln | 4 +- .../BackendFxApplication.cs | 17 ++++++++ .../BackendFxApplicationDecorator.cs | 15 +++++++ .../Integration/MessageBusApplication.cs | 4 +- .../RaiseIntegrationEventsInvokerDecorator.cs | 16 ++++---- .../AggregateMapping.cs | 2 +- .../Backend.Fx.EfCore5Persistence.csproj} | 2 +- .../DbContextTransactionOperationDecorator.cs | 2 +- .../Bootstrapping/EfCorePersistenceModule.cs | 2 +- .../Bootstrapping/IDbConnectionFactory.cs | 2 +- .../DbContextExtensions.cs | 2 +- .../EfFlush.cs | 2 +- .../EfRepository.cs | 2 +- .../EntityQueryable.cs | 2 +- .../IAggregateMapping.cs | 2 +- .../Mssql/MsSqlSequence.cs | 4 +- .../Oracle/OracleSequence.cs | 4 +- .../PlainAggregateMapping.cs | 2 +- .../Postgres/PostgresSequence.cs | 4 +- .../Properties/AssemblyInfo.cs | 0 ...ackend.Fx.EfCore5Persistence.Tests.csproj} | 2 +- .../DbConnectionEx.cs | 2 +- .../DummyImpl/Domain/Blog.cs | 2 +- .../DummyImpl/Domain/BlogAuthorization.cs | 2 +- .../DummyImpl/Domain/Blogger.cs | 2 +- .../DummyImpl/Domain/BloggerAuthorization.cs | 2 +- .../DummyImpl/Domain/Post.cs | 2 +- .../DummyImpl/Persistence/BlogMapping.cs | 4 +- .../DummyImpl/Persistence/BloggerMapping.cs | 8 ++++ .../DummyImpl/Persistence/TestDbContext.cs | 4 +- .../Persistence/TestDbContextFactory.cs | 2 +- .../Fixtures/DatabaseFixture.cs | 4 +- .../Fixtures/SqlServerDatabaseFixture.cs | 4 +- .../Fixtures/SqliteDatabaseFixture.cs | 4 +- .../Fixtures/TestDbSession.cs | 4 +- .../20190624150947_Initial.Designer.cs | 2 +- .../Migrations/20190624150947_Initial.cs | 0 .../Migrations/TestDbContextModelSnapshot.cs | 2 +- .../TestConfig.cs | 0 .../TheDbApplicationWithEfCore.cs | 4 +- .../TheDbContext.cs | 6 +-- .../TheRepositoryOfComposedAggregate.cs | 8 ++-- .../TheRepositoryOfPlainAggregate.cs | 8 ++-- .../DummyImpl/Persistence/BloggerMapping.cs | 8 ---- .../TheAuthorizingApplication.cs | 4 ++ .../TheBackendFxApplication.cs | 41 +++++++++++++++++++ 46 files changed, 149 insertions(+), 72 deletions(-) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/AggregateMapping.cs (91%) rename src/implementations/{Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj => Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj} (97%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Bootstrapping/DbContextTransactionOperationDecorator.cs (93%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Bootstrapping/EfCorePersistenceModule.cs (99%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Bootstrapping/IDbConnectionFactory.cs (67%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/DbContextExtensions.cs (98%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/EfFlush.cs (99%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/EfRepository.cs (99%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/EntityQueryable.cs (97%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/IAggregateMapping.cs (91%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Mssql/MsSqlSequence.cs (96%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Oracle/OracleSequence.cs (97%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/PlainAggregateMapping.cs (93%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Postgres/PostgresSequence.cs (96%) rename src/implementations/{Backend.Fx.EfCorePersistence => Backend.Fx.EfCore5Persistence}/Properties/AssemblyInfo.cs (100%) rename tests/{Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj => Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj} (96%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DbConnectionEx.cs (96%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Domain/Blog.cs (93%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Domain/BlogAuthorization.cs (72%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Domain/Blogger.cs (88%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Domain/BloggerAuthorization.cs (72%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Domain/Post.cs (94%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Persistence/BlogMapping.cs (83%) create mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Persistence/TestDbContext.cs (87%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/DummyImpl/Persistence/TestDbContextFactory.cs (88%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Fixtures/DatabaseFixture.cs (92%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Fixtures/SqlServerDatabaseFixture.cs (95%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Fixtures/SqliteDatabaseFixture.cs (90%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Fixtures/TestDbSession.cs (90%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Migrations/20190624150947_Initial.Designer.cs (98%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Migrations/20190624150947_Initial.cs (100%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/Migrations/TestDbContextModelSnapshot.cs (98%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/TestConfig.cs (100%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/TheDbApplicationWithEfCore.cs (99%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/TheDbContext.cs (93%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/TheRepositoryOfComposedAggregate.cs (98%) rename tests/{Backend.Fx.EfCorePersistence.Tests => Backend.Fx.EfCore5Persistence.Tests}/TheRepositoryOfPlainAggregate.cs (97%) delete mode 100644 tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs diff --git a/Backend.Fx.sln b/Backend.Fx.sln index 3cb362d4..0388ec29 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -15,7 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C7885592 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.Tests", "tests\Backend.Fx.Tests\Backend.Fx.Tests.csproj", "{3706F748-43F6-41BD-8875-81FA679220C7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCorePersistence.Tests", "tests\Backend.Fx.EfCorePersistence.Tests\Backend.Fx.EfCorePersistence.Tests.csproj", "{4BB72B85-61F2-4C7F-9079-EA43492FCD44}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCore5Persistence.Tests", "tests\Backend.Fx.EfCore5Persistence.Tests\Backend.Fx.EfCore5Persistence.Tests.csproj", "{4BB72B85-61F2-4C7F-9079-EA43492FCD44}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "abstractions", "abstractions", "{A742F814-725A-44ED-95E6-98E142738E9D}" EndProject @@ -25,7 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "implementations", "implemen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx", "src\abstractions\Backend.Fx\Backend.Fx.csproj", "{581DCC00-9246-4A2E-AE31-206742B2746A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCorePersistence", "src\implementations\Backend.Fx.EfCorePersistence\Backend.Fx.EfCorePersistence.csproj", "{A60B7952-D92C-403D-9710-65BE13963C7E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.EfCore5Persistence", "src\implementations\Backend.Fx.EfCore5Persistence\Backend.Fx.EfCore5Persistence.csproj", "{A60B7952-D92C-403D-9710-65BE13963C7E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend.Fx.RabbitMq", "src\implementations\Backend.Fx.RabbitMq\Backend.Fx.RabbitMq.csproj", "{2C826FC0-443A-4874-B213-C35BFDEA200A}" EndProject diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs index c1c62968..b2ba4a69 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Backend.Fx.Extensions; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -45,6 +47,8 @@ public interface IBackendFxApplication : IDisposable /// /// Task BootAsync(CancellationToken cancellationToken = default); + + TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationDecorator; } @@ -61,6 +65,13 @@ public class BackendFxApplication : IBackendFxApplication /// public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger exceptionLogger, params Assembly[] assemblies) { + assemblies = assemblies ?? Array.Empty(); + + Logger.LogInformation( + "Initializing application with {CompositionRoot} providing services from [{Assemblies}]", + compositionRoot.GetType().GetDetailedTypeName(), + string.Join(", ", assemblies.Select(ass => ass.GetName().Name))); + var invoker = new BackendFxApplicationInvoker(compositionRoot); AsyncInvoker = new ExceptionLoggingAsyncInvoker(exceptionLogger, invoker); Invoker = new ExceptionLoggingInvoker(exceptionLogger, invoker); @@ -89,6 +100,12 @@ public Task BootAsync(CancellationToken cancellationToken = default) return Task.CompletedTask; } + public virtual TBackendFxApplicationDecorator As() + where TBackendFxApplicationDecorator : BackendFxApplicationDecorator + { + return null; + } + public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default) { return _isBooted.Wait(timeoutMilliSeconds, cancellationToken); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs index c447df7c..09088fa9 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs @@ -2,16 +2,21 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Backend.Fx.Extensions; using Backend.Fx.Logging; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.DependencyInjection { public abstract class BackendFxApplicationDecorator : IBackendFxApplication { + private static readonly ILogger Logger = Log.Create(); private readonly IBackendFxApplication _application; protected BackendFxApplicationDecorator(IBackendFxApplication application) { + Logger.LogInformation("Decorating the application with {Decorator}", GetType().GetDetailedTypeName()); _application = application; } @@ -37,6 +42,16 @@ public virtual Task BootAsync(CancellationToken cancellationToken = default) return _application.BootAsync(cancellationToken); } + public TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationDecorator + { + if (this is TBackendFxApplicationDecorator matchingDecorator) + { + return matchingDecorator; + } + + return _application.As(); + } + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs index cb3a28c1..bf107b4d 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -14,8 +14,8 @@ public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication appli { application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); _messageBus = messageBus; - Invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot.ServiceProvider, base.Invoker); - AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot.ServiceProvider, base.AsyncInvoker); + Invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot, base.Invoker); + AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot, base.AsyncInvoker); } public override async Task BootAsync(CancellationToken cancellationToken = default) diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs index 567c86c9..4d40cf8e 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs @@ -10,14 +10,14 @@ namespace Backend.Fx.Patterns.EventAggregation.Integration { public class RaiseIntegrationEventsInvokerDecorator : IBackendFxApplicationInvoker { - private readonly IServiceProvider _serviceProvider; + private readonly ICompositionRoot _compositionRoot; private readonly IBackendFxApplicationInvoker _invoker; public RaiseIntegrationEventsInvokerDecorator( - IServiceProvider serviceProvider, + ICompositionRoot compositionRoot, IBackendFxApplicationInvoker invoker) { - _serviceProvider = serviceProvider; + _compositionRoot = compositionRoot; _invoker = invoker; } @@ -28,20 +28,20 @@ public void Invoke( Guid? correlationId = null) { _invoker.Invoke(action, identity, tenantId, correlationId); - AsyncHelper.RunSync(() => _serviceProvider.GetRequiredService().RaiseEvents()); + AsyncHelper.RunSync(() => _compositionRoot.ServiceProvider.GetRequiredService().RaiseEvents()); } } public class RaiseIntegrationEventsAsyncInvokerDecorator : IBackendFxApplicationAsyncInvoker { - private readonly IServiceProvider _serviceProvider; + private readonly ICompositionRoot _compositionRoot; private readonly IBackendFxApplicationAsyncInvoker _invoker; public RaiseIntegrationEventsAsyncInvokerDecorator( - IServiceProvider serviceProvider, + ICompositionRoot compositionRoot, IBackendFxApplicationAsyncInvoker invoker) { - _serviceProvider = serviceProvider; + _compositionRoot = compositionRoot; _invoker = invoker; } @@ -52,7 +52,7 @@ public async Task InvokeAsync( Guid? correlationId = null) { await _invoker.InvokeAsync(awaitableAsyncAction, identity, tenantId, correlationId).ConfigureAwait(false); - await _serviceProvider.GetRequiredService().RaiseEvents().ConfigureAwait(false); + await _compositionRoot.ServiceProvider.GetRequiredService().RaiseEvents().ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCorePersistence/AggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs similarity index 91% rename from src/implementations/Backend.Fx.EfCorePersistence/AggregateMapping.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs index a7ce812b..00a725c3 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/AggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs @@ -4,7 +4,7 @@ using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public abstract class AggregateMapping : IAggregateMapping where T : AggregateRoot { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj b/src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj similarity index 97% rename from src/implementations/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj rename to src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj index 4e63596d..a89f74b3 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Backend.Fx.EfCorePersistence.csproj +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj @@ -14,7 +14,7 @@ Marc Wittke anic GmbH All rights reserved. Distributed under the terms of the MIT License. - Persistence implementation for Backend.Fx using Entity Framework Core 2 + Persistence implementation for Backend.Fx using Entity Framework Core 6 False MIT https://github.com/marcwittke/Backend.Fx diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs similarity index 93% rename from src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/DbContextTransactionOperationDecorator.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs index 6dedd0be..32813a63 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs @@ -4,7 +4,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCore5Persistence.Bootstrapping { public class DbContextTransactionOperationDecorator : DbTransactionOperationDecorator { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs similarity index 99% rename from src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index 593f6b0e..e59a6465 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCore5Persistence.Bootstrapping { public class EfCorePersistenceModule : IModule where TDbContext : DbContext diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/IDbConnectionFactory.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/IDbConnectionFactory.cs similarity index 67% rename from src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/IDbConnectionFactory.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/IDbConnectionFactory.cs index df953115..ccb05b17 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Bootstrapping/IDbConnectionFactory.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/IDbConnectionFactory.cs @@ -1,6 +1,6 @@ using System.Data; -namespace Backend.Fx.EfCorePersistence.Bootstrapping +namespace Backend.Fx.EfCore5Persistence.Bootstrapping { public interface IDbConnectionFactory { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs similarity index 98% rename from src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs index 065d0ad3..9bb31099 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public static class DbContextExtensions { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs similarity index 99% rename from src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs index eb3f828d..e53ec3ac 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs @@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public class EfFlush : ICanFlush { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs similarity index 99% rename from src/implementations/Backend.Fx.EfCorePersistence/EfRepository.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs index 03258217..2b784efc 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs @@ -17,7 +17,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public class EfRepository : Repository, IAsyncRepository where TAggregateRoot : AggregateRoot { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EntityQueryable.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs similarity index 97% rename from src/implementations/Backend.Fx.EfCorePersistence/EntityQueryable.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs index 48d4a959..f3d60231 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EntityQueryable.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs @@ -7,7 +7,7 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public class EntityQueryable : IQueryable where TEntity : Entity { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/IAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs similarity index 91% rename from src/implementations/Backend.Fx.EfCorePersistence/IAggregateMapping.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs index 76dd9fd5..41dcefe7 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/IAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs @@ -4,7 +4,7 @@ using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public interface IAggregateMapping { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs similarity index 96% rename from src/implementations/Backend.Fx.EfCorePersistence/Mssql/MsSqlSequence.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs index 9131007e..041671e9 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs @@ -1,12 +1,12 @@ using System; using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence.Mssql +namespace Backend.Fx.EfCore5Persistence.Mssql { public abstract class MsSqlSequence : ISequence { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs similarity index 97% rename from src/implementations/Backend.Fx.EfCorePersistence/Oracle/OracleSequence.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs index aea02cef..9bcc3592 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs @@ -1,12 +1,12 @@ using System; using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence.Oracle +namespace Backend.Fx.EfCore5Persistence.Oracle { public abstract class OracleSequence : ISequence { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs similarity index 93% rename from src/implementations/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs index 2bf4efec..fe15600c 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/PlainAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs @@ -4,7 +4,7 @@ using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence +namespace Backend.Fx.EfCore5Persistence { public class PlainAggregateMapping : AggregateMapping where TAggregateRoot : AggregateRoot diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs similarity index 96% rename from src/implementations/Backend.Fx.EfCorePersistence/Postgres/PostgresSequence.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs index 7d51835b..e759bdca 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs @@ -1,12 +1,12 @@ using System; using System.Data; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.EfCorePersistence.Postgres +namespace Backend.Fx.EfCore5Persistence.Postgres { public abstract class PostgresSequence : ISequence { diff --git a/src/implementations/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Properties/AssemblyInfo.cs similarity index 100% rename from src/implementations/Backend.Fx.EfCorePersistence/Properties/AssemblyInfo.cs rename to src/implementations/Backend.Fx.EfCore5Persistence/Properties/AssemblyInfo.cs diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj similarity index 96% rename from tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj rename to tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj index 98d43617..6916f6ce 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Backend.Fx.EfCorePersistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DbConnectionEx.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DbConnectionEx.cs similarity index 96% rename from tests/Backend.Fx.EfCorePersistence.Tests/DbConnectionEx.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DbConnectionEx.cs index 499d8a51..e3d9e0d5 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DbConnectionEx.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DbConnectionEx.cs @@ -3,7 +3,7 @@ using System.Data; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore5Persistence.Tests { public static class DbConnectionEx { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blog.cs similarity index 93% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blog.cs index 27c8be41..20c02646 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blog.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blog.cs @@ -3,7 +3,7 @@ using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain { public class Blog : AggregateRoot { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs similarity index 72% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs index 958e27a8..f1d9937d 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BlogAuthorization.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs @@ -1,7 +1,7 @@ using Backend.Fx.Patterns.Authorization; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain { [UsedImplicitly] public class BlogAuthorization : AllowAll diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs similarity index 88% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs index a21db5ab..2fe53b32 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Blogger.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs @@ -1,7 +1,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain { public class Blogger : AggregateRoot { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs similarity index 72% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs index fe223d0c..5cf3b44f 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs @@ -1,7 +1,7 @@ using Backend.Fx.Patterns.Authorization; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain { [UsedImplicitly] public class BloggerAuthorization : AllowAll diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Post.cs similarity index 94% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Post.cs index e71875c0..8dc534ae 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Domain/Post.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Post.cs @@ -2,7 +2,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain { public class Post : Entity { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs similarity index 83% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs index 4f21dddb..529ff2cd 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BlogMapping.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence { public class BlogMapping : AggregateMapping { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs new file mode 100644 index 00000000..56a926af --- /dev/null +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs @@ -0,0 +1,8 @@ +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; + +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence +{ + public class BloggerMapping : PlainAggregateMapping + { + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs similarity index 87% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs index bd40f2f5..b47f2cea 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs @@ -1,9 +1,9 @@ -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; using Backend.Fx.Environment.MultiTenancy; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence { public sealed class TestDbContext : DbContext { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs similarity index 88% rename from tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs index cefd3713..4f21c7fe 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence { [UsedImplicitly] [Obsolete("Only for migration support at design time")] diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs similarity index 92% rename from tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/DatabaseFixture.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs index 59578f8c..8c902651 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -1,12 +1,12 @@ using System.Data; using System.Security.Principal; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures { public abstract class DatabaseFixture { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs similarity index 95% rename from tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqlServerDatabaseFixture.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 5f57d5b2..26e6504e 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -1,12 +1,12 @@ using System; using System.Data; using System.Data.SqlClient; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures { [Obsolete("Not supported on build agents")] public class SqlServerDatabaseFixture : DatabaseFixture diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs similarity index 90% rename from tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqliteDatabaseFixture.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 18440c2c..6c4b6a6e 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,12 +1,12 @@ using System.Data; using System.IO; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures { public class SqliteDatabaseFixture : DatabaseFixture { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs similarity index 90% rename from tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/TestDbSession.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs index 8fe47f7f..b997adc6 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs @@ -1,12 +1,12 @@ using System; using System.Data; using System.Security.Principal; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.EfCorePersistence.Tests.Fixtures +namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures { public class TestDbSession : ICanFlush, IDisposable { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs similarity index 98% rename from tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs index 1c1cdd8b..82d686e0 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.Designer.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.cs similarity index 100% rename from tests/Backend.Fx.EfCorePersistence.Tests/Migrations/20190624150947_Initial.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.cs diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs similarity index 98% rename from tests/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs index e76dd8b8..49a7653f 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/Migrations/TestDbContextModelSnapshot.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // using System; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TestConfig.cs similarity index 100% rename from tests/Backend.Fx.EfCorePersistence.Tests/TestConfig.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/TestConfig.cs diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs similarity index 99% rename from tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs index ee35c8bb..db402835 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -4,7 +4,7 @@ using System.IO; using System.Threading.Tasks; using Backend.Fx.BuildingBlocks; -using Backend.Fx.EfCorePersistence.Bootstrapping; +using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Environment.Persistence; @@ -22,7 +22,7 @@ using Xunit; using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore5Persistence.Tests { public class TheDbApplicationWithEfCore { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs similarity index 93% rename from tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs index d348b045..4ce3580b 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs @@ -1,12 +1,12 @@ using System.Linq; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore5Persistence.Tests.Fixtures; using Backend.Fx.Tests; using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore5Persistence.Tests { public class TheDbContext: TestWithLogging { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs similarity index 98% rename from tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs index a8ebb6a0..d04b13e6 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.Fixtures; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; @@ -15,7 +15,7 @@ using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore5Persistence.Tests { public class TheRepositoryOfComposedAggregate : TestWithLogging { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs similarity index 97% rename from tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs index a77f4b57..d5321e83 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.Fixtures; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; @@ -11,7 +11,7 @@ using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.EfCorePersistence.Tests +namespace Backend.Fx.EfCore5Persistence.Tests { public class TheRepositoryOfPlainAggregate: TestWithLogging { diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs deleted file mode 100644 index 2659bcd9..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/BloggerMapping.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; - -namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence -{ - public class BloggerMapping : PlainAggregateMapping - { - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs index 2014e6c1..548282ad 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs @@ -6,7 +6,11 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Patterns.Jobs; using Backend.Fx.Tests.Patterns.DependencyInjection; +using Backend.Fx.Tests.Patterns.Jobs; +using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index 4af0f0f2..bb9eb542 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -2,9 +2,13 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; +using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Tests.Patterns.Authorization; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -22,6 +26,43 @@ public TheBackendFxApplication(ITestOutputHelper output) : base(output) _sut = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); } + [Fact] + public void ProvidesSpecificDecoratorWhenPresent() + { + var sut = + new SingleTenantApplication( + A.Fake(), + true, + new MessageBusApplication( + A.Fake(), + new AuthorizingApplication( + new BackendFxApplication( + CompositionRootType.Microsoft.Create(), + new ExceptionLoggers(), + typeof(TheAuthorizingApplication).Assembly)))); + + var authorizingApplication = sut.As(); + Assert.NotNull(authorizingApplication); + } + + [Fact] + public void ProvidesNoDecoratorWhenNotPresent() + { + var sut = + new SingleTenantApplication( + A.Fake(), + true, + new MessageBusApplication( + A.Fake(), + new BackendFxApplication( + CompositionRootType.Microsoft.Create(), + new ExceptionLoggers(), + typeof(TheAuthorizingApplication).Assembly))); + + var authorizingApplication = sut.As(); + Assert.Null(authorizingApplication); + } + [Fact] public void CanWaitForBoot() { From 6a33af989dc511ec00034b40083ce2d1a62b0883 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 11:08:14 -0300 Subject: [PATCH 07/51] coverage --- .../MultiTenancy/TenantActivated.cs | 10 -- .../MultiTenancy/TenantDeactivated.cs | 10 -- .../Environment/MultiTenancy/TenantDeleted.cs | 10 -- .../Environment/MultiTenancy/TenantEvent.cs | 20 ---- .../Environment/MultiTenancy/TenantEvents.cs | 14 --- .../Environment/MultiTenancy/TenantUpdated.cs | 10 -- .../Patterns/DataGeneration/DataGenerated.cs | 14 --- .../Patterns/DependencyInjection/Operation.cs | 2 +- .../DependencyInjection/WaitForBootInvoker.cs | 24 ---- .../Integration/MessageBusApplication.cs | 5 +- .../IdGeneration/SequenceHiLoIdGenerator.cs | 4 +- .../IdGeneration/SequenceIdGenerator.cs | 4 +- .../TheBackendFxApplication.cs | 38 ++++++- .../DependencyInjection/TheOperation.cs | 106 ++++++++++++++++++ .../Patterns/IdGeneration/InMemorySequence.cs | 29 +++++ .../IdGeneration/TheHiLoIdGenerator.cs | 47 ++------ .../IdGeneration/TheSequenceIdGenerator.cs | 50 +++++++++ 17 files changed, 237 insertions(+), 160 deletions(-) delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeleted.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantUpdated.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs deleted file mode 100644 index ad2a5a9a..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantActivated.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - public class TenantActivated : TenantEvent - { - public TenantActivated(int tenantId, string name, string description, bool isDemoTenant) - : base(tenantId, name, description, isDemoTenant) - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs deleted file mode 100644 index c014cfca..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeactivated.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - public class TenantDeactivated : TenantEvent - { - public TenantDeactivated(int tenantId, string name, string description, bool isDemoTenant) - : base(tenantId, name, description, isDemoTenant) - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeleted.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeleted.cs deleted file mode 100644 index e845d341..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantDeleted.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - public class TenantDeleted : TenantEvent - { - public TenantDeleted(int tenantId, string name, string description, bool isDemoTenant) - : base(tenantId, name, description, isDemoTenant) - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs deleted file mode 100644 index 5c59e9be..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvent.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - public abstract class TenantEvent - { - protected TenantEvent(int tenantId, string name, string description, bool isDemoTenant) - { - TenantId = tenantId; - Name = name; - Description = description; - IsDemoTenant = isDemoTenant; - } - - public int TenantId { get; } - public string Name { get; } - - public string Description { get; } - - public bool IsDemoTenant { get; } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs deleted file mode 100644 index 20cbb70a..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantEvents.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Concurrent; - -namespace Backend.Fx.Environment.MultiTenancy -{ - public class TenantEvents - { - private readonly ConcurrentQueue _events = new ConcurrentQueue(); - - public void Publish(TenantEvent tenantEvent) - { - _events.Enqueue(tenantEvent); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantUpdated.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantUpdated.cs deleted file mode 100644 index 509b7d89..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantUpdated.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - public class TenantUpdated : TenantEvent - { - public TenantUpdated(int tenantId, string name, string description, bool isDemoTenant) - : base(tenantId, name, description, isDemoTenant) - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs b/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs deleted file mode 100644 index bc778c67..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerated.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Backend.Fx.Patterns.EventAggregation.Integration; - -namespace Backend.Fx.Patterns.DataGeneration -{ - /// - /// Will appear on the message bus when the data generation process has been completed - /// - public class DataGenerated : IntegrationEvent - { - public DataGenerated(int tenantId) : base() - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs index a1a5b39f..56e0a2a2 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs @@ -43,7 +43,7 @@ public virtual void Complete() Logger.LogInformation("Completing operation #{OperationId}", _instanceId); if (_isActive != true) { - throw new InvalidOperationException($"Cannot begin an operation that is {(_isActive == false ? "terminated" : "not active")}"); + throw new InvalidOperationException($"Cannot complete an operation that is {(_isActive == false ? "terminated" : "not active")}"); } _isActive = false; diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs deleted file mode 100644 index 07326cd3..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/WaitForBootInvoker.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Security.Principal; -using Backend.Fx.Environment.MultiTenancy; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - public class WaitForBootInvoker : IBackendFxApplicationInvoker - { - private readonly IBackendFxApplication _application; - private readonly IBackendFxApplicationInvoker _invoker; - - public WaitForBootInvoker(IBackendFxApplication application, IBackendFxApplicationInvoker invoker) - { - _application = application; - _invoker = invoker; - } - - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) - { - _application.WaitForBoot(); - _invoker.Invoke(action, identity, tenantId, correlationId); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs index bf107b4d..dc8972d7 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -7,7 +7,6 @@ namespace Backend.Fx.Patterns.EventAggregation.Integration public class MessageBusApplication : BackendFxApplicationDecorator { private readonly IMessageBus _messageBus; - private readonly IBackendFxApplicationInvoker _invoker; public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication application) : base(application) @@ -21,7 +20,9 @@ public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication appli public override async Task BootAsync(CancellationToken cancellationToken = default) { await base.BootAsync(cancellationToken).ConfigureAwait(false); - _messageBus.ProvideInvoker(new SequentializingBackendFxApplicationInvoker(new ExceptionLoggingAndHandlingInvoker(ExceptionLogger, Invoker))); + _messageBus.ProvideInvoker( + new SequentializingBackendFxApplicationInvoker( + new ExceptionLoggingAndHandlingInvoker(ExceptionLogger, Invoker))); _messageBus.Connect(); } diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs index ec4b7619..dd154181 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs @@ -1,10 +1,10 @@ namespace Backend.Fx.Patterns.IdGeneration { - public abstract class SequenceHiLoIdGenerator : HiLoIdGenerator + public class SequenceHiLoIdGenerator : HiLoIdGenerator { private readonly ISequence _sequence; - protected SequenceHiLoIdGenerator(ISequence sequence) + public SequenceHiLoIdGenerator(ISequence sequence) { _sequence = sequence; } diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs index 9d696ff3..ff1f44c5 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs @@ -1,10 +1,10 @@ namespace Backend.Fx.Patterns.IdGeneration { - public abstract class SequenceIdGenerator : IIdGenerator + public class SequenceIdGenerator : IIdGenerator { private readonly ISequence _sequence; - protected SequenceIdGenerator(ISequence sequence) + public SequenceIdGenerator(ISequence sequence) { _sequence = sequence; } diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index bb9eb542..946f883d 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; using Backend.Fx.Patterns.Authorization; @@ -20,10 +21,11 @@ public class TheBackendFxApplication : TestWithLogging { private readonly IBackendFxApplication _sut; private readonly DiTestFakes _fakes = new DiTestFakes(); + private readonly IExceptionLogger _exceptionLogger = A.Fake(); public TheBackendFxApplication(ITestOutputHelper output) : base(output) { - _sut = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); + _sut = new BackendFxApplication(_fakes.CompositionRoot, _exceptionLogger); } [Fact] @@ -77,9 +79,9 @@ public void CanWaitForBoot() } [Fact] - public void DisposesCompositionRootOnDispose() + public async Task DisposesCompositionRootOnDispose() { - _sut.BootAsync(); + await _sut.BootAsync(); _sut.Dispose(); A.CallTo(() => _fakes.CompositionRoot.Dispose()).MustHaveHappenedOnceExactly(); } @@ -111,10 +113,36 @@ public void ProvidesExceptionLoggingInvoker() [Fact] - public void VerifiesCompositionRootOnBoot() + public async Task VerifiesCompositionRootOnBoot() { - _sut.BootAsync(); + await _sut.BootAsync(); A.CallTo(() => _fakes.CompositionRoot.Verify()).MustHaveHappenedOnceExactly(); } + + [Fact] + public async Task LogsButDoesNotHandleExceptions() + { + var exception = new Exception(); + + await _sut.BootAsync(); + Assert.Throws(() => + _sut.Invoker.Invoke(sp => throw exception, new AnonymousIdentity(), new TenantId(111))); + + A.CallTo(() => _exceptionLogger.LogException(A.That.IsEqualTo(exception))) + .MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task LogsButDoesNotHandleExceptionsAsync() + { + var exception = new Exception(); + + await _sut.BootAsync(); + await Assert.ThrowsAsync(() => + _sut.AsyncInvoker.InvokeAsync(sp => throw exception, new AnonymousIdentity(), new TenantId(111))); + + A.CallTo(() => _exceptionLogger.LogException(A.That.IsEqualTo(exception))) + .MustHaveHappenedOnceExactly(); + } } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs new file mode 100644 index 00000000..729f7479 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs @@ -0,0 +1,106 @@ +using System; +using Backend.Fx.Patterns.DependencyInjection; +using Xunit; +using Xunit.Abstractions; + +namespace Backend.Fx.Tests.Patterns.DependencyInjection +{ + public class TheOperation : TestWithLogging + { + public TheOperation(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void CanCancel() + { + var sut = new Operation(); + sut.Begin(); + sut.Cancel(); + } + + [Fact] + public void CanComplete() + { + var sut = new Operation(); + sut.Begin(); + sut.Complete(); + } + + [Fact] + public void CannotCompleteCanceled() + { + var sut = new Operation(); + sut.Begin(); + sut.Cancel(); + Assert.Throws(() => sut.Complete()); + } + + [Fact] + public void CannotBeginCanceled() + { + var sut = new Operation(); + sut.Begin(); + sut.Cancel(); + Assert.Throws(() => sut.Begin()); + } + + [Fact] + public void CannotCancelNew() + { + var sut = new Operation(); + Assert.Throws(() => sut.Cancel()); + } + + [Fact] + public void CannotCompleteNew() + { + var sut = new Operation(); + Assert.Throws(() => sut.Complete()); + } + + [Fact] + public void CannotCancelCompleted() + { + var sut = new Operation(); + sut.Begin(); + sut.Complete(); + Assert.Throws(() => sut.Cancel()); + } + + [Fact] + public void CannotBeginCompleted() + { + var sut = new Operation(); + sut.Begin(); + sut.Complete(); + Assert.Throws(() => sut.Begin()); + } + + [Fact] + public void CannotBeginTwice() + { + var sut = new Operation(); + sut.Begin(); + Assert.Throws(() => sut.Begin()); + } + + [Fact] + public void CannotCompleteTwice() + { + var sut = new Operation(); + sut.Begin(); + sut.Complete(); + Assert.Throws(() => sut.Complete()); + } + + [Fact] + public void CannotCancelTwice() + { + var sut = new Operation(); + sut.Begin(); + sut.Cancel(); + Assert.Throws(() => sut.Cancel()); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs new file mode 100644 index 00000000..e433a134 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs @@ -0,0 +1,29 @@ +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.Tests.Patterns.IdGeneration +{ + public class InMemorySequence : ISequence + { + private int _currentValue = 1; + + public InMemorySequence(int increment = 1) + { + Increment = increment; + } + + public void EnsureSequence() + { } + + public int GetNextValue() + { + lock (this) + { + int nextValue = _currentValue; + _currentValue += Increment; + return nextValue; + } + } + + public int Increment { get; } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs index 7f029538..2e96d569 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs @@ -7,9 +7,9 @@ namespace Backend.Fx.Tests.Patterns.IdGeneration { public class TheHiLoIdGenerator : TestWithLogging { - private readonly HiLoIdGenerator _sut = new InMemoryHiLoIdGenerator(1, 100); + private readonly HiLoIdGenerator _sut = new SequenceHiLoIdGenerator(new InMemorySequence(100)); - private class IdConsument + private class IdConsumer { public int[] Ids { get; private set; } @@ -23,18 +23,18 @@ public void GetIds(int count, IIdGenerator idGenerator) [Fact] public void AllowsMultipleThreadsToGetIds() { - const int consumentCount = 50; - const int idCountPerConsument = 1000; - var idConsuments = new IdConsument[consumentCount]; + const int consumerCount = 50; + const int idCountPerConsumer = 1000; + var idConsumers = new IdConsumer[consumerCount]; - for (var i = 0; i < consumentCount; i++) idConsuments[i] = new IdConsument(); + for (var i = 0; i < consumerCount; i++) idConsumers[i] = new IdConsumer(); - idConsuments.AsParallel().ForAll(idConsument => { idConsument.GetIds(idCountPerConsument, _sut); }); + idConsumers.AsParallel().ForAll(idConsumer => { idConsumer.GetIds(idCountPerConsumer, _sut); }); - var allIds = idConsuments.SelectMany(idConsument => idConsument.Ids).ToArray(); - Assert.Equal(consumentCount * idCountPerConsument, allIds.Length); - Assert.Equal(consumentCount * idCountPerConsument, allIds.Distinct().Count()); - Assert.Equal(consumentCount * idCountPerConsument + 1, _sut.NextId()); + var allIds = idConsumers.SelectMany(idConsumer => idConsumer.Ids).ToArray(); + Assert.Equal(consumerCount * idCountPerConsumer, allIds.Length); + Assert.Equal(consumerCount * idCountPerConsumer, allIds.Distinct().Count()); + Assert.Equal(allIds.Max() + 1, _sut.NextId()); } [Fact] @@ -47,29 +47,4 @@ public TheHiLoIdGenerator(ITestOutputHelper output) : base(output) { } } - - public class InMemoryHiLoIdGenerator : HiLoIdGenerator - { - private readonly object _synclock = new object(); - private int _nextBlockStart; - - public InMemoryHiLoIdGenerator(int start, int increment) - { - _nextBlockStart = start; - BlockSize = increment; - } - - protected override int BlockSize { get; } - - protected override int GetNextBlockStart() - { - lock (_synclock) - { - // this simulates the behavior of a SQL sequence for example - var result = _nextBlockStart; - _nextBlockStart += BlockSize; - return result; - } - } - } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs new file mode 100644 index 00000000..a503b1ee --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs @@ -0,0 +1,50 @@ +using System.Linq; +using Backend.Fx.Patterns.IdGeneration; +using Xunit; +using Xunit.Abstractions; + +namespace Backend.Fx.Tests.Patterns.IdGeneration +{ + public class TheSequenceIdGenerator : TestWithLogging + { + private readonly IIdGenerator _sut = new SequenceIdGenerator(new InMemorySequence()); + + public TheSequenceIdGenerator(ITestOutputHelper output) : base(output) + { + } + + private class IdConsumer + { + public int[] Ids { get; private set; } + + public void GetIds(int count, IIdGenerator idGenerator) + { + Ids = new int[count]; + for (var i = 0; i < count; i++) Ids[i] = idGenerator.NextId(); + } + } + + [Fact] + public void AllowsMultipleThreadsToGetIds() + { + const int consumerCount = 50; + const int idCountPerConsumer = 1000; + var idConsumers = new IdConsumer[consumerCount]; + + for (var i = 0; i < consumerCount; i++) idConsumers[i] = new IdConsumer(); + + idConsumers.AsParallel().ForAll(idConsumer => { idConsumer.GetIds(idCountPerConsumer, _sut); }); + + var allIds = idConsumers.SelectMany(idConsumer => idConsumer.Ids).ToArray(); + Assert.Equal(consumerCount * idCountPerConsumer, allIds.Length); + Assert.Equal(consumerCount * idCountPerConsumer, allIds.Distinct().Count()); + Assert.Equal(allIds.Max() + 1, _sut.NextId()); + } + + [Fact] + public void StartsWithInitialValueAndCountsUp() + { + for (var i = 1; i < 1000; i++) Assert.Equal(i, _sut.NextId()); + } + } +} \ No newline at end of file From 4ad62fdb6c6fe55bc85335080114176d2178b46b Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 11:50:46 -0300 Subject: [PATCH 08/51] tests on net6.0 --- Backend.Fx.sln | 7 +++++ .../Backend.Fx.AspNetCore.csproj | 2 +- .../Backend.Fx.AspNetCore.Tests.csproj | 3 ++- .../TheBackendFxMvcApplication.cs | 1 + .../TheMultiTenantApplication.cs | 1 + ...Backend.Fx.EfCore5Persistence.Tests.csproj | 4 +-- .../TheDbApplicationWithEfCore.cs | 3 +-- .../TheDbContext.cs | 2 +- .../TheRepositoryOfComposedAggregate.cs | 2 +- .../TheRepositoryOfPlainAggregate.cs | 2 +- ...Backend.Fx.EfCore6Persistence.Tests.csproj | 1 + .../TheDbApplicationWithEfCore.cs | 1 + .../TheDbContext.cs | 1 + .../TheRepositoryOfComposedAggregate.cs | 1 + .../TheRepositoryOfPlainAggregate.cs | 1 + .../Backend.Fx.RabbitMq.Tests.csproj | 3 ++- .../TheRabbitMqChannel.cs | 1 + .../TheRabbitMqMessageBus.cs | 1 + .../Backend.Fx.TestUtil.csproj | 18 +++++++++++++ .../CompositionRootType.cs | 26 +++++++++++++++++++ .../TestWithLogging.cs | 2 +- .../Backend.Fx.Tests/Backend.Fx.Tests.csproj | 5 ++-- .../BuildingBlocks/TheAggregateRoot.cs | 1 + .../BuildingBlocks/TheRepository.cs | 1 + .../BuildingBlocks/TheValueObject.cs | 1 + .../ConfigurationSettings/TheSetting.cs | 1 + .../TheSettingSerializerFactory.cs | 1 + .../TheSettingsService.cs | 1 + .../Authentication/TheAnonymousIdentity.cs | 1 + .../TheCurrentIdentityHolder.cs | 1 + .../Authentication/TheSystemIdentity.cs | 1 + .../DateAndTime/TheAdjustableClock.cs | 1 + .../Environment/DateAndTime/TheFrozenClock.cs | 1 + .../Environment/DateAndTime/TheWallClock.cs | 1 + ...TheAllTenantBackendFxApplicationInvoker.cs | 1 + .../MultiTenancy/TheCurrentTenantIdHolder.cs | 1 + .../TheSingleTenantApplication.cs | 1 + .../Environment/MultiTenancy/TheTenant.cs | 1 + .../Environment/MultiTenancy/TheTenantId.cs | 1 + .../MultiTenancy/TheTenantService.cs | 1 + .../Exceptions/TheNotFoundException.cs | 1 + .../TheUnprocessableExceptionBuilder.cs | 1 + .../Extensions/TheDateTimeEx.cs | 1 + .../Extensions/TheEnumerableEx.cs | 1 + .../Extensions/TheStringEnumUtil.cs | 1 + .../TheAllowAllImplementation.cs | 1 + .../TheAuthorizingApplication.cs | 1 + .../Authorization/TheDenyAllImplementation.cs | 1 + .../TheDataGenerationContext.cs | 1 + .../TheGenerateDataOnBootDecorator.cs | 1 + .../DataGeneration/TheInitialDataGenerator.cs | 1 + .../CompositionRootType.cs | 8 ------ .../TheBackendFxApplication.cs | 1 + .../TheBackendFxApplicationAsyncInvoker.cs | 1 + .../TheBackendFxApplicationInvoker.cs | 1 + .../TheBackendFxDbApplication.cs | 1 + .../DependencyInjection/TheCorrelation.cs | 1 + .../DependencyInjection/TheCurrentTHolder.cs | 1 + .../DependencyInjection/TheDomainModule.cs | 1 + .../DependencyInjection/TheOperation.cs | 1 + ...uentializingBackendFxApplicationInvoker.cs | 1 + .../Domain/TheEventAggregator.cs | 1 + .../TheInMemoryMessageBusChannel.cs | 1 + .../Integration/TheMessageBus.cs | 1 + .../Integration/TheMessageBusApplication.cs | 1 + .../Integration/TheMessageBusScope.cs | 1 + .../IdGeneration/TheHiLoIdGenerator.cs | 1 + .../TheSequenceHiLoIdGenerator.cs | 1 + .../IdGeneration/TheSequenceIdGenerator.cs | 1 + .../Patterns/Jobs/TheApplicationWithJobs.cs | 1 + .../TheDbTransactionOperationDecorator.cs | 1 + .../Transactions/TheReadonlyDecorator.cs | 1 + .../RandomData/TheGenerators.cs | 1 + tests/Backend.Fx.Tests/TestHelpers.cs | 13 +--------- 74 files changed, 125 insertions(+), 34 deletions(-) create mode 100644 tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj create mode 100644 tests/Backend.Fx.TestUtil/CompositionRootType.cs rename tests/{Backend.Fx.Tests => Backend.Fx.TestUtil}/TestWithLogging.cs (96%) delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs diff --git a/Backend.Fx.sln b/Backend.Fx.sln index 0388ec29..49871acf 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -58,6 +58,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.EfCore6Persisten EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.MicrosoftDependencyInjection", "src\implementations\Backend.Fx.MicrosoftDependencyInjection\Backend.Fx.MicrosoftDependencyInjection.csproj", "{B4791DB0-F8DD-4248-86CB-407E46F55B13}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.TestUtil", "tests\Backend.Fx.TestUtil\Backend.Fx.TestUtil.csproj", "{3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -119,6 +121,10 @@ Global {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Debug|Any CPU.Build.0 = Debug|Any CPU {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Release|Any CPU.ActiveCfg = Release|Any CPU {B4791DB0-F8DD-4248-86CB-407E46F55B13}.Release|Any CPU.Build.0 = Release|Any CPU + {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -145,6 +151,7 @@ Global {38034961-CE3B-4286-A9EB-496DECA39632} = {ADC35CAD-F5B1-42B6-A0CC-B96974C11F11} {E50D7E8D-D012-4683-BA05-C877BAA25230} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {B4791DB0-F8DD-4248-86CB-407E46F55B13} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} + {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969} diff --git a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj index 2f836e89..ab0f87cb 100644 --- a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj +++ b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 Library true snupkg diff --git a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj b/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj index 088a30d3..935d99dd 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj +++ b/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 false @@ -33,6 +33,7 @@ + diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs index 711dbd90..e3038173 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Backend.Fx.AspNetCore.Tests.SampleApp.Domain; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs index 1a52f798..932f6711 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs @@ -3,6 +3,7 @@ using Backend.Fx.AspNetCore.MultiTenancy; using Backend.Fx.RandomData; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj index 6916f6ce..36811c00 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 @@ -28,7 +28,7 @@ - + diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs index db402835..058abae4 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -13,8 +13,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; -using Backend.Fx.Tests; -using Backend.Fx.Tests.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs index 4ce3580b..ccd75ee1 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs @@ -1,7 +1,7 @@ using System.Linq; using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; using Backend.Fx.EfCore5Persistence.Tests.Fixtures; -using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs index d04b13e6..f85c7c08 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -10,7 +10,7 @@ using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.IdGeneration; -using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs index d5321e83..50332417 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -7,7 +7,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj index edd2e02f..5504bb91 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj @@ -29,6 +29,7 @@ + diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs index 1e5754f6..417624a5 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -15,6 +15,7 @@ using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.Tests; using Backend.Fx.Tests.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs index 00c7a615..0ab6c1bb 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs @@ -2,6 +2,7 @@ using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs index 8c3756be..cfc99a88 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -11,6 +11,7 @@ using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs index 69a092c3..5ec95983 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -8,6 +8,7 @@ using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj b/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj index 3072ebc7..a06d02cf 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj +++ b/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false @@ -21,6 +21,7 @@ + diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs index 768aeee7..e924e200 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using RabbitMQ.Client; using RabbitMQ.Client.Events; using Xunit; diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index 05923443..12348608 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -4,6 +4,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.Tests; +using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using RabbitMQ.Client; diff --git a/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj b/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj new file mode 100644 index 00000000..5722fe9a --- /dev/null +++ b/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.1 + + + + + + + + + + + + + + diff --git a/tests/Backend.Fx.TestUtil/CompositionRootType.cs b/tests/Backend.Fx.TestUtil/CompositionRootType.cs new file mode 100644 index 00000000..beae5701 --- /dev/null +++ b/tests/Backend.Fx.TestUtil/CompositionRootType.cs @@ -0,0 +1,26 @@ +using System; +using Backend.Fx.MicrosoftDependencyInjection; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.SimpleInjectorDependencyInjection; + +namespace Backend.Fx.TestUtil +{ + public enum CompositionRootType + { + Microsoft, + SimpleInjector + } + + public static class CompositionRootTypeEx + { + public static ICompositionRoot Create(this CompositionRootType compositionRootType) + { + return compositionRootType switch + { + CompositionRootType.Microsoft => new MicrosoftCompositionRoot(), + CompositionRootType.SimpleInjector => new SimpleInjectorCompositionRoot(), + _ => throw new ArgumentException(nameof(compositionRootType)) + }; + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/TestWithLogging.cs b/tests/Backend.Fx.TestUtil/TestWithLogging.cs similarity index 96% rename from tests/Backend.Fx.Tests/TestWithLogging.cs rename to tests/Backend.Fx.TestUtil/TestWithLogging.cs index 8094c833..bfe9135b 100644 --- a/tests/Backend.Fx.Tests/TestWithLogging.cs +++ b/tests/Backend.Fx.TestUtil/TestWithLogging.cs @@ -4,7 +4,7 @@ using Xunit.Abstractions; using ILogger = Serilog.ILogger; -namespace Backend.Fx.Tests +namespace Backend.Fx.TestUtil { public abstract class TestWithLogging : IDisposable { diff --git a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj index d1d97e03..90914105 100644 --- a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj +++ b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 @@ -15,8 +15,6 @@ - - @@ -27,6 +25,7 @@ + diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs index e3080865..0190d9f4 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Backend.Fx.BuildingBlocks; using Backend.Fx.RandomData; +using Backend.Fx.TestUtil; using JetBrains.Annotations; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs index 939ef2ba..f158e027 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs @@ -4,6 +4,7 @@ using Backend.Fx.Exceptions; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.Authorization; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs index 161ba558..88cecfa6 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Backend.Fx.BuildingBlocks; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs index 435baad4..a27b0d9c 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs @@ -2,6 +2,7 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.TestUtil; using JetBrains.Annotations; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs index f24749cf..917d5dca 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.ConfigurationSettings; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs index 5a33811e..54d96c9c 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs @@ -6,6 +6,7 @@ using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs b/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs index 3c78ac09..4ba18001 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs +++ b/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs @@ -1,4 +1,5 @@ using Backend.Fx.Environment.Authentication; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs b/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs index 6e0fac60..44f1c8ee 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs +++ b/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs @@ -1,4 +1,5 @@ using Backend.Fx.Environment.Authentication; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs b/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs index 94cb83b6..b870b7f5 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs +++ b/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs @@ -1,4 +1,5 @@ using Backend.Fx.Environment.Authentication; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs index 9e0cf2a0..463719c0 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs +++ b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs index a4be9bb8..4ef74b58 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs +++ b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs index efe0b818..dc23ae47 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs +++ b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs @@ -3,6 +3,7 @@ using System.Threading; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Extensions; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs index 1673424c..97c8b55c 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs @@ -3,6 +3,7 @@ using System.Security.Principal; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs index 27c81ba0..18ff5e31 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs @@ -1,4 +1,5 @@ using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs index 9ee159c1..0b0a4a4f 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs @@ -4,6 +4,7 @@ using Backend.Fx.Logging; using Backend.Fx.MicrosoftDependencyInjection; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs index 1f115c82..d46b363f 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs index 2b72a093..df74c6b6 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs index 299158de..aac1ed46 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs @@ -3,6 +3,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Exceptions/TheNotFoundException.cs b/tests/Backend.Fx.Tests/Exceptions/TheNotFoundException.cs index 17205c5a..0c9af561 100644 --- a/tests/Backend.Fx.Tests/Exceptions/TheNotFoundException.cs +++ b/tests/Backend.Fx.Tests/Exceptions/TheNotFoundException.cs @@ -1,5 +1,6 @@ using Backend.Fx.Exceptions; using Backend.Fx.Tests.BuildingBlocks; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs b/tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs index 7dfeb17d..7d7ffc15 100644 --- a/tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs +++ b/tests/Backend.Fx.Tests/Exceptions/TheUnprocessableExceptionBuilder.cs @@ -1,5 +1,6 @@ using Backend.Fx.Exceptions; using Backend.Fx.Tests.BuildingBlocks; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs b/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs index a8227ce6..b155ed57 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Extensions; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs b/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs index e083f847..69709c72 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Backend.Fx.Extensions; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs b/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs index 457c8b50..a4a466b3 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Extensions; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs index e00e3d5b..e2255d97 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs @@ -1,6 +1,7 @@ using System.Linq; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Tests.BuildingBlocks; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs index 548282ad..a9dcac4d 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs @@ -10,6 +10,7 @@ using Backend.Fx.Patterns.Jobs; using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.Jobs; +using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs index 8151b29b..4d8857f9 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs @@ -1,6 +1,7 @@ using System.Linq; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Tests.BuildingBlocks; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index 4fe69036..41e46acf 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -5,6 +5,7 @@ using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs index ce951941..4c061ff1 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs @@ -3,6 +3,7 @@ using Backend.Fx.Hacking; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs index 263a9ddc..72e6db68 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs @@ -1,4 +1,5 @@ using Backend.Fx.Patterns.DataGeneration; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs deleted file mode 100644 index a280d823..00000000 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/CompositionRootType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Backend.Fx.Tests.Patterns.DependencyInjection -{ - public enum CompositionRootType - { - Microsoft, - SimpleInjector - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index 946f883d..2d63f86c 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -10,6 +10,7 @@ using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.Tests.Patterns.Authorization; +using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs index 2af5a01b..735adcd1 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs @@ -4,6 +4,7 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs index fc4702e9..57dbaa31 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs @@ -3,6 +3,7 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs index 3df4a38a..1fd79e5a 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs @@ -2,6 +2,7 @@ using Backend.Fx.Environment.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs index cd104f90..02532331 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs index a72ca0bd..b2e1866b 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs @@ -1,5 +1,6 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs index 22675637..7437c64f 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs @@ -8,6 +8,7 @@ using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs index 729f7479..43299186 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index 9ca44147..4b861760 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -7,6 +7,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs index 0b834da7..3d2b1353 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs index 022d346e..30dcace9 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs index e30313bc..cff54ed4 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs @@ -6,6 +6,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.TestUtil; using FakeItEasy; using JetBrains.Annotations; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs index da2eba9a..ab76a8af 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs @@ -3,6 +3,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.Tests.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs index eb5109a9..6cd753e9 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs @@ -1,6 +1,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs index 2e96d569..54a3d1bf 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs @@ -1,5 +1,6 @@ using System.Linq; using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs index 49a4f55f..074efa55 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs @@ -1,4 +1,5 @@ using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs index a503b1ee..27c9d1b5 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs @@ -1,5 +1,6 @@ using System.Linq; using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs index edd20131..f0aaad05 100644 --- a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -7,6 +7,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.Jobs; using Backend.Fx.Tests.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using FluentScheduler; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs b/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs index 1f1998f9..224274c8 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs @@ -2,6 +2,7 @@ using System.Data; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs b/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs index c8a399c9..c54a5865 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs @@ -1,5 +1,6 @@ using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/RandomData/TheGenerators.cs b/tests/Backend.Fx.Tests/RandomData/TheGenerators.cs index abd38469..44d60064 100644 --- a/tests/Backend.Fx.Tests/RandomData/TheGenerators.cs +++ b/tests/Backend.Fx.Tests/RandomData/TheGenerators.cs @@ -1,5 +1,6 @@ using System.Linq; using Backend.Fx.RandomData; +using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/TestHelpers.cs b/tests/Backend.Fx.Tests/TestHelpers.cs index cfced19c..a78ae726 100644 --- a/tests/Backend.Fx.Tests/TestHelpers.cs +++ b/tests/Backend.Fx.Tests/TestHelpers.cs @@ -6,16 +6,5 @@ namespace Backend.Fx.Tests { - public static class TestHelpers - { - public static ICompositionRoot Create(this CompositionRootType compositionRootType) - { - return compositionRootType switch - { - CompositionRootType.Microsoft => new MicrosoftCompositionRoot(), - CompositionRootType.SimpleInjector => new SimpleInjectorCompositionRoot(), - _ => throw new ArgumentException(nameof(compositionRootType)) - }; - } - } + } \ No newline at end of file From db6744b22c22dd4bf469e9fac697fc8bf413f5ea Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 11:57:34 -0300 Subject: [PATCH 09/51] bumps --- src/abstractions/Backend.Fx/Backend.Fx.csproj | 4 ++-- .../Backend.Fx.AspNetCore.csproj | 4 ++-- .../Backend.Fx.EfCore5Persistence.csproj | 2 +- .../Backend.Fx.EfCore6Persistence.csproj | 2 +- .../Backend.Fx.InMemoryPersistence.csproj | 2 +- .../Backend.Fx.RabbitMq.csproj | 2 +- ...x.SimpleInjectorDependencyInjection.csproj | 2 +- .../Backend.Fx.AspNetCore.Tests.csproj | 16 ++++++------- ...Backend.Fx.EfCore5Persistence.Tests.csproj | 21 +++++++++-------- ...Backend.Fx.EfCore6Persistence.Tests.csproj | 23 +++++++++++-------- .../Backend.Fx.RabbitMq.Tests.csproj | 11 +++++---- .../Backend.Fx.Tests/Backend.Fx.Tests.csproj | 17 ++++++++------ 12 files changed, 60 insertions(+), 46 deletions(-) diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index ed369fa8..0f0a4ac1 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -30,7 +30,7 @@ - + @@ -39,7 +39,7 @@ - + \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj index ab0f87cb..eb1c529b 100644 --- a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj +++ b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj @@ -29,8 +29,8 @@ - - + + diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj b/src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj index a89f74b3..dffabf11 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Backend.Fx.EfCore5Persistence.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj b/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj index 1a9aecf1..32bada34 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Backend.Fx.EfCore6Persistence.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj b/src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj index 993c7626..3c135dc6 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj +++ b/src/implementations/Backend.Fx.InMemoryPersistence/Backend.Fx.InMemoryPersistence.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj b/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj index 5c563d70..393427ce 100644 --- a/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj +++ b/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj index 133cea5a..69a5ffd5 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/Backend.Fx.SimpleInjectorDependencyInjection.csproj @@ -25,7 +25,7 @@ - + diff --git a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj b/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj index 935d99dd..ac43c0d1 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj +++ b/tests/Backend.Fx.AspNetCore.Tests/Backend.Fx.AspNetCore.Tests.csproj @@ -7,21 +7,21 @@ - - - - - + + + + + - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj index 36811c00..748d32d6 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj @@ -5,23 +5,26 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj index 5504bb91..e1f8888b 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj @@ -5,23 +5,28 @@ - + - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj b/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj index a06d02cf..d434d1fc 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj +++ b/tests/Backend.Fx.RabbitMq.Tests/Backend.Fx.RabbitMq.Tests.csproj @@ -7,12 +7,15 @@ - + - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj index 90914105..89a48258 100644 --- a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj +++ b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj @@ -5,19 +5,22 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - + + + + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From a610749ddd6e4f4a1fcfd0404f4ceca210e513f0 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 12:36:52 -0300 Subject: [PATCH 10/51] bump nuke.build --- .gitignore | 5 +- .nuke | 1 - .nuke/build.schema.json | 116 ++++++++++++++++++++++++++++++++++++++++ .nuke/parameters.json | 4 ++ build.cmd | 2 +- build.ps1 | 8 +-- build.sh | 4 +- build/Build.cs | 1 - build/_build.csproj | 5 +- 9 files changed, 134 insertions(+), 12 deletions(-) delete mode 100644 .nuke create mode 100644 .nuke/build.schema.json create mode 100644 .nuke/parameters.json diff --git a/.gitignore b/.gitignore index ec39d478..d7fc5cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ _ReSharper*/ # JetBrains Rider .idea/ -*.sln.iml \ No newline at end of file +*.sln.iml + +#nuke.build +.nuke/temp diff --git a/.nuke b/.nuke deleted file mode 100644 index 3287f53f..00000000 --- a/.nuke +++ /dev/null @@ -1 +0,0 @@ -Backend.Fx.sln \ No newline at end of file diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 00000000..924c5192 --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,116 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Build Schema", + "$ref": "#/definitions/build", + "definitions": { + "build": { + "type": "object", + "properties": { + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "type": "string", + "description": "Host for execution. Default is 'automatic'", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Pack", + "Publish", + "Restore", + "Test" + ] + } + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Pack", + "Publish", + "Restore", + "Test" + ] + } + }, + "Verbosity": { + "type": "string", + "description": "Logging verbosity during build execution. Default is 'Normal'", + "enum": [ + "Minimal", + "Normal", + "Quiet", + "Verbose" + ] + } + } + } + } +} \ No newline at end of file diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 00000000..b1a9a717 --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,4 @@ +{ + "$schema": "./build.schema.json", + "Solution": "Backend.Fx.sln" +} \ No newline at end of file diff --git a/build.cmd b/build.cmd index 8b8b89dc..b08cc590 100755 --- a/build.cmd +++ b/build.cmd @@ -4,4 +4,4 @@ :; exit $? @ECHO OFF -powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %* +powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* diff --git a/build.ps1 b/build.ps1 index e5c8a44e..8c52d631 100644 --- a/build.ps1 +++ b/build.ps1 @@ -14,7 +14,7 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent ########################################################################### $BuildProjectFile = "$PSScriptRoot\build\_build.csproj" -$TempDirectory = "$PSScriptRoot\\.tmp" +$TempDirectory = "$PSScriptRoot\\.nuke\temp" $DotNetGlobalFile = "$PSScriptRoot\\global.json" $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" @@ -56,14 +56,14 @@ else { # Install by channel or version $DotNetDirectory = "$TempDirectory\dotnet-win" if (!(Test-Path variable:DotNetVersion)) { - ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } } else { - ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } } $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" } -Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" +Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } diff --git a/build.sh b/build.sh index 3d526432..1f3ba09e 100755 --- a/build.sh +++ b/build.sh @@ -10,7 +10,7 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) ########################################################################### BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj" -TEMP_DIRECTORY="$SCRIPT_DIR//.tmp" +TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" @@ -56,7 +56,7 @@ else export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" fi -echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" +echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" diff --git a/build/Build.cs b/build/Build.cs index 8777529b..93ba7f2e 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -11,7 +11,6 @@ using static Nuke.Common.IO.FileSystemTasks; using static Nuke.Common.Tools.DotNet.DotNetTasks; -[CheckBuildProjectConfigurations] [ShutdownDotNetAfterServerBuild] class Build : NukeBuild { diff --git a/build/_build.csproj b/build/_build.csproj index 565047d2..665a209d 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -2,15 +2,16 @@ Exe - net5.0 + net6.0 CS0649;CS0169 .. .. + 1 - + From e822db1a621d19532c75013dde6322bdffd5ff6e Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 12:41:49 -0300 Subject: [PATCH 11/51] r# greening --- .../TheBackendFxMvcApplication.cs | 1 - .../TheMultiTenantApplication.cs | 1 - .../TheDbApplicationWithEfCore.cs | 2 -- tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs | 1 - .../TheRepositoryOfComposedAggregate.cs | 1 - .../TheRepositoryOfPlainAggregate.cs | 1 - tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs | 1 - tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs | 1 - .../Patterns/Authorization/TheAuthorizingApplication.cs | 4 ---- .../Patterns/DataGeneration/TheDataGenerationContext.cs | 1 - .../Integration/TheMessageBusApplication.cs | 1 - .../Patterns/Jobs/TheApplicationWithJobs.cs | 1 - tests/Backend.Fx.Tests/TestHelpers.cs | 6 ------ 13 files changed, 22 deletions(-) diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs index e3038173..4073e9dd 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/TheBackendFxMvcApplication.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Threading.Tasks; using Backend.Fx.AspNetCore.Tests.SampleApp.Domain; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using Newtonsoft.Json; using Xunit; diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs index 932f6711..1057d5e5 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Backend.Fx.AspNetCore.MultiTenancy; using Backend.Fx.RandomData; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using Newtonsoft.Json; using Xunit; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs index 417624a5..b601797a 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -13,8 +13,6 @@ using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; -using Backend.Fx.Tests; -using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Data.Sqlite; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs index 0ab6c1bb..f30c7a64 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs @@ -1,7 +1,6 @@ using System.Linq; using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using Microsoft.EntityFrameworkCore; using Xunit; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs index cfc99a88..71e24c79 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -10,7 +10,6 @@ using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.IdGeneration; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs index 5ec95983..b767acda 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -7,7 +7,6 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs index e924e200..53d8f454 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs @@ -1,7 +1,6 @@ using System.Linq; using System.Text; using System.Threading; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using RabbitMQ.Client; using RabbitMQ.Client.Events; diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index 12348608..3dab75d1 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -3,7 +3,6 @@ using System.Threading; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.Tests; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs index a9dcac4d..6b4c9e82 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs @@ -6,12 +6,8 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.Patterns.Jobs; using Backend.Fx.Tests.Patterns.DependencyInjection; -using Backend.Fx.Tests.Patterns.Jobs; using Backend.Fx.TestUtil; -using FakeItEasy; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index 41e46acf..14e2a01f 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -4,7 +4,6 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs index ab76a8af..02fb40a9 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs @@ -2,7 +2,6 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Integration; -using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs index f0aaad05..c2615259 100644 --- a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -6,7 +6,6 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.Jobs; -using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using FluentScheduler; diff --git a/tests/Backend.Fx.Tests/TestHelpers.cs b/tests/Backend.Fx.Tests/TestHelpers.cs index a78ae726..22a1ff23 100644 --- a/tests/Backend.Fx.Tests/TestHelpers.cs +++ b/tests/Backend.Fx.Tests/TestHelpers.cs @@ -1,9 +1,3 @@ -using System; -using Backend.Fx.MicrosoftDependencyInjection; -using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.SimpleInjectorDependencyInjection; -using Backend.Fx.Tests.Patterns.DependencyInjection; - namespace Backend.Fx.Tests { From 915625e78e42c5939ddb587b8356bf50ae1f3395 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 13:41:19 -0300 Subject: [PATCH 12/51] test hardening --- .../Patterns/Jobs/TheApplicationWithJobs.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs index c2615259..e056de97 100644 --- a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -50,8 +50,9 @@ public async Task RunsJobs(CompositionRootType compositionRootType) sut.Dispose(); - Assert.True(SayHelloJob.Invocations.Count > 35); - Assert.True(SayHelloJob.Invocations.Count < 45); + // now plus 3 x 300ms during a second, four tenants = 16 invocations + Assert.Equal(16, SayHelloJob.Invocations.Count); + } private class FluentSchedulerApplication : ApplicationWithJobs @@ -84,7 +85,7 @@ public JobSchedule(IBackendFxApplication application, ITenantIdProvider tenantId Schedule(() => _invoker.Invoke(sp => sp.GetRequiredService().Run())) .ToRunNow() - .AndEvery(interval: 111) + .AndEvery(interval: 300) .Milliseconds(); } } From 780cd83f8883f13b2aaefea85e98f46886b6a2cf Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 19:54:23 -0300 Subject: [PATCH 13/51] resurrect SimpleInjectorModule --- .../SimpleInjectorCompositionRoot.cs | 15 +++++-------- .../SimpleInjectorModule.cs | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index 80c8257b..80a69e56 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -20,7 +20,6 @@ public class SimpleInjectorCompositionRoot : CompositionRoot { private static readonly ILogger Logger = Log.Create(); private readonly Lazy _container; - private readonly ScopedLifestyle _scopedLifestyle; private readonly IList _services = new List(); private readonly IList _decorators = new List(); private readonly IList _serviceCollections = new List(); @@ -38,13 +37,13 @@ public SimpleInjectorCompositionRoot( ScopedLifestyle scopedLifestyle) { Logger.LogInformation("Initializing SimpleInjector"); - _scopedLifestyle = scopedLifestyle; + ScopedLifestyle = scopedLifestyle; _container = new Lazy(() => { Logger.LogInformation("Building SimpleInjector Container"); var container = new Container(); container.Options.LifestyleSelectionBehavior = lifestyleBehavior; - container.Options.DefaultScopedLifestyle = _scopedLifestyle; + container.Options.DefaultScopedLifestyle = ScopedLifestyle; foreach (var serviceDescriptor in _services) { @@ -109,6 +108,9 @@ public SimpleInjectorCompositionRoot( }); } + public ScopedLifestyle ScopedLifestyle { get; } + + public Container Container => _container.Value; #region ICompositionRoot implementation @@ -175,12 +177,7 @@ public override IServiceScope BeginScope() return _container.Value.CreateScope(); } - public override IServiceProvider ServiceProvider => _container.Value; - - public Scope GetCurrentScope() - { - return _scopedLifestyle.GetCurrentScope(_container.Value); - } + public override IServiceProvider ServiceProvider => Container; /// /// A behavior that defaults to scoped life style for injected instances diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs new file mode 100644 index 00000000..3d1f699b --- /dev/null +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs @@ -0,0 +1,22 @@ +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.Logging; +using SimpleInjector; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Backend.Fx.SimpleInjectorDependencyInjection +{ + public abstract class SimpleInjectorModule : IModule + { + private static readonly ILogger Logger = Log.Create(); + + public virtual void Register(ICompositionRoot compositionRoot) + { + Logger.LogDebug("Registering {Module}", GetType().Name); + var simpleInjectorCompositionRoot = (SimpleInjectorCompositionRoot) compositionRoot; + Register(simpleInjectorCompositionRoot.Container, simpleInjectorCompositionRoot.ScopedLifestyle); + } + + protected abstract void Register(Container container, ScopedLifestyle scopedLifestyle); + } +} \ No newline at end of file From 90ecaea8215c4ee9841c3a5e19c6d50eb31fa54e Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 20:18:25 -0300 Subject: [PATCH 14/51] fix flaky test --- .../Patterns/Jobs/TheApplicationWithJobs.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs index e056de97..43470ef8 100644 --- a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -51,7 +51,7 @@ public async Task RunsJobs(CompositionRootType compositionRootType) sut.Dispose(); // now plus 3 x 300ms during a second, four tenants = 16 invocations - Assert.Equal(16, SayHelloJob.Invocations.Count); + Assert.True(SayHelloJob.Invocations.Count >= 12, $"Recorded {SayHelloJob.Invocations.Count} job invocations, while at least 12 were expected"); } @@ -76,14 +76,12 @@ public override async Task BootAsync(CancellationToken cancellationToken = defau private class JobSchedule : Registry { - private readonly AllTenantBackendFxApplicationInvoker _invoker; - public JobSchedule(IBackendFxApplication application, ITenantIdProvider tenantIdProvider) { - _invoker = new AllTenantBackendFxApplicationInvoker(tenantIdProvider, application.Invoker); + var invoker = new AllTenantBackendFxApplicationInvoker(tenantIdProvider, application.Invoker); NonReentrantAsDefault(); - Schedule(() => _invoker.Invoke(sp => sp.GetRequiredService().Run())) + Schedule(() => invoker.Invoke(sp => sp.GetRequiredService().Run())) .ToRunNow() .AndEvery(interval: 300) .Milliseconds(); From b1107f72e700a6341909aaad9f074bcde3af82d2 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 15 Jun 2022 22:46:46 -0300 Subject: [PATCH 15/51] enforce Utc kind --- .../DateAndTime/AdjustableClock.cs | 5 +++++ .../RandomData/TestPersonGenerator.cs | 2 +- .../Backend.Fx/RandomData/TestRandom.cs | 4 ++-- .../BuildingBlocks/TheAggregateRoot.cs | 20 +++++++++---------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs index f9572972..506ae3fa 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs @@ -22,6 +22,11 @@ public AdjustableClock(IClock clockImplementation) public void OverrideUtcNow(DateTime utcNow) { Logger.LogTrace("Adjusting clock to {UtcNow}", utcNow); + if (utcNow.Kind != DateTimeKind.Utc) + { + utcNow = new DateTime(utcNow.Ticks, DateTimeKind.Utc); + } + _overriddenUtcNow = utcNow; } diff --git a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs index 2094a8dd..7e623e20 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs @@ -34,7 +34,7 @@ protected override TestPerson Next() Names.Family.Random(), _random.Next(100) < 20 ? "Dr." : "", isFemale ? TestPerson.Genders.Female : TestPerson.Genders.Male, - DateTime.Now.AddDays(-_random.Next(MinimumAgeInDays, MaximumAgeInDays)).Date); + DateTime.UtcNow.AddDays(-_random.Next(MinimumAgeInDays, MaximumAgeInDays)).Date); } while (EnforceUniqueNames && _uniqueNames.Contains($"{generated.FirstName}{generated.LastName}")); if (EnforceUniqueNames) diff --git a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs index a26a29d9..eba65ed5 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs @@ -40,8 +40,8 @@ public static double NextDouble() public static DateTime RandomDateTime(int rangeDays) { return rangeDays < 0 - ? DateTime.Now.AddDays(-Next(-rangeDays)).AddSeconds(-Next(100000)) - : DateTime.Now.AddDays(Next(rangeDays)).AddSeconds(-Next(100000)); + ? DateTime.UtcNow.AddDays(-Next(-rangeDays)).AddSeconds(-Next(100000)) + : DateTime.UtcNow.AddDays(Next(rangeDays)).AddSeconds(-Next(100000)); } public static decimal RandomDecimal(int min = 0, int max = 999999) diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs index 0190d9f4..991733a1 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs @@ -44,7 +44,7 @@ public TestEntity(string name, TestAggregateRoot parent) [Fact] public void ChangedByPropertyIsChoppedAt100Chars() { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; var sut = new TestAggregateRoot(_nextId++, "gaga"); var moreThanHundred = Letters.RandomLowerCase(110); sut.SetModifiedProperties(moreThanHundred, now); @@ -54,7 +54,7 @@ public void ChangedByPropertyIsChoppedAt100Chars() [Fact] public void ChangedByPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetModifiedProperties("me", now); Assert.Equal("me", sut.ChangedBy); @@ -64,7 +64,7 @@ public void ChangedByPropertyIsStoredCorrectly() [Fact] public void ChangedOnPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetModifiedProperties("me", now); Assert.Equal(now, sut.ChangedOn); @@ -74,7 +74,7 @@ public void ChangedOnPropertyIsStoredCorrectly() [Fact] public void CreatedByPropertyIsChoppedAt100Chars() { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; var sut = new TestAggregateRoot(_nextId++, "gaga"); var moreThanHundred = Letters.RandomLowerCase(110); sut.SetCreatedProperties(moreThanHundred, now); @@ -84,7 +84,7 @@ public void CreatedByPropertyIsChoppedAt100Chars() [Fact] public void CreatedByPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetCreatedProperties("me", now); Assert.Equal("me", sut.CreatedBy); @@ -94,7 +94,7 @@ public void CreatedByPropertyIsStoredCorrectly() [Fact] public void CreatedOnPropertyIsStoredCorrectly() { - DateTime now = DateTime.Now; + DateTime now = DateTime.UtcNow; var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetCreatedProperties("me", now); Assert.Equal(now, sut.CreatedOn); @@ -106,7 +106,7 @@ public void ThrowsGivenEmptyChangedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetModifiedProperties("", DateTime.Now)); + Assert.Throws(() => sut.SetModifiedProperties("", DateTime.UtcNow)); } [Fact] @@ -114,7 +114,7 @@ public void ThrowsGivenEmptyCreatedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetCreatedProperties("", DateTime.Now)); + Assert.Throws(() => sut.SetCreatedProperties("", DateTime.UtcNow)); } [Fact] @@ -122,7 +122,7 @@ public void ThrowsGivenNullChangedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetModifiedProperties(null, DateTime.Now)); + Assert.Throws(() => sut.SetModifiedProperties(null, DateTime.UtcNow)); } [Fact] @@ -130,7 +130,7 @@ public void ThrowsGivenNullCreatedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetCreatedProperties(null, DateTime.Now)); + Assert.Throws(() => sut.SetCreatedProperties(null, DateTime.UtcNow)); } public TheAggregateRoot(ITestOutputHelper output) : base(output) From 52b21440973a8c9641d4f3fb5acbc5d4ac625b1c Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Thu, 16 Jun 2022 09:41:13 -0300 Subject: [PATCH 16/51] fix arrayoutofrange --- .../MicrosoftCompositionRoot.cs | 10 ++++++- .../SimpleInjectorCompositionRoot.cs | 29 +++++++++++-------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs index a8b24ac9..2203095c 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -63,7 +63,15 @@ public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) public override void RegisterCollection(IEnumerable serviceDescriptors) { - foreach (var serviceDescriptor in serviceDescriptors) + var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); + + if (serviceDescriptorArray.Length == 0) + { + Logger.Warn("Skipping registration of empty collection"); + return; + } + + foreach (var serviceDescriptor in serviceDescriptorArray) { serviceDescriptor.LogDetails(Logger, "Adding"); _serviceCollection.Add(serviceDescriptor); diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index 80a69e56..0a0077d6 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -12,7 +12,6 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection { - /// /// Provides a reusable composition root assuming Simple Injector as container /// @@ -23,7 +22,7 @@ public class SimpleInjectorCompositionRoot : CompositionRoot private readonly IList _services = new List(); private readonly IList _decorators = new List(); private readonly IList _serviceCollections = new List(); - + /// /// This constructor creates a composition root that prefers scoped lifestyle /// @@ -44,11 +43,11 @@ public SimpleInjectorCompositionRoot( var container = new Container(); container.Options.LifestyleSelectionBehavior = lifestyleBehavior; container.Options.DefaultScopedLifestyle = ScopedLifestyle; - + foreach (var serviceDescriptor in _services) { serviceDescriptor.LogDetails(Logger, "Adding"); - + if (serviceDescriptor.ImplementationType != null) { container.Register( @@ -78,7 +77,7 @@ public SimpleInjectorCompositionRoot( foreach (var serviceDescriptor in _decorators) { serviceDescriptor.LogDetails(Logger, "Adding decorator"); - + container.RegisterDecorator( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, @@ -91,7 +90,7 @@ public SimpleInjectorCompositionRoot( serviceDescriptors[0].Lifetime.ToString(), serviceDescriptors[0].ServiceType.Name, $"[{string.Join(",", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))}]"); - + foreach (var serviceDescriptor in serviceDescriptors) { container.Collection.Append( @@ -103,13 +102,13 @@ public SimpleInjectorCompositionRoot( // needed to support extension method IServiceProvider.CreateScope() container.RegisterInstance(new SimpleInjectorServiceScopeFactory(container)); - + return container; }); } public ScopedLifestyle ScopedLifestyle { get; } - + public Container Container => _container.Value; #region ICompositionRoot implementation @@ -120,12 +119,12 @@ public override void Register(ServiceDescriptor serviceDescriptor) { throw new InvalidOperationException("Container has been built and cannot be changed any more."); } - + foreach (var descriptor in _services.Where(sd => sd.ServiceType == serviceDescriptor.ServiceType).ToArray()) { _services.Remove(descriptor); } - + _services.Add(serviceDescriptor); } @@ -135,7 +134,7 @@ public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) { throw new InvalidOperationException("Container has been built and cannot be changed any more."); } - + _decorators.Add(serviceDescriptor); } @@ -145,9 +144,15 @@ public override void RegisterCollection(IEnumerable serviceDe { throw new InvalidOperationException("Container has been built and cannot be changed any more."); } - + var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); + if (serviceDescriptorArray.Length == 0) + { + Logger.Warn("Skipping registration of empty collection"); + return; + } + if (serviceDescriptorArray.Select(sd => sd.ServiceType).Distinct().Count() > 1) { throw new InvalidOperationException( From e073b13927bc74c18b17b58031e9c420f64cf58d Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Thu, 16 Jun 2022 10:48:43 -0300 Subject: [PATCH 17/51] (fix) messagebus invocation --- .../EventAggregation/Integration/MessageBusApplication.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs index dc8972d7..1916c162 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -13,8 +13,11 @@ public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication appli { application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); _messageBus = messageBus; - Invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot, base.Invoker); + var invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot, base.Invoker); + _messageBus.ProvideInvoker(invoker); + Invoker = invoker; AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot, base.AsyncInvoker); + } public override async Task BootAsync(CancellationToken cancellationToken = default) From 059dbc886c5b910fe75570c8cb39fe4064730dcd Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 14:21:55 -0300 Subject: [PATCH 18/51] Make a persistence module mandatory when adding the PersistentApplication --- Backend.Fx.sln | 7 + .../Persistence/PersistentApplication.cs | 2 + .../Integration/MessageBusApplication.cs | 14 +- .../Bootstrapping/EfCorePersistenceModule.cs | 24 +-- .../DbContextExtensions.cs | 30 --- .../Backend.Fx.EfCore5Persistence/EfFlush.cs | 2 +- .../Bootstrapping/EfCorePersistenceModule.cs | 24 +-- .../DbContextExtensions.cs | 30 --- .../Backend.Fx.EfCore6Persistence/EfFlush.cs | 4 +- .../InMemorySequence.cs | 2 +- .../SampleApp/Domain/JwtService.cs | 2 +- ...Backend.Fx.EfCore5Persistence.Tests.csproj | 2 + .../DummyImpl/Domain/Blogger.cs | 23 --- .../DummyImpl/Domain/BloggerAuthorization.cs | 10 - .../DummyImpl/Persistence/BloggerMapping.cs | 8 - .../Persistence/TestDbContextFactory.cs | 17 -- .../Fixtures/DatabaseFixture.cs | 8 +- .../Fixtures/SqlServerDatabaseFixture.cs | 10 +- .../Fixtures/SqliteDatabaseFixture.cs | 10 +- .../Fixtures/TestDbSession.cs | 6 +- .../20190624150947_Initial.Designer.cs | 4 +- .../Migrations/TestDbContextModelSnapshot.cs | 6 +- .../SampleApp}/Persistence/BlogMapping.cs | 4 +- .../SampleApp/Persistence/BloggerMapping.cs | 8 + .../Persistence/SampleAppDbBootstrapper.cs | 24 +++ .../Persistence/SampleAppDbContext.cs} | 10 +- .../Persistence/SampleAppDbContextFactory.cs | 17 ++ .../Persistence/SampleAppIdGenerator.cs | 11 ++ .../SampleApp/Runtime/SampleAppBuilder.cs | 43 +++++ .../TheDbApplicationWithEfCore.cs | 175 ++++------------- .../TheDbContext.cs | 2 +- .../TheRepositoryOfComposedAggregate.cs | 72 +++---- .../TheRepositoryOfPlainAggregate.cs | 4 +- ...Backend.Fx.EfCore6Persistence.Tests.csproj | 1 + .../DbConnectionEx.cs | 1 + .../DummyImpl/Domain/Blog.cs | 37 ---- .../DummyImpl/Domain/BlogAuthorization.cs | 10 - .../DummyImpl/Domain/Post.cs | 48 ----- .../DummyImpl/Persistence/BloggerMapping.cs | 8 - .../Persistence/TestDbContextFactory.cs | 17 -- .../Fixtures/DatabaseFixture.cs | 8 +- .../Fixtures/SqlServerDatabaseFixture.cs | 10 +- .../Fixtures/SqliteDatabaseFixture.cs | 10 +- .../Fixtures/TestDbSession.cs | 6 +- .../20190624150947_Initial.Designer.cs | 4 +- .../Migrations/TestDbContextModelSnapshot.cs | 6 +- .../SampleApp}/Persistence/BlogMapping.cs | 4 +- .../SampleApp/Persistence/BloggerMapping.cs | 8 + .../Persistence/SampleAppDbBootstrapper.cs | 24 +++ .../Persistence/SampleAppDbContext.cs} | 13 +- .../Persistence/SampleAppDbContextFactory.cs | 17 ++ .../Persistence/SampleAppIdGenerator.cs | 11 ++ .../SampleApp/Runtime/SampleAppBuilder.cs | 43 +++++ .../TheDbApplicationWithEfCore.cs | 182 ++++-------------- .../TheDbContext.cs | 2 +- .../TheRepositoryOfComposedAggregate.cs | 84 ++++---- .../TheRepositoryOfPlainAggregate.cs | 4 +- .../TheRabbitMqChannel.cs | 2 +- .../Backend.Fx.TestUtil.csproj | 10 +- .../Persistence/ThePersistentApplication.cs} | 34 ++-- .../Extensions/TheEnumerableEx.cs | 2 +- .../DependencyInjection/DITestFakes.cs | 2 +- .../TheBackendFxApplication.cs | 4 +- .../TheBackendFxApplicationAsyncInvoker.cs | 18 +- .../TheBackendFxApplicationInvoker.cs | 18 +- ...uentializingBackendFxApplicationInvoker.cs | 2 +- .../TheInMemoryMessageBusChannel.cs | 4 +- .../Integration/TheMessageBus.cs | 8 +- .../IdGeneration/TheHiLoIdGenerator.cs | 1 + .../IdGeneration/TheSequenceIdGenerator.cs | 1 + .../Domain => SampleApp.Domain}/Blog.cs | 2 +- .../BlogAuthorization.cs | 2 +- tests/SampleApp.Domain/BlogCreated.cs | 14 ++ tests/SampleApp.Domain/BlogCreatedHandler.cs | 25 +++ .../Domain => SampleApp.Domain}/Blogger.cs | 2 +- .../BloggerAuthorization.cs | 2 +- .../Domain => SampleApp.Domain}/Post.cs | 2 +- .../SampleApp.Domain/SampleApp.Domain.csproj | 15 ++ 78 files changed, 597 insertions(+), 746 deletions(-) rename {tests/Backend.Fx.Tests/Patterns/IdGeneration => src/implementations/Backend.Fx.InMemoryPersistence}/InMemorySequence.cs (92%) delete mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs delete mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs delete mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs delete mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs rename tests/{Backend.Fx.EfCore6Persistence.Tests/DummyImpl => Backend.Fx.EfCore5Persistence.Tests/SampleApp}/Persistence/BlogMapping.cs (83%) create mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs create mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs rename tests/{Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs => Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs} (68%) create mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs create mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs create mode 100644 tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs delete mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs delete mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs delete mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs delete mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs delete mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs rename tests/{Backend.Fx.EfCore5Persistence.Tests/DummyImpl => Backend.Fx.EfCore6Persistence.Tests/SampleApp}/Persistence/BlogMapping.cs (83%) create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs rename tests/{Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs => Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs} (59%) create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs create mode 100644 tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs rename tests/Backend.Fx.Tests/{Patterns/DependencyInjection/TheBackendFxDbApplication.cs => Environment/Persistence/ThePersistentApplication.cs} (71%) rename tests/{Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain => SampleApp.Domain}/Blog.cs (93%) rename tests/{Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain => SampleApp.Domain}/BlogAuthorization.cs (72%) create mode 100644 tests/SampleApp.Domain/BlogCreated.cs create mode 100644 tests/SampleApp.Domain/BlogCreatedHandler.cs rename tests/{Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain => SampleApp.Domain}/Blogger.cs (88%) rename tests/{Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain => SampleApp.Domain}/BloggerAuthorization.cs (72%) rename tests/{Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain => SampleApp.Domain}/Post.cs (94%) create mode 100644 tests/SampleApp.Domain/SampleApp.Domain.csproj diff --git a/Backend.Fx.sln b/Backend.Fx.sln index 49871acf..07533205 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.MicrosoftDepende EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.TestUtil", "tests\Backend.Fx.TestUtil\Backend.Fx.TestUtil.csproj", "{3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Domain", "tests\SampleApp.Domain\SampleApp.Domain.csproj", "{ADCBD99B-0C75-484C-9C7F-6E174455B3AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -125,6 +127,10 @@ Global {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D}.Release|Any CPU.Build.0 = Release|Any CPU + {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -152,6 +158,7 @@ Global {E50D7E8D-D012-4683-BA05-C877BAA25230} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {B4791DB0-F8DD-4248-86CB-407E46F55B13} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} + {ADCBD99B-0C75-484C-9C7F-6E174455B3AE} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969} diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs b/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs index 1cf8501a..2a52975d 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs @@ -16,10 +16,12 @@ public class PersistentApplication : BackendFxApplicationDecorator public PersistentApplication(IDatabaseBootstrapper databaseBootstrapper, IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, + IModule persistenceModule, IBackendFxApplication application) : base(application) { _databaseBootstrapper = databaseBootstrapper; _databaseAvailabilityAwaiter = databaseAvailabilityAwaiter; + application.CompositionRoot.RegisterModules(persistenceModule); } diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs index 1916c162..679ba2e2 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -11,21 +11,19 @@ public class MessageBusApplication : BackendFxApplicationDecorator public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication application) : base(application) { + Invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot, base.Invoker); + AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot, base.AsyncInvoker); + application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); _messageBus = messageBus; - var invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot, base.Invoker); - _messageBus.ProvideInvoker(invoker); - Invoker = invoker; - AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot, base.AsyncInvoker); - + _messageBus.ProvideInvoker( + new SequentializingBackendFxApplicationInvoker( + new ExceptionLoggingAndHandlingInvoker(application.ExceptionLogger, application.Invoker))); } public override async Task BootAsync(CancellationToken cancellationToken = default) { await base.BootAsync(cancellationToken).ConfigureAwait(false); - _messageBus.ProvideInvoker( - new SequentializingBackendFxApplicationInvoker( - new ExceptionLoggingAndHandlingInvoker(ExceptionLogger, Invoker))); _messageBus.Connect(); } diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index e59a6465..f8cc71cb 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -6,6 +6,7 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -13,26 +14,24 @@ namespace Backend.Fx.EfCore5Persistence.Bootstrapping { - public class EfCorePersistenceModule : IModule + public class EfCorePersistenceModule : IModule where TDbContext : DbContext + where TIdGenerator : IEntityIdGenerator { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; private readonly IDbConnectionFactory _dbConnectionFactory; - private readonly IEntityIdGenerator _entityIdGenerator; private readonly Type[] _aggregateRootTypes; private readonly Type[] _entityTypes; private readonly Dictionary _aggregateMappingTypes; public EfCorePersistenceModule( IDbConnectionFactory dbConnectionFactory, - IEntityIdGenerator entityIdGenerator, ILoggerFactory loggerFactory, Action, IDbConnection> configure, params Assembly[] assemblies) { _dbConnectionFactory = dbConnectionFactory; - _entityIdGenerator = entityIdGenerator; _loggerFactory = loggerFactory; _configure = configure; @@ -70,13 +69,14 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( new ServiceDescriptor( typeof(IEntityIdGenerator), - _entityIdGenerator)); + typeof(TIdGenerator), + ServiceLifetime.Singleton)); // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly compositionRoot.Register( new ServiceDescriptor( typeof(IDbConnection), - sp => _dbConnectionFactory.Create(), + _ => _dbConnectionFactory.Create(), ServiceLifetime.Scoped)); // EF core requires us to flush frequently, because of a missing identity map @@ -163,12 +163,12 @@ public void Register(ICompositionRoot compositionRoot) typeof(DbConnectionOperationDecorator), ServiceLifetime.Scoped)); - // // ensure everything dirty is flushed to the db before handling domain events - // compositionRoot.Register( - // new ServiceDescriptor( - // typeof(IDomainEventAggregator), - // typeof(FlushDomainEventAggregatorDecorator), - // ServiceLifetime.Scoped)); + // ensure everything dirty is flushed to the db before handling domain events + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IDomainEventAggregator), + typeof(FlushDomainEventAggregatorDecorator), + ServiceLifetime.Scoped)); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs index 9bb31099..7d4fb82c 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs @@ -1,12 +1,10 @@ using System; -using System.Globalization; using System.Linq; using System.Reflection; using Backend.Fx.BuildingBlocks; using Backend.Fx.Extensions; using Backend.Fx.Logging; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -54,33 +52,5 @@ public static void ApplyAggregateMappings(this DbContext dbContext, ModelBuilder aggregateMapping.ApplyEfMapping(modelBuilder); } } - - - - public static void TraceChangeTrackerState(this DbContext dbContext) - { - if (Logger.IsEnabled(LogLevel.Trace)) - try - { - var changeTrackerState = new - { - added = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Added).ToArray(), - modified = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Modified).ToArray(), - deleted = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Deleted).ToArray(), - unchanged = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Unchanged).ToArray() - }; - - Logger.LogTrace("Change tracker state: {@ChangeTrackerState}", changeTrackerState); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Change tracker state could not be dumped"); - } - } - - private static string GetPrimaryKeyValue(EntityEntry entry) - { - return (entry.Entity as Entity)?.Id.ToString(CultureInfo.InvariantCulture) ?? "?"; - } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs index e53ec3ac..8a1f22d0 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs @@ -37,7 +37,7 @@ public void Flush() { DetectChanges(); UpdateTrackingProperties(); - DbContext.TraceChangeTrackerState(); + if (Logger.IsEnabled(LogLevel.Trace)) Logger.LogTrace("Change tracker state: {@ChangeTrackerState}", DbContext.ChangeTracker.DebugView.LongView); CheckForMissingTenantIds(); SaveChanges(); } diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index a3337416..bf4fc1b7 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -6,6 +6,7 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -13,26 +14,24 @@ namespace Backend.Fx.EfCore6Persistence.Bootstrapping { - public class EfCorePersistenceModule : IModule + public class EfCorePersistenceModule : IModule where TDbContext : DbContext + where TIdGenerator : IEntityIdGenerator { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; private readonly IDbConnectionFactory _dbConnectionFactory; - private readonly IEntityIdGenerator _entityIdGenerator; private readonly Type[] _aggregateRootTypes; private readonly Type[] _entityTypes; private readonly Dictionary _aggregateMappingTypes; public EfCorePersistenceModule( IDbConnectionFactory dbConnectionFactory, - IEntityIdGenerator entityIdGenerator, ILoggerFactory loggerFactory, Action, IDbConnection> configure, params Assembly[] assemblies) { _dbConnectionFactory = dbConnectionFactory; - _entityIdGenerator = entityIdGenerator; _loggerFactory = loggerFactory; _configure = configure; @@ -70,13 +69,14 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( new ServiceDescriptor( typeof(IEntityIdGenerator), - _entityIdGenerator)); + typeof(TIdGenerator), + ServiceLifetime.Singleton)); // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly compositionRoot.Register( new ServiceDescriptor( typeof(IDbConnection), - sp => _dbConnectionFactory.Create(), + _ => _dbConnectionFactory.Create(), ServiceLifetime.Scoped)); // EF core requires us to flush frequently, because of a missing identity map @@ -163,12 +163,12 @@ public void Register(ICompositionRoot compositionRoot) typeof(DbConnectionOperationDecorator), ServiceLifetime.Scoped)); - // // ensure everything dirty is flushed to the db before handling domain events - // compositionRoot.Register( - // new ServiceDescriptor( - // typeof(IDomainEventAggregator), - // typeof(FlushDomainEventAggregatorDecorator), - // ServiceLifetime.Scoped)); + // ensure everything dirty is flushed to the db before handling domain events + compositionRoot.RegisterDecorator( + new ServiceDescriptor( + typeof(IDomainEventAggregator), + typeof(FlushDomainEventAggregatorDecorator), + ServiceLifetime.Scoped)); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs index 44fdb0d0..0f4b0055 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs @@ -1,12 +1,10 @@ using System; -using System.Globalization; using System.Linq; using System.Reflection; using Backend.Fx.BuildingBlocks; using Backend.Fx.Extensions; using Backend.Fx.Logging; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -54,33 +52,5 @@ public static void ApplyAggregateMappings(this DbContext dbContext, ModelBuilder aggregateMapping.ApplyEfMapping(modelBuilder); } } - - - - public static void TraceChangeTrackerState(this DbContext dbContext) - { - if (Logger.IsEnabled(LogLevel.Trace)) - try - { - var changeTrackerState = new - { - added = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Added).ToArray(), - modified = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Modified).ToArray(), - deleted = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Deleted).ToArray(), - unchanged = dbContext.ChangeTracker.Entries().Where(entry => entry.State == EntityState.Unchanged).ToArray() - }; - - Logger.LogTrace("Change tracker state: {@ChangeTrackerState}", changeTrackerState); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Change tracker state could not be dumped"); - } - } - - private static string GetPrimaryKeyValue(EntityEntry entry) - { - return (entry.Entity as Entity)?.Id.ToString(CultureInfo.InvariantCulture) ?? "?"; - } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs index 0e5a9c8d..1ba2584f 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs @@ -37,7 +37,7 @@ public void Flush() { DetectChanges(); UpdateTrackingProperties(); - DbContext.TraceChangeTrackerState(); + if (Logger.IsEnabled(LogLevel.Trace)) Logger.LogTrace("Change tracker state: {@ChangeTrackerState}", DbContext.ChangeTracker.DebugView.LongView); CheckForMissingTenantIds(); SaveChanges(); } @@ -186,7 +186,7 @@ private static EntityEntry GetAggregateRootEntry(ChangeTracker changeTracker, En Logger.LogDebug("Recursing..."); return GetAggregateRootEntry(changeTracker, navigationTargetEntry); } - + throw new InvalidOperationException($"Could not find aggregate root of {entry.Entity.GetType().Name}[{(entry.Entity as Identified)?.Id}]"); } } diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs similarity index 92% rename from tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs rename to src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs index e433a134..ab7c9850 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/InMemorySequence.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs @@ -1,6 +1,6 @@ using Backend.Fx.Patterns.IdGeneration; -namespace Backend.Fx.Tests.Patterns.IdGeneration +namespace Backend.Fx.InMemoryPersistence { public class InMemorySequence : ISequence { diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs index a56c80c8..24959265 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/JwtService.cs @@ -30,7 +30,7 @@ public static TokenValidationParameters TokenValidationParameters() { return new TokenValidationParameters { - NameClaimTypeRetriever = (token, s) => ClaimTypes.Name, + NameClaimTypeRetriever = (_, _) => ClaimTypes.Name, ValidateIssuerSigningKey = true, IssuerSigningKey = SigningCredentials.Key, ValidateIssuer = true, diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj index 748d32d6..796a4f67 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Backend.Fx.EfCore5Persistence.Tests.csproj @@ -30,8 +30,10 @@ + + diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs deleted file mode 100644 index 2fe53b32..00000000 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blogger.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Backend.Fx.BuildingBlocks; -using JetBrains.Annotations; - -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain -{ - public class Blogger : AggregateRoot - { - [UsedImplicitly] - private Blogger() - { - } - - public Blogger(int id, string lastName, string firstName) : base(id) - { - LastName = lastName; - FirstName = firstName; - } - - public string LastName { get; set; } - public string FirstName { get; set; } - public string Bio { get; set; } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs deleted file mode 100644 index 5cf3b44f..00000000 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Backend.Fx.Patterns.Authorization; -using JetBrains.Annotations; - -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain -{ - [UsedImplicitly] - public class BloggerAuthorization : AllowAll - { - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs deleted file mode 100644 index 56a926af..00000000 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; - -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence -{ - public class BloggerMapping : PlainAggregateMapping - { - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs deleted file mode 100644 index 4f21c7fe..00000000 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; - -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence -{ - [UsedImplicitly] - [Obsolete("Only for migration support at design time")] - public class TestDbContextFactory : IDesignTimeDbContextFactory - { - public TestDbContext CreateDbContext(string[] args) - { - return new TestDbContext(new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs index 8c902651..ec1db9e1 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -1,6 +1,6 @@ using System.Data; using System.Security.Principal; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; @@ -12,15 +12,15 @@ public abstract class DatabaseFixture { public void CreateDatabase() { - using (var dbContext = new TestDbContext(GetDbContextOptionsForDbCreation())) + using (var dbContext = new SampleAppDbContext(GetDbContextOptionsForDbCreation())) { dbContext.Database.EnsureCreated(); } } - protected abstract DbContextOptions GetDbContextOptionsForDbCreation(); + protected abstract DbContextOptions GetDbContextOptionsForDbCreation(); - public abstract DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection); + public abstract DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection); public abstract DbConnectionOperationDecorator UseOperation(); diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 26e6504e..2a7e30e4 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -1,7 +1,7 @@ using System; using System.Data; using System.Data.SqlClient; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; @@ -47,15 +47,15 @@ public SqlServerDatabaseFixture() _connectionString = sqlConnectionStringBuilder.ConnectionString; } - protected override DbContextOptions GetDbContextOptionsForDbCreation() + protected override DbContextOptions GetDbContextOptionsForDbCreation() { - return new DbContextOptionsBuilder().UseSqlServer(_connectionString).Options; + return new DbContextOptionsBuilder().UseSqlServer(_connectionString).Options; } - public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) + public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) { - return new DbContextOptionsBuilder().UseSqlServer((SqlConnection) connection); + return new DbContextOptionsBuilder().UseSqlServer((SqlConnection) connection); } public override DbConnectionOperationDecorator UseOperation() diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 6c4b6a6e..0c8d9d20 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,6 +1,6 @@ using System.Data; using System.IO; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Data.Sqlite; @@ -12,14 +12,14 @@ public class SqliteDatabaseFixture : DatabaseFixture { private readonly string _connectionString = "Data Source=" + Path.GetTempFileName(); - protected override DbContextOptions GetDbContextOptionsForDbCreation() + protected override DbContextOptions GetDbContextOptionsForDbCreation() { - return new DbContextOptionsBuilder().UseSqlite(_connectionString).Options; + return new DbContextOptionsBuilder().UseSqlite(_connectionString).Options; } - public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) + public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) { - return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection); + return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection); } public override DbConnectionOperationDecorator UseOperation() diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs index b997adc6..8582111c 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs @@ -1,7 +1,7 @@ using System; using System.Data; using System.Security.Principal; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; @@ -16,13 +16,13 @@ public class TestDbSession : ICanFlush, IDisposable public TestDbSession(DatabaseFixture fixture, DbConnectionOperationDecorator operation, ICurrentTHolder identityHolder, IClock clock) { _operation = operation; - DbContext = new TestDbContext(fixture.GetDbContextOptionsBuilder(operation.DbConnection).Options); + DbContext = new SampleAppDbContext(fixture.GetDbContextOptionsBuilder(operation.DbConnection).Options); _efFlush = new EfFlush(DbContext, identityHolder, clock); DbConnection = operation.DbConnection; } - public TestDbContext DbContext { get; } + public SampleAppDbContext DbContext { get; } public IDbConnection DbConnection { get; } public void Flush() diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs index 82d686e0..70c99230 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -8,7 +8,7 @@ namespace Backend.Fx.EfCorePersistence.Tests.Migrations { - [DbContext(typeof(TestDbContext))] + [DbContext(typeof(SampleAppDbContext))] [Migration("20190624150947_Initial")] partial class Initial { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs index 49a7653f..6d372d2c 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs @@ -1,13 +1,13 @@ // using System; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Backend.Fx.EfCorePersistence.Tests.Migrations { - [DbContext(typeof(TestDbContext))] - partial class TestDbContextModelSnapshot : ModelSnapshot + [DbContext(typeof(SampleAppDbContext))] + partial class SampleAppDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BlogMapping.cs similarity index 83% rename from tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BlogMapping.cs index 9d2265c7..03875d6a 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BlogMapping.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence { public class BlogMapping : AggregateMapping { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs new file mode 100644 index 00000000..4c3d3797 --- /dev/null +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs @@ -0,0 +1,8 @@ +using SampleApp.Domain; + +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence +{ + public class BloggerMapping : PlainAggregateMapping + { + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs new file mode 100644 index 00000000..b908e82d --- /dev/null +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs @@ -0,0 +1,24 @@ +using Backend.Fx.Environment.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence +{ + public class SampleAppDbBootstrapper : IDatabaseBootstrapper + { + private readonly string _connectionString; + + public SampleAppDbBootstrapper(string connectionString) + { + _connectionString = connectionString; + } + public void Dispose() + { } + + public void EnsureDatabaseExistence() + { + var dbContext = new SampleAppDbContext( + new DbContextOptionsBuilder().UseSqlite(_connectionString).Options); + dbContext.Database.EnsureCreated(); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs similarity index 68% rename from tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs rename to tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs index aa223153..50a9b9ea 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs @@ -1,13 +1,13 @@ -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; -using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Environment.MultiTenancy; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence { - public sealed class TestDbContext : DbContext + public sealed class SampleAppDbContext : DbContext { - public TestDbContext([NotNull] DbContextOptions options) : base(options) + public SampleAppDbContext([NotNull] DbContextOptions options) : base(options) { Database.AutoTransactionsEnabled = false; } diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs new file mode 100644 index 00000000..2b81588e --- /dev/null +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs @@ -0,0 +1,17 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence +{ + [UsedImplicitly] + [Obsolete("Only for migration support at design time")] + public class SampleAppDbContextFactory : IDesignTimeDbContextFactory + { + public SampleAppDbContext CreateDbContext(string[] args) + { + return new SampleAppDbContext(new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs new file mode 100644 index 00000000..28d7d18d --- /dev/null +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs @@ -0,0 +1,11 @@ +using Backend.Fx.InMemoryPersistence; +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence +{ + public class SampleAppIdGenerator : SequenceHiLoIdGenerator, IEntityIdGenerator + { + public SampleAppIdGenerator() : base(new InMemorySequence()) + { } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs new file mode 100644 index 00000000..3a1764ef --- /dev/null +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -0,0 +1,43 @@ +using System.Data.Common; +using Backend.Fx.EfCore5Persistence.Bootstrapping; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; +using FakeItEasy; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; +using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; + +namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Runtime +{ + public static class SampleAppBuilder + { + public static IBackendFxApplication Build(CompositionRootType compositionRootType, string connectionString) + { + var dbConnectionFactory = A.Fake(); + A.CallTo(() => dbConnectionFactory.Create()).Returns(new SqliteConnection(connectionString)); + + IBackendFxApplication application = new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + typeof(BlogMapping).Assembly, + typeof(Blog).Assembly); + + application = new PersistentApplication( + new SampleAppDbBootstrapper(connectionString), + A.Fake(), + new EfCorePersistenceModule( + dbConnectionFactory, + A.Fake(), + (builder, connection) => builder.UseSqlite((DbConnection)connection), + application.Assemblies + ), + application); + + return application; + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs index 058abae4..3c786a7c 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -1,25 +1,21 @@ using System; using System.Data; -using System.Data.Common; using System.IO; using System.Threading.Tasks; using Backend.Fx.BuildingBlocks; -using Backend.Fx.EfCore5Persistence.Bootstrapping; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Runtime; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Environment.Persistence; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; -using FakeItEasy; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using SampleApp.Domain; using Xunit; -using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; namespace Backend.Fx.EfCore5Persistence.Tests { @@ -30,10 +26,10 @@ public class TheDbApplicationWithEfCore [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveIdGenerator(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); var entityIdGenerator = sut.CompositionRoot.ServiceProvider.GetRequiredService(); - Assert.StrictEqual(sut.EntityIdGenerator, entityIdGenerator); + Assert.IsType(entityIdGenerator); } [Theory] @@ -41,7 +37,7 @@ public async Task CanResolveIdGenerator(CompositionRootType compositionRootType) [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveDbConnection(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( @@ -59,7 +55,7 @@ public async Task CanResolveDbConnection(CompositionRootType compositionRootType [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveICanFlush(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( @@ -78,14 +74,14 @@ public async Task CanResolveICanFlush(CompositionRootType compositionRootType) [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveDbContext(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { var dbContext = sp.GetRequiredService(); - Assert.IsType(dbContext); + Assert.IsType(dbContext); }, new SystemIdentity(), new TenantId(333)); @@ -96,14 +92,14 @@ public async Task CanResolveDbContext(CompositionRootType compositionRootType) [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveAggregateMapping(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var aggregateMapping = sp.GetRequiredService>(); - Assert.IsType(aggregateMapping); + var aggregateMapping = sp.GetRequiredService>(); + Assert.IsType(aggregateMapping); }, new SystemIdentity(), new TenantId(333)); @@ -114,14 +110,14 @@ public async Task CanResolveAggregateMapping(CompositionRootType compositionRoot [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveEfRepositoryForAggregateRoot(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - Assert.IsType>(repo); + var repo = sp.GetRequiredService>(); + Assert.IsType>(repo); }, new SystemIdentity(), new TenantId(333)); @@ -132,17 +128,18 @@ public async Task CanResolveEfRepositoryForAggregateRoot(CompositionRootType com [InlineData(CompositionRootType.SimpleInjector)] public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + var repo = sp.GetRequiredService>(); + var idGen = sp.GetRequiredService(); + repo.Add(new Blog(idGen.NextId(), "me1")); + repo.Add(new Blog(idGen.NextId(), "me2")); + repo.Add(new Blog(idGen.NextId(), "me3")); + repo.Add(new Blog(idGen.NextId(), "me4")); }, new SystemIdentity(), new TenantId(333)); @@ -150,7 +147,7 @@ public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType c sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); + var repo = sp.GetRequiredService>(); Assert.Equal(4, repo.GetAll().Length); }, new SystemIdentity(), @@ -162,18 +159,19 @@ public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType c [InlineData(CompositionRootType.SimpleInjector)] public async Task DoesNotCommitChangesOnException(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); Assert.Throws( () => sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + var repo = sp.GetRequiredService>(); + var idGen = sp.GetRequiredService(); + repo.Add(new Blog(idGen.NextId(), "me1")); + repo.Add(new Blog(idGen.NextId(), "me2")); + repo.Add(new Blog(idGen.NextId(), "me3")); + repo.Add(new Blog(idGen.NextId(), "me4")); throw new Exception("intentionally thrown for a test case"); }, new SystemIdentity(), @@ -182,7 +180,7 @@ public async Task DoesNotCommitChangesOnException(CompositionRootType compositio sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); + var repo = sp.GetRequiredService>(); Assert.Empty(repo.GetAll()); }, new SystemIdentity(), @@ -194,120 +192,23 @@ public async Task DoesNotCommitChangesOnException(CompositionRootType compositio [InlineData(CompositionRootType.SimpleInjector)] public async Task DoesFlushBeforeRaisingDomainEvents(CompositionRootType compositionRootType) { - var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - var order = new Order(sut.EntityIdGenerator.NextId(), "domain event test"); + var repo = sp.GetRequiredService>(); + var idGen = sp.GetRequiredService(); + var order = new Blog(idGen.NextId(), "domain event test"); repo.Add(order); - sp.GetRequiredService().PublishDomainEvent(new OrderCreated(order.Id)); - OrderCreatedHandler.ExpectedInvocationCount++; + sp.GetRequiredService().PublishDomainEvent(new BlogCreated(order.Id)); + BlogCreatedHandler.ExpectedInvocationCount++; }, new SystemIdentity(), new TenantId(333)); - Assert.Equal(OrderCreatedHandler.ExpectedInvocationCount, OrderCreatedHandler.InvocationCount); - } - - - private class EfCoreTestApplication : PersistentApplication - { - private int _nextId = 1; - private static readonly IDatabaseBootstrapper DbBootstrapper = A.Fake(); - public readonly IEntityIdGenerator EntityIdGenerator = A.Fake(); - - public EfCoreTestApplication(CompositionRootType compositionRootType, string connectionString) - : base( - DbBootstrapper, - A.Fake(), - new BackendFxApplication( - compositionRootType.Create(), - A.Fake(), - typeof(TheDbApplicationWithEfCore).Assembly)) - { - A.CallTo(() => DbBootstrapper.EnsureDatabaseExistence()).Invokes(() => - { - var dbContext = new EfCoreTestDbContext(new DbContextOptionsBuilder() - .UseSqlite(connectionString).Options); - dbContext.Database.EnsureCreated(); - }); - - var dbConnectionFactory = A.Fake(); - A.CallTo(() => dbConnectionFactory.Create()).Returns(new SqliteConnection(connectionString)); - - A.CallTo(() => EntityIdGenerator.NextId()) - .ReturnsLazily(() => _nextId++); - - var loggerFactory = A.Fake(); - - CompositionRoot.RegisterModules( - new EfCorePersistenceModule( - dbConnectionFactory, - EntityIdGenerator, - loggerFactory, - (builder, connection) => builder.UseSqlite((DbConnection)connection), - Assemblies - )); - } - } - } - - public class EfCoreTestDbContext : DbContext - { - public EfCoreTestDbContext(DbContextOptions options) - : base(options) - { - } - - public DbSet Orders { get; set; } - } - - public class Order : AggregateRoot - { - public string Recipient { get; private set; } - - public Order(int id, string recipient) : base(id) - { - Recipient = recipient; - } - } - - public class OrderMapping : PlainAggregateMapping - { - } - - public class OrderAuthorization : AllowAll - { - } - - public class OrderCreated : IDomainEvent - { - public int OrderId { get; } - - public OrderCreated(int orderId) - { - OrderId = orderId; - } - } - - public class OrderCreatedHandler : IDomainEventHandler - { - private readonly DbContext _dbContext; - public static int InvocationCount = 0; - public static int ExpectedInvocationCount = 0; - - public OrderCreatedHandler(DbContext dbContext) - { - _dbContext = dbContext; - } - - public void Handle(OrderCreated domainEvent) - { - InvocationCount++; - Assert.NotNull(_dbContext.Set().Find(domainEvent.OrderId)); + Assert.Equal(BlogCreatedHandler.ExpectedInvocationCount, BlogCreatedHandler.InvocationCount); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs index ccd75ee1..dcb3f0f8 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbContext.cs @@ -1,8 +1,8 @@ using System.Linq; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; using Backend.Fx.EfCore5Persistence.Tests.Fixtures; using Backend.Fx.TestUtil; using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs index f85c7c08..ab91146c 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.EfCore5Persistence.Tests.Fixtures; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; @@ -12,6 +11,7 @@ using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using FakeItEasy; +using SampleApp.Domain; using Xunit; using Xunit.Abstractions; @@ -54,37 +54,43 @@ private int CreateBlogWithPost(IDbConnection dbConnection, int postCount = 1) } } - //FAILING!!!! - // this shows, that ValueObjects treated as OwnedTypes are not supported very well - //[Fact] - //public void CanUpdateDependantValueObject() - //{ - // using (DbSession dbs = _fixture.UseDbSession()) - // { - // int id = CreateBlogWithPost(dbSession.DbConnection, 10); - // Post post; - - // using (var uow = dbs.UseUnitOfWork(_clock)) - // { - // var sut = new EfRepository(uow.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), - // new AllowAll()); - // var blog = sut.Single(id); - // post = blog.Posts.First(); - // post.TargetAudience = new TargetAudience{Culture = "es-AR", IsPublic = false}; - // uow.Complete(); - // } - - // - // { - // string culture = dbSession.DbConnection.ExecuteScalar($"SELECT TargetAudience_Culture ame FROM Posts where id = {post.Id}"); - // Assert.Equal("es-AR", culture); - - // string strChangedOn = dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); - // DateTime changedOn = DateTime.Parse(strChangedOn); - // Assert.Equal(_clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); - // } - // } - //} + // //FAILING!!!! + // // this shows, that ValueObjects treated as OwnedTypes are not supported very well + // [Fact] + // public void CanUpdateDependentValueObject() + // { + // Post post; + // + // var clock = new AdjustableClock(new WallClock()); + // clock.OverrideUtcNow(new DateTime(2000, 1, 2, 3, 4, 5)); + // + // using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) + // { + // int id = CreateBlogWithPost(dbSession.DbConnection, 10); + // + // var sut = new EfRepository( + // dbSession.DbContext, + // new BlogMapping(), + // CurrentTenantIdHolder.Create(_tenantId), + // new AllowAll()); + // var blog = sut.Single(id); + // post = blog.Posts.First(); + // post.TargetAudience = new TargetAudience { Culture = "es-AR", IsPublic = false }; + // } + // + // using (TestDbSession dbSession = _fixture.CreateTestDbSession()) + // { + // string culture = + // dbSession.DbConnection.ExecuteScalar( + // $"SELECT TargetAudience_Culture FROM Posts where id = {post.Id}"); + // Assert.Equal("es-AR", culture); + // + // string strChangedOn = + // dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); + // DateTime changedOn = DateTime.Parse(strChangedOn); + // Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(100))); + // } + // } [Fact] public void CanAddDependent() diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs index 50332417..55e79941 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -1,13 +1,13 @@ using System; using System.Threading.Tasks; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.EfCore5Persistence.Tests.Fixtures; +using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; using Backend.Fx.TestUtil; +using SampleApp.Domain; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj index e1f8888b..7830fb08 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj @@ -35,6 +35,7 @@ + diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs index 7933cb5b..e2314e1e 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/DbConnectionEx.cs @@ -22,6 +22,7 @@ public static T ExecuteScalar(this IDbConnection openConnection, string cmd) { command.CommandText = cmd; object scalarResult = command.ExecuteScalar(); + if (scalarResult is DBNull) return default; if (typeof(T) == typeof(int)) return (T) (object) Convert.ToInt32(scalarResult); return (T) scalarResult; } diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs deleted file mode 100644 index 9e923b5c..00000000 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blog.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Patterns.IdGeneration; -using JetBrains.Annotations; - -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain -{ - public class Blog : AggregateRoot - { - [UsedImplicitly] - private Blog() - { - } - - public Blog(int id, string name) : base(id) - { - Name = name; - } - - public string Name { get; private set; } - - public ISet Posts { get; } = new HashSet(); - - public Post AddPost(IEntityIdGenerator idGenerator, string name, bool isPublic = false) - { - var post = new Post(idGenerator.NextId(), this, name, isPublic); - Posts.Add(post); - return post; - } - - public void Modify(string modified) - { - Name = modified; - foreach (Post post in Posts) post.SetName(modified); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs deleted file mode 100644 index b71d0a05..00000000 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Backend.Fx.Patterns.Authorization; -using JetBrains.Annotations; - -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain -{ - [UsedImplicitly] - public class BlogAuthorization : AllowAll - { - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs deleted file mode 100644 index 7f609269..00000000 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Post.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; -using JetBrains.Annotations; - -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain -{ - public class Post : Entity - { - [UsedImplicitly] - private Post() - { - } - - public Post(int id, Blog blog, string name, bool isPublic = false) : base(id) - { - Blog = blog; - BlogId = blog.Id; - Name = name; - TargetAudience = new TargetAudience {IsPublic = isPublic, Culture = "fr-FR"}; - } - - [UsedImplicitly] public int BlogId { get; private set; } - - [UsedImplicitly] public Blog Blog { get; private set; } - - [UsedImplicitly] public string Name { get; private set; } - - [UsedImplicitly] public TargetAudience TargetAudience { get; set; } - - public void SetName(string name) - { - Name = name; - } - } - - public class TargetAudience : ValueObject - { - public string Culture { get; set; } - - public bool IsPublic { get; set; } - - protected override IEnumerable GetEqualityComponents() - { - yield return Culture; - yield return IsPublic; - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs deleted file mode 100644 index 2c26a7e7..00000000 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/BloggerMapping.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; - -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence -{ - public class BloggerMapping : PlainAggregateMapping - { - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs deleted file mode 100644 index 5d0bfe26..00000000 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Persistence/TestDbContextFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; - -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence -{ - [UsedImplicitly] - [Obsolete("Only for migration support at design time")] - public class TestDbContextFactory : IDesignTimeDbContextFactory - { - public TestDbContext CreateDbContext(string[] args) - { - return new TestDbContext(new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs index 87192608..487ce089 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -1,6 +1,6 @@ using System.Data; using System.Security.Principal; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; @@ -12,15 +12,15 @@ public abstract class DatabaseFixture { public void CreateDatabase() { - using (var dbContext = new TestDbContext(GetDbContextOptionsForDbCreation())) + using (var dbContext = new SampleAppDbContext(GetDbContextOptionsForDbCreation())) { dbContext.Database.EnsureCreated(); } } - protected abstract DbContextOptions GetDbContextOptionsForDbCreation(); + protected abstract DbContextOptions GetDbContextOptionsForDbCreation(); - public abstract DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection); + public abstract DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection); public abstract DbConnectionOperationDecorator UseOperation(); diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index f48d2d8e..83125a47 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -1,7 +1,7 @@ using System; using System.Data; using System.Data.SqlClient; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; @@ -47,15 +47,15 @@ public SqlServerDatabaseFixture() _connectionString = sqlConnectionStringBuilder.ConnectionString; } - protected override DbContextOptions GetDbContextOptionsForDbCreation() + protected override DbContextOptions GetDbContextOptionsForDbCreation() { - return new DbContextOptionsBuilder().UseSqlServer(_connectionString).Options; + return new DbContextOptionsBuilder().UseSqlServer(_connectionString).Options; } - public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) + public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) { - return new DbContextOptionsBuilder().UseSqlServer((SqlConnection) connection); + return new DbContextOptionsBuilder().UseSqlServer((SqlConnection) connection); } public override DbConnectionOperationDecorator UseOperation() diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 9912f84f..fc775591 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,6 +1,6 @@ using System.Data; using System.IO; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Data.Sqlite; @@ -12,14 +12,14 @@ public class SqliteDatabaseFixture : DatabaseFixture { private readonly string _connectionString = "Data Source=" + Path.GetTempFileName(); - protected override DbContextOptions GetDbContextOptionsForDbCreation() + protected override DbContextOptions GetDbContextOptionsForDbCreation() { - return new DbContextOptionsBuilder().UseSqlite(_connectionString).Options; + return new DbContextOptionsBuilder().UseSqlite(_connectionString).Options; } - public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) + public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) { - return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection); + return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection); } public override DbConnectionOperationDecorator UseOperation() diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs index c12d2cb8..6abbbe87 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs @@ -1,7 +1,7 @@ using System; using System.Data; using System.Security.Principal; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; @@ -16,13 +16,13 @@ public class TestDbSession : ICanFlush, IDisposable public TestDbSession(DatabaseFixture fixture, DbConnectionOperationDecorator operation, ICurrentTHolder identityHolder, IClock clock) { _operation = operation; - DbContext = new TestDbContext(fixture.GetDbContextOptionsBuilder(operation.DbConnection).Options); + DbContext = new SampleAppDbContext(fixture.GetDbContextOptionsBuilder(operation.DbConnection).Options); _efFlush = new EfFlush(DbContext, identityHolder, clock); DbConnection = operation.DbConnection; } - public TestDbContext DbContext { get; } + public SampleAppDbContext DbContext { get; } public IDbConnection DbConnection { get; } public void Flush() diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs index 2f7f600e..decffc7e 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/20190624150947_Initial.Designer.cs @@ -1,6 +1,6 @@ // using System; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; @@ -8,7 +8,7 @@ namespace Backend.Fx.EfCorePersistence.Tests.Migrations { - [DbContext(typeof(TestDbContext))] + [DbContext(typeof(SampleAppDbContext))] [Migration("20190624150947_Initial")] partial class Initial { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs index 9e58463a..9b88fc26 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Migrations/TestDbContextModelSnapshot.cs @@ -1,13 +1,13 @@ // using System; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Backend.Fx.EfCorePersistence.Tests.Migrations { - [DbContext(typeof(TestDbContext))] - partial class TestDbContextModelSnapshot : ModelSnapshot + [DbContext(typeof(SampleAppDbContext))] + partial class SampleAppDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BlogMapping.cs similarity index 83% rename from tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs rename to tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BlogMapping.cs index 529ff2cd..de7e2807 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/BlogMapping.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BlogMapping.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence { public class BlogMapping : AggregateMapping { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs new file mode 100644 index 00000000..32e31401 --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/BloggerMapping.cs @@ -0,0 +1,8 @@ +using SampleApp.Domain; + +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence +{ + public class BloggerMapping : PlainAggregateMapping + { + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs new file mode 100644 index 00000000..a130665e --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs @@ -0,0 +1,24 @@ +using Backend.Fx.Environment.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence +{ + public class SampleAppDbBootstrapper : IDatabaseBootstrapper + { + private readonly string _connectionString; + + public SampleAppDbBootstrapper(string connectionString) + { + _connectionString = connectionString; + } + public void Dispose() + { } + + public void EnsureDatabaseExistence() + { + var dbContext = new SampleAppDbContext( + new DbContextOptionsBuilder().UseSqlite(_connectionString).Options); + dbContext.Database.EnsureCreated(); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs similarity index 59% rename from tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs rename to tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs index b47f2cea..9164f748 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Persistence/TestDbContext.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContext.cs @@ -1,22 +1,19 @@ -using Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain; -using Backend.Fx.Environment.MultiTenancy; -using JetBrains.Annotations; +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Persistence +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence { - public sealed class TestDbContext : DbContext + public sealed class SampleAppDbContext : DbContext { - public TestDbContext([NotNull] DbContextOptions options) : base(options) + public SampleAppDbContext([NotNull] DbContextOptions options) : base(options) { Database.AutoTransactionsEnabled = false; } public DbSet Bloggers { get; [UsedImplicitly] set; } - public DbSet Blogs { get; [UsedImplicitly] set; } public DbSet Posts { get; [UsedImplicitly] set; } - public DbSet Tenants { get; [UsedImplicitly] set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs new file mode 100644 index 00000000..4730fbfd --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs @@ -0,0 +1,17 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence +{ + [UsedImplicitly] + [Obsolete("Only for migration support at design time")] + public class SampleAppDbContextFactory : IDesignTimeDbContextFactory + { + public SampleAppDbContext CreateDbContext(string[] args) + { + return new SampleAppDbContext(new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs new file mode 100644 index 00000000..2c1fe17a --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs @@ -0,0 +1,11 @@ +using Backend.Fx.InMemoryPersistence; +using Backend.Fx.Patterns.IdGeneration; + +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence +{ + public class SampleAppIdGenerator : SequenceHiLoIdGenerator, IEntityIdGenerator + { + public SampleAppIdGenerator() : base(new InMemorySequence()) + { } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs new file mode 100644 index 00000000..544eb782 --- /dev/null +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -0,0 +1,43 @@ +using System.Data.Common; +using Backend.Fx.EfCore6Persistence.Bootstrapping; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.TestUtil; +using FakeItEasy; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; +using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; + +namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Runtime +{ + public static class SampleAppBuilder + { + public static IBackendFxApplication Build(CompositionRootType compositionRootType, string connectionString) + { + var dbConnectionFactory = A.Fake(); + A.CallTo(() => dbConnectionFactory.Create()).Returns(new SqliteConnection(connectionString)); + + IBackendFxApplication application = new BackendFxApplication( + compositionRootType.Create(), + A.Fake(), + typeof(BlogMapping).Assembly, + typeof(Blog).Assembly); + + application = new PersistentApplication( + new SampleAppDbBootstrapper(connectionString), + A.Fake(), + new EfCorePersistenceModule( + dbConnectionFactory, + A.Fake(), + (builder, connection) => builder.UseSqlite((DbConnection)connection), + application.Assemblies + ), + application); + + return application; + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs index b601797a..e510bb80 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -1,44 +1,35 @@ using System; using System.Data; -using System.Data.Common; using System.IO; using System.Threading.Tasks; using Backend.Fx.BuildingBlocks; -using Backend.Fx.EfCore6Persistence.Bootstrapping; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Runtime; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Environment.Persistence; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; -using FakeItEasy; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using SampleApp.Domain; using Xunit; -using Xunit.Abstractions; -using ILoggerFactory = Microsoft.Extensions.Logging.ILoggerFactory; namespace Backend.Fx.EfCore6Persistence.Tests { - public class TheDbApplicationWithEfCore : TestWithLogging + public class TheDbApplicationWithEfCore { - public TheDbApplicationWithEfCore(ITestOutputHelper output) : base(output) - { - } - [Theory] [InlineData(CompositionRootType.Microsoft)] [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveIdGenerator(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); var entityIdGenerator = sut.CompositionRoot.ServiceProvider.GetRequiredService(); - Assert.StrictEqual(sut.EntityIdGenerator, entityIdGenerator); + Assert.IsType(entityIdGenerator); } [Theory] @@ -46,7 +37,7 @@ public async Task CanResolveIdGenerator(CompositionRootType compositionRootType) [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveDbConnection(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( @@ -64,7 +55,7 @@ public async Task CanResolveDbConnection(CompositionRootType compositionRootType [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveICanFlush(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( @@ -83,14 +74,14 @@ public async Task CanResolveICanFlush(CompositionRootType compositionRootType) [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveDbContext(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { var dbContext = sp.GetRequiredService(); - Assert.IsType(dbContext); + Assert.IsType(dbContext); }, new SystemIdentity(), new TenantId(333)); @@ -101,14 +92,14 @@ public async Task CanResolveDbContext(CompositionRootType compositionRootType) [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveAggregateMapping(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var aggregateMapping = sp.GetRequiredService>(); - Assert.IsType(aggregateMapping); + var aggregateMapping = sp.GetRequiredService>(); + Assert.IsType(aggregateMapping); }, new SystemIdentity(), new TenantId(333)); @@ -119,14 +110,14 @@ public async Task CanResolveAggregateMapping(CompositionRootType compositionRoot [InlineData(CompositionRootType.SimpleInjector)] public async Task CanResolveEfRepositoryForAggregateRoot(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - Assert.IsType>(repo); + var repo = sp.GetRequiredService>(); + Assert.IsType>(repo); }, new SystemIdentity(), new TenantId(333)); @@ -137,17 +128,18 @@ public async Task CanResolveEfRepositoryForAggregateRoot(CompositionRootType com [InlineData(CompositionRootType.SimpleInjector)] public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + var repo = sp.GetRequiredService>(); + var idGen = sp.GetRequiredService(); + repo.Add(new Blog(idGen.NextId(), "me1")); + repo.Add(new Blog(idGen.NextId(), "me2")); + repo.Add(new Blog(idGen.NextId(), "me3")); + repo.Add(new Blog(idGen.NextId(), "me4")); }, new SystemIdentity(), new TenantId(333)); @@ -155,7 +147,7 @@ public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType c sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); + var repo = sp.GetRequiredService>(); Assert.Equal(4, repo.GetAll().Length); }, new SystemIdentity(), @@ -167,18 +159,19 @@ public async Task AutoCommitsChangesOnCompletingInvocation(CompositionRootType c [InlineData(CompositionRootType.SimpleInjector)] public async Task DoesNotCommitChangesOnException(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); Assert.Throws( () => sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me1")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me2")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me3")); - repo.Add(new Order(sut.EntityIdGenerator.NextId(), "me4")); + var repo = sp.GetRequiredService>(); + var idGen = sp.GetRequiredService(); + repo.Add(new Blog(idGen.NextId(), "me1")); + repo.Add(new Blog(idGen.NextId(), "me2")); + repo.Add(new Blog(idGen.NextId(), "me3")); + repo.Add(new Blog(idGen.NextId(), "me4")); throw new Exception("intentionally thrown for a test case"); }, new SystemIdentity(), @@ -187,7 +180,7 @@ public async Task DoesNotCommitChangesOnException(CompositionRootType compositio sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); + var repo = sp.GetRequiredService>(); Assert.Empty(repo.GetAll()); }, new SystemIdentity(), @@ -199,120 +192,23 @@ public async Task DoesNotCommitChangesOnException(CompositionRootType compositio [InlineData(CompositionRootType.SimpleInjector)] public async Task DoesFlushBeforeRaisingDomainEvents(CompositionRootType compositionRootType) { - using var sut = new EfCoreTestApplication(compositionRootType, "Data Source=" + Path.GetTempFileName()); + var sut = SampleAppBuilder.Build(compositionRootType, "Data Source=" + Path.GetTempFileName()); await sut.BootAsync(); sut.Invoker.Invoke( sp => { - var repo = sp.GetRequiredService>(); - var order = new Order(sut.EntityIdGenerator.NextId(), "domain event test"); + var repo = sp.GetRequiredService>(); + var idGen = sp.GetRequiredService(); + var order = new Blog(idGen.NextId(), "domain event test"); repo.Add(order); - sp.GetRequiredService().PublishDomainEvent(new OrderCreated(order.Id)); - OrderCreatedHandler.ExpectedInvocationCount++; + sp.GetRequiredService().PublishDomainEvent(new BlogCreated(order.Id)); + BlogCreatedHandler.ExpectedInvocationCount++; }, new SystemIdentity(), new TenantId(333)); - Assert.Equal(OrderCreatedHandler.ExpectedInvocationCount, OrderCreatedHandler.InvocationCount); - } - - - private class EfCoreTestApplication : PersistentApplication - { - private int _nextId = 1; - private static readonly IDatabaseBootstrapper DbBootstrapper = A.Fake(); - public readonly IEntityIdGenerator EntityIdGenerator = A.Fake(); - - public EfCoreTestApplication(CompositionRootType compositionRootType, string connectionString) - : base( - DbBootstrapper, - A.Fake(), - new BackendFxApplication( - compositionRootType.Create(), - A.Fake(), - typeof(TheDbApplicationWithEfCore).Assembly)) - { - A.CallTo(() => DbBootstrapper.EnsureDatabaseExistence()).Invokes(() => - { - var dbContext = new EfCoreTestDbContext(new DbContextOptionsBuilder() - .UseSqlite(connectionString).Options); - dbContext.Database.EnsureCreated(); - }); - - var dbConnectionFactory = A.Fake(); - A.CallTo(() => dbConnectionFactory.Create()).Returns(new SqliteConnection(connectionString)); - - A.CallTo(() => EntityIdGenerator.NextId()) - .ReturnsLazily(() => _nextId++); - - var loggerFactory = A.Fake(); - - CompositionRoot.RegisterModules( - new EfCorePersistenceModule( - dbConnectionFactory, - EntityIdGenerator, - loggerFactory, - (builder, connection) => builder.UseSqlite((DbConnection)connection), - Assemblies - )); - } - } - } - - public class EfCoreTestDbContext : DbContext - { - public EfCoreTestDbContext(DbContextOptions options) - : base(options) - { - } - - public DbSet Orders { get; set; } - } - - public class Order : AggregateRoot - { - public string Recipient { get; private set; } - - public Order(int id, string recipient) : base(id) - { - Recipient = recipient; - } - } - - public class OrderMapping : PlainAggregateMapping - { - } - - public class OrderAuthorization : AllowAll - { - } - - public class OrderCreated : IDomainEvent - { - public int OrderId { get; } - - public OrderCreated(int orderId) - { - OrderId = orderId; - } - } - - public class OrderCreatedHandler : IDomainEventHandler - { - private readonly DbContext _dbContext; - public static int InvocationCount = 0; - public static int ExpectedInvocationCount = 0; - - public OrderCreatedHandler(DbContext dbContext) - { - _dbContext = dbContext; - } - - public void Handle(OrderCreated domainEvent) - { - InvocationCount++; - Assert.NotNull(_dbContext.Set().Find(domainEvent.OrderId)); + Assert.Equal(BlogCreatedHandler.ExpectedInvocationCount, BlogCreatedHandler.InvocationCount); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs index f30c7a64..155ee158 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbContext.cs @@ -1,8 +1,8 @@ using System.Linq; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.TestUtil; using Microsoft.EntityFrameworkCore; +using SampleApp.Domain; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs index 71e24c79..5ca0fa86 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; @@ -12,6 +11,7 @@ using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using FakeItEasy; +using SampleApp.Domain; using Xunit; using Xunit.Abstractions; @@ -54,37 +54,43 @@ private int CreateBlogWithPost(IDbConnection dbConnection, int postCount = 1) } } - //FAILING!!!! - // this shows, that ValueObjects treated as OwnedTypes are not supported very well - //[Fact] - //public void CanUpdateDependantValueObject() - //{ - // using (DbSession dbs = _fixture.UseDbSession()) - // { - // int id = CreateBlogWithPost(dbSession.DbConnection, 10); - // Post post; - - // using (var uow = dbs.UseUnitOfWork(_clock)) - // { - // var sut = new EfRepository(uow.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), - // new AllowAll()); - // var blog = sut.Single(id); - // post = blog.Posts.First(); - // post.TargetAudience = new TargetAudience{Culture = "es-AR", IsPublic = false}; - // uow.Complete(); - // } - - // - // { - // string culture = dbSession.DbConnection.ExecuteScalar($"SELECT TargetAudience_Culture ame FROM Posts where id = {post.Id}"); - // Assert.Equal("es-AR", culture); - - // string strChangedOn = dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); - // DateTime changedOn = DateTime.Parse(strChangedOn); - // Assert.Equal(_clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); - // } - // } - //} + // //FAILING!!!! + // // this shows, that ValueObjects treated as OwnedTypes are not supported very well + // [Fact] + // public void CanUpdateDependentValueObject() + // { + // Post post; + // + // var clock = new AdjustableClock(new WallClock()); + // clock.OverrideUtcNow(new DateTime(2000, 1, 2, 3, 4, 5)); + // + // using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) + // { + // int id = CreateBlogWithPost(dbSession.DbConnection, 10); + // + // var sut = new EfRepository( + // dbSession.DbContext, + // new BlogMapping(), + // CurrentTenantIdHolder.Create(_tenantId), + // new AllowAll()); + // var blog = sut.Single(id); + // post = blog.Posts.First(); + // post.TargetAudience = new TargetAudience { Culture = "es-AR", IsPublic = false }; + // } + // + // using (TestDbSession dbSession = _fixture.CreateTestDbSession()) + // { + // string culture = + // dbSession.DbConnection.ExecuteScalar( + // $"SELECT TargetAudience_Culture FROM Posts where id = {post.Id}"); + // Assert.Equal("es-AR", culture); + // + // string strChangedOn = + // dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); + // DateTime changedOn = DateTime.Parse(strChangedOn); + // Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(100))); + // } + // } [Fact] public void CanAddDependent() @@ -92,7 +98,8 @@ public void CanAddDependent() using (TestDbSession dbSession = _fixture.CreateTestDbSession()) { var id = CreateBlogWithPost(dbSession.DbConnection, 10); - var sut = new EfRepository(dbSession.DbContext, + var sut = new EfRepository( + dbSession.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); @@ -277,8 +284,10 @@ public void CanUpdate() { Assert.Equal(1, dbSession.DbConnection.ExecuteScalar("SELECT count(*) FROM Blogs")); Assert.Equal(id, dbSession.DbConnection.ExecuteScalar("SELECT Id FROM Blogs LIMIT 1")); - Assert.Equal("modified", dbSession.DbConnection.ExecuteScalar("SELECT Name FROM Blogs LIMIT 1")); - Assert.Equal("modified", dbSession.DbConnection.ExecuteScalar("SELECT Name FROM Posts LIMIT 1")); + Assert.Equal("modified", + dbSession.DbConnection.ExecuteScalar("SELECT Name FROM Blogs LIMIT 1")); + Assert.Equal("modified", + dbSession.DbConnection.ExecuteScalar("SELECT Name FROM Posts LIMIT 1")); } } @@ -312,7 +321,8 @@ public void CanUpdateDependant() var name = dbSession.DbConnection.ExecuteScalar($"SELECT name FROM Posts where id = {post.Id}"); Assert.Equal("modified", name); - var strChangedOn = dbSession.DbConnection.ExecuteScalar($"SELECT changedon FROM Posts where id = {post.Id}"); + var strChangedOn = + dbSession.DbConnection.ExecuteScalar($"SELECT changedon FROM Posts where id = {post.Id}"); DateTime changedOn = DateTime.Parse(strChangedOn); Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); } diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs index b767acda..d78ece33 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -1,13 +1,13 @@ using System; using System.Threading.Tasks; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Persistence; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; +using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; using Backend.Fx.TestUtil; +using SampleApp.Domain; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs index 53d8f454..c0dcf161 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqChannel.cs @@ -96,7 +96,7 @@ public void Connect() _testOutputHelper.WriteLine(" [*] Waiting for messages."); var consumer = new EventingBasicConsumer(_channel); - consumer.Received += (model, ea) => + consumer.Received += (_, ea) => { var body = ea.Body.ToArray(); var message = Encoding.UTF8.GetString(body); diff --git a/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj b/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj index 5722fe9a..769bdb63 100644 --- a/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj +++ b/tests/Backend.Fx.TestUtil/Backend.Fx.TestUtil.csproj @@ -5,14 +5,14 @@ - - + + - - - + + + diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs b/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs similarity index 71% rename from tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs rename to tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs index 1fd79e5a..02eb40d1 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxDbApplication.cs +++ b/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs @@ -1,20 +1,26 @@ using System.Threading; +using System.Threading.Tasks; using Backend.Fx.Environment.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; using Xunit.Abstractions; -namespace Backend.Fx.Tests.Patterns.DependencyInjection +namespace Backend.Fx.Tests.Environment.Persistence { - public class TheBackendFxDbApplication : TestWithLogging + public class ThePersistentApplication : TestWithLogging { - public TheBackendFxDbApplication(ITestOutputHelper output): base(output) + public ThePersistentApplication(ITestOutputHelper output): base(output) { IBackendFxApplication application = new BackendFxApplication(_fakes.CompositionRoot, A.Fake()); - _sut = new PersistentApplication(_databaseBootstrapper, _databaseAvailabilityAwaiter, application); + _sut = new PersistentApplication( + _databaseBootstrapper, + _databaseAvailabilityAwaiter, + A.Fake(), + application); } private readonly DiTestFakes _fakes = new DiTestFakes(); @@ -33,26 +39,26 @@ public void CallsDatabaseBootExtensionPointsOnBoot() } [Fact] - public void DelegatesAllCalls() + public async Task DelegatesAllCalls() { var application =A.Fake(); var sut = new PersistentApplication(A.Fake(), - A.Fake(), - application); + A.Fake(), + A.Fake(), + application); - // ReSharper disable once UnusedVariable - IBackendFxApplicationAsyncInvoker ai = sut.AsyncInvoker; + Fake.ClearRecordedCalls(application); + + object unused1 = sut.AsyncInvoker; A.CallTo(() => application.AsyncInvoker).MustHaveHappenedOnceExactly(); - // ReSharper disable once UnusedVariable - ICompositionRoot cr = sut.CompositionRoot; + object unused2 = sut.CompositionRoot; A.CallTo(() => application.CompositionRoot).MustHaveHappenedOnceExactly(); - // ReSharper disable once UnusedVariable - IBackendFxApplicationInvoker i = sut.Invoker; + object unused3 = sut.Invoker; A.CallTo(() => application.Invoker).MustHaveHappenedOnceExactly(); - sut.BootAsync(); + await sut.BootAsync(); A.CallTo(() => application.BootAsync(A._)).MustHaveHappenedOnceExactly(); sut.Dispose(); diff --git a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs b/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs index 69709c72..3ded35c7 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs @@ -17,7 +17,7 @@ private class Item [Fact] public void ExecutesActionForAll() { - IEnumerable enumerable = Enumerable.Range(0, 100).Select(i => new Item()).ToArray(); + IEnumerable enumerable = Enumerable.Range(0, 100).Select(_ => new Item()).ToArray(); enumerable.ForAll(itm => itm.Touched = true); Assert.All(enumerable, itm => Assert.True(itm.Touched)); diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs index bfec3c0d..228dd226 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs @@ -24,7 +24,7 @@ public DiTestFakes() A.CallTo(() => CompositionRoot.BeginScope()).Returns(ServiceScope); A.CallTo(() => Invoker.Invoke(A>._, A._, A._, A._)) - .Invokes((Action a, IIdentity i, TenantId t, Guid? g) => a.Invoke(ServiceProvider)); + .Invokes((Action a, IIdentity _, TenantId _, Guid? _) => a.Invoke(ServiceProvider)); } public ICurrentTHolder TenantIdHolder { get; } = A.Fake>(); diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index 2d63f86c..21cd3237 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -127,7 +127,7 @@ public async Task LogsButDoesNotHandleExceptions() await _sut.BootAsync(); Assert.Throws(() => - _sut.Invoker.Invoke(sp => throw exception, new AnonymousIdentity(), new TenantId(111))); + _sut.Invoker.Invoke(_ => throw exception, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _exceptionLogger.LogException(A.That.IsEqualTo(exception))) .MustHaveHappenedOnceExactly(); @@ -140,7 +140,7 @@ public async Task LogsButDoesNotHandleExceptionsAsync() await _sut.BootAsync(); await Assert.ThrowsAsync(() => - _sut.AsyncInvoker.InvokeAsync(sp => throw exception, new AnonymousIdentity(), new TenantId(111))); + _sut.AsyncInvoker.InvokeAsync(_ => throw exception, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _exceptionLogger.LogException(A.That.IsEqualTo(exception))) .MustHaveHappenedOnceExactly(); diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs index 735adcd1..f608e6e2 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs @@ -25,11 +25,11 @@ public TheBackendFxApplicationAsyncInvoker(ITestOutputHelper output): base(outpu [Fact] public async Task BeginsNewScopeForEveryInvocation() { - await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedTwiceExactly(); } @@ -37,11 +37,11 @@ public async Task BeginsNewScopeForEveryInvocation() [Fact] public async Task BeginsNewOperationForEveryInvocation() { - await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -50,7 +50,7 @@ public async Task BeginsNewOperationForEveryInvocation() public async Task DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -58,7 +58,7 @@ public async Task DoesNotCatchFrameworkExceptions() [Fact] public async Task DoesNotCatchOperationExceptions() { - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(sp => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(_ => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -67,7 +67,7 @@ public async Task DoesNotCatchOperationExceptions() public async Task MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -75,7 +75,7 @@ public async Task MaintainsCorrelationIdOnInvocation() public async Task MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - await _sut.InvokeAsync(sp => Task.CompletedTask, identity, new TenantId(123)); + await _sut.InvokeAsync(_ => Task.CompletedTask, identity, new TenantId(123)); A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); } @@ -83,7 +83,7 @@ public async Task MaintainsIdentityOnInvocation() public async Task MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - await _sut.InvokeAsync(sp => Task.CompletedTask, new AnonymousIdentity(), tenantId); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), tenantId); A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); } diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs index 57dbaa31..ef7d07cc 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs @@ -24,11 +24,11 @@ public TheBackendFxApplicationInvoker(ITestOutputHelper output): base(output) [Fact] public void BeginsNewScopeForEveryInvocation() { - _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedOnceExactly(); - _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedTwiceExactly(); } @@ -36,11 +36,11 @@ public void BeginsNewScopeForEveryInvocation() [Fact] public void BeginsNewOperationForEveryInvocation() { - _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111)); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111)); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -49,7 +49,7 @@ public void BeginsNewOperationForEveryInvocation() public void DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - Assert.Throws(() => _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(111))); + Assert.Throws(() => _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -57,7 +57,7 @@ public void DoesNotCatchFrameworkExceptions() [Fact] public void DoesNotCatchOperationExceptions() { - Assert.Throws(() => _sut.Invoke(sp => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + Assert.Throws(() => _sut.Invoke(_ => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -66,7 +66,7 @@ public void DoesNotCatchOperationExceptions() public void MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - _sut.Invoke(sp => { }, new AnonymousIdentity(), new TenantId(123), correlationId); + _sut.Invoke(_ => { }, new AnonymousIdentity(), new TenantId(123), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -74,7 +74,7 @@ public void MaintainsCorrelationIdOnInvocation() public void MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - _sut.Invoke(sp => { }, identity, new TenantId(123)); + _sut.Invoke(_ => { }, identity, new TenantId(123)); A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); } @@ -82,7 +82,7 @@ public void MaintainsIdentityOnInvocation() public void MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - _sut.Invoke(sp => { }, new AnonymousIdentity(), tenantId); + _sut.Invoke(_ => { }, new AnonymousIdentity(), tenantId); A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); } diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index 4b861760..a182781a 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -33,7 +33,7 @@ private async Task InvokeSomeActions(int count, IBackendFxApplicationInvoker inv { var tasks = Enumerable .Range(0, count) - .Select(i => Task.Run(() => invoker.Invoke(DoTheAction, new AnonymousIdentity(), new TenantId(1)))) + .Select(_ => Task.Run(() => invoker.Invoke(DoTheAction, new AnonymousIdentity(), new TenantId(1)))) .ToArray(); await Task.WhenAll(tasks); diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs index 30dcace9..25b33a5d 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs @@ -38,13 +38,13 @@ public async Task InvokesAllApplicationHandlers() var eventHandled = false; messageBus.Connect(); messageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); - messageBus.Subscribe(new DelegateIntegrationMessageHandler(ev => eventHandled = true)); + messageBus.Subscribe(new DelegateIntegrationMessageHandler(_ => eventHandled = true)); var anotherMessageBus = new InMemoryMessageBus(channel); var anotherEventHandled = false; anotherMessageBus.Connect(); anotherMessageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); - messageBus.Subscribe(new DelegateIntegrationMessageHandler(ev => anotherEventHandled = true)); + messageBus.Subscribe(new DelegateIntegrationMessageHandler(_ => anotherEventHandled = true)); await messageBus.Publish(new TestIntegrationEvent(0, string.Empty)); await channel.FinishHandlingAllMessagesAsync(); diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs index cff54ed4..be0e4cb7 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs @@ -68,8 +68,8 @@ public class TestInvoker : IBackendFxApplicationInvoker public TestInvoker() { - A.CallTo(() => TypedHandler.Handle(A._)).Invokes((TestIntegrationEvent e) => IntegrationEvent.TypedProcessed.Set()); - A.CallTo(() => DynamicHandler.Handle(new object())).WithAnyArguments().Invokes((object e) => IntegrationEvent.DynamicProcessed.Set()); + A.CallTo(() => TypedHandler.Handle(A._)).Invokes((TestIntegrationEvent _) => IntegrationEvent.TypedProcessed.Set()); + A.CallTo(() => DynamicHandler.Handle(new object())).WithAnyArguments().Invokes((object _) => IntegrationEvent.DynamicProcessed.Set()); A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(TypedMessageHandler)))) .Returns(new TypedMessageHandler(TypedHandler)); @@ -165,7 +165,7 @@ public async void DoesNotCallUnsubscribedDynamicEventHandler() public async void DoesNotCallUnsubscribedDelegateEventHandler() { var handled = new ManualResetEvent(false); - var handler = new DelegateIntegrationMessageHandler(ev => handled.Set()); + var handler = new DelegateIntegrationMessageHandler(_ => handled.Set()); Sut.Subscribe(handler); Sut.Unsubscribe(handler); await Sut.Publish(Invoker.IntegrationEvent); @@ -182,7 +182,7 @@ public void CannCallConnectButItDoesNothing() public async void DelegateIntegrationMessageHandler() { var handled = new ManualResetEvent(false); - var handler = new DelegateIntegrationMessageHandler(ev => handled.Set()); + var handler = new DelegateIntegrationMessageHandler(_ => handled.Set()); Sut.Subscribe(handler); await Sut.Publish(Invoker.IntegrationEvent); Assert.True(handled.WaitOne(Debugger.IsAttached ? int.MaxValue : 10000)); diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs index 54a3d1bf..42fbc32d 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs @@ -1,4 +1,5 @@ using System.Linq; +using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs index 27c9d1b5..7f69dca3 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs @@ -1,4 +1,5 @@ using System.Linq; +using Backend.Fx.InMemoryPersistence; using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using Xunit; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blog.cs b/tests/SampleApp.Domain/Blog.cs similarity index 93% rename from tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blog.cs rename to tests/SampleApp.Domain/Blog.cs index 20c02646..83007b19 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Blog.cs +++ b/tests/SampleApp.Domain/Blog.cs @@ -3,7 +3,7 @@ using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain +namespace SampleApp.Domain { public class Blog : AggregateRoot { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs b/tests/SampleApp.Domain/BlogAuthorization.cs similarity index 72% rename from tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs rename to tests/SampleApp.Domain/BlogAuthorization.cs index f1d9937d..9b32ce3e 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/BlogAuthorization.cs +++ b/tests/SampleApp.Domain/BlogAuthorization.cs @@ -1,7 +1,7 @@ using Backend.Fx.Patterns.Authorization; using JetBrains.Annotations; -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain +namespace SampleApp.Domain { [UsedImplicitly] public class BlogAuthorization : AllowAll diff --git a/tests/SampleApp.Domain/BlogCreated.cs b/tests/SampleApp.Domain/BlogCreated.cs new file mode 100644 index 00000000..2655a758 --- /dev/null +++ b/tests/SampleApp.Domain/BlogCreated.cs @@ -0,0 +1,14 @@ +using Backend.Fx.Patterns.EventAggregation.Domain; + +namespace SampleApp.Domain +{ + public class BlogCreated : IDomainEvent + { + public int BlogId { get; } + + public BlogCreated(int blogId) + { + BlogId = blogId; + } + } +} \ No newline at end of file diff --git a/tests/SampleApp.Domain/BlogCreatedHandler.cs b/tests/SampleApp.Domain/BlogCreatedHandler.cs new file mode 100644 index 00000000..185da711 --- /dev/null +++ b/tests/SampleApp.Domain/BlogCreatedHandler.cs @@ -0,0 +1,25 @@ +using Backend.Fx.BuildingBlocks; +using Backend.Fx.Patterns.EventAggregation.Domain; +using Xunit; + +namespace SampleApp.Domain +{ + public class BlogCreatedHandler : IDomainEventHandler + { + private readonly IRepository _blogRepository; + + public static int InvocationCount = 0; + public static int ExpectedInvocationCount = 0; + + public BlogCreatedHandler(IRepository blogRepository) + { + _blogRepository = blogRepository; + } + + public void Handle(BlogCreated domainEvent) + { + InvocationCount++; + Assert.NotNull(_blogRepository.SingleOrDefault(domainEvent.BlogId)); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs b/tests/SampleApp.Domain/Blogger.cs similarity index 88% rename from tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs rename to tests/SampleApp.Domain/Blogger.cs index 39db43c6..b57e7ed6 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/Blogger.cs +++ b/tests/SampleApp.Domain/Blogger.cs @@ -1,7 +1,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain +namespace SampleApp.Domain { public class Blogger : AggregateRoot { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs b/tests/SampleApp.Domain/BloggerAuthorization.cs similarity index 72% rename from tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs rename to tests/SampleApp.Domain/BloggerAuthorization.cs index 8e7d9b78..882429f1 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/DummyImpl/Domain/BloggerAuthorization.cs +++ b/tests/SampleApp.Domain/BloggerAuthorization.cs @@ -1,7 +1,7 @@ using Backend.Fx.Patterns.Authorization; using JetBrains.Annotations; -namespace Backend.Fx.EfCore6Persistence.Tests.DummyImpl.Domain +namespace SampleApp.Domain { [UsedImplicitly] public class BloggerAuthorization : AllowAll diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Post.cs b/tests/SampleApp.Domain/Post.cs similarity index 94% rename from tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Post.cs rename to tests/SampleApp.Domain/Post.cs index 8dc534ae..b7df2582 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/DummyImpl/Domain/Post.cs +++ b/tests/SampleApp.Domain/Post.cs @@ -2,7 +2,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.EfCore5Persistence.Tests.DummyImpl.Domain +namespace SampleApp.Domain { public class Post : Entity { diff --git a/tests/SampleApp.Domain/SampleApp.Domain.csproj b/tests/SampleApp.Domain/SampleApp.Domain.csproj new file mode 100644 index 00000000..e1d7d1df --- /dev/null +++ b/tests/SampleApp.Domain/SampleApp.Domain.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + From 9dbae96caf56ffe094799203d3bbe1bc518e2b82 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 16:34:38 -0300 Subject: [PATCH 19/51] tighten clock criteria --- .../Environment/DateAndTime/AdjustableClock.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs index 506ae3fa..cb7694f7 100644 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs +++ b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs @@ -22,11 +22,17 @@ public AdjustableClock(IClock clockImplementation) public void OverrideUtcNow(DateTime utcNow) { Logger.LogTrace("Adjusting clock to {UtcNow}", utcNow); - if (utcNow.Kind != DateTimeKind.Utc) + if (utcNow.Kind == DateTimeKind.Unspecified) { + Logger.LogWarning("Overriding UtcNow with a date time value of unspecified kind. Assuming kind:Utc"); utcNow = new DateTime(utcNow.Ticks, DateTimeKind.Utc); } - + + if (utcNow.Kind == DateTimeKind.Local) + { + throw new ArgumentException("When overriding the UtcNow value you have to provide a DateTime value of kind Utc"); + } + _overriddenUtcNow = utcNow; } From 3eedb93192ba3635a319db59bcb2ffb9946c19b7 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 16:35:36 -0300 Subject: [PATCH 20/51] Prefer generic service descriptors --- .../DependencyInjection/DomainModule.cs | 22 ++----- .../Integration/MessageBusModule.cs | 6 +- .../Bootstrapping/EfCorePersistenceModule.cs | 58 ++++-------------- .../Bootstrapping/EfCorePersistenceModule.cs | 59 +++++-------------- 4 files changed, 35 insertions(+), 110 deletions(-) diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs index f396f76c..8ead6efc 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs @@ -47,22 +47,12 @@ public void Register(ICompositionRoot compositionRoot) private static void RegisterDomainInfrastructureServices(ICompositionRoot compositionRoot) { - compositionRoot.Register( - new ServiceDescriptor(typeof(IClock), _ => new WallClock(), ServiceLifetime.Scoped)); - compositionRoot.Register( - new ServiceDescriptor(typeof(IOperation), _ => new Operation(), ServiceLifetime.Scoped)); - compositionRoot.Register( - new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentCorrelationHolder(), - ServiceLifetime.Scoped)); - compositionRoot.Register( - new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentIdentityHolder(), - ServiceLifetime.Scoped)); - compositionRoot.Register( - new ServiceDescriptor(typeof(ICurrentTHolder), _ => new CurrentTenantIdHolder(), - ServiceLifetime.Scoped)); - compositionRoot.Register( - new ServiceDescriptor(typeof(IDomainEventAggregator), sp => new DomainEventAggregator(sp), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped()); + compositionRoot.Register(ServiceDescriptor.Scoped()); + compositionRoot.Register(ServiceDescriptor.Scoped,CurrentCorrelationHolder>()); + compositionRoot.Register(ServiceDescriptor.Scoped,CurrentIdentityHolder>()); + compositionRoot.Register(ServiceDescriptor.Scoped,CurrentTenantIdHolder>()); + compositionRoot.Register(ServiceDescriptor.Scoped(sp => new DomainEventAggregator(sp))); } private void RegisterDomainAndApplicationServices(ICompositionRoot container) diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs index 9632dbac..eac556c2 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs @@ -25,13 +25,11 @@ public void Register(ICompositionRoot compositionRoot) // register the message bus scope compositionRoot.Register( - new ServiceDescriptor( - typeof(IMessageBusScope), + ServiceDescriptor.Scoped( sp => new MessageBusScope( _messageBus, sp.GetRequiredService>(), - sp.GetRequiredService>()), - ServiceLifetime.Scoped)); + sp.GetRequiredService>()))); // register typed handlers foreach (Type integrationEventType in _assemblies.GetImplementingTypes(typeof(IIntegrationEvent))) diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index f8cc71cb..bac56b00 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -16,7 +16,7 @@ namespace Backend.Fx.EfCore5Persistence.Bootstrapping { public class EfCorePersistenceModule : IModule where TDbContext : DbContext - where TIdGenerator : IEntityIdGenerator + where TIdGenerator : class, IEntityIdGenerator { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; @@ -66,44 +66,27 @@ public EfCorePersistenceModule( public void Register(ICompositionRoot compositionRoot) { // singleton id generator - compositionRoot.Register( - new ServiceDescriptor( - typeof(IEntityIdGenerator), - typeof(TIdGenerator), - ServiceLifetime.Singleton)); + compositionRoot.Register(ServiceDescriptor.Singleton()); // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly - compositionRoot.Register( - new ServiceDescriptor( - typeof(IDbConnection), - _ => _dbConnectionFactory.Create(), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped(_ => _dbConnectionFactory.Create())); // EF core requires us to flush frequently, because of a missing identity map - compositionRoot.Register( - new ServiceDescriptor( - typeof(ICanFlush), - typeof(EfFlush), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped()); // DbContext is injected into repositories (not TDbContext!) - compositionRoot.Register( - new ServiceDescriptor( - typeof(DbContext), - typeof(TDbContext), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped()); // TDbContext ctor requires DbContextOptions, which is configured to use a container managed db connection compositionRoot.Register( - new ServiceDescriptor(typeof(DbContextOptions), + ServiceDescriptor.Scoped>( sp => { var dbContextOptionsBuilder = new DbContextOptionsBuilder(); var dbConnection = sp.GetRequiredService(); _configure.Invoke(dbContextOptionsBuilder, dbConnection); - return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; - }, ServiceLifetime.Scoped)); + })); // loop through aggregate root types to... foreach (var aggregateRootType in _aggregateRootTypes) @@ -147,28 +130,13 @@ public void Register(ICompositionRoot compositionRoot) // flush // | // end invoke <- connection.close <- transaction.commit <-+ - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IOperation), - typeof(FlushOperationDecorator), - ServiceLifetime.Scoped)); - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IOperation), - typeof(DbContextTransactionOperationDecorator), - ServiceLifetime.Scoped)); - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IOperation), - typeof(DbConnectionOperationDecorator), - ServiceLifetime.Scoped)); - + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + // ensure everything dirty is flushed to the db before handling domain events - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IDomainEventAggregator), - typeof(FlushDomainEventAggregatorDecorator), - ServiceLifetime.Scoped)); + compositionRoot.RegisterDecorator(ServiceDescriptor + .Scoped()); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index bf4fc1b7..cda52290 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -11,12 +11,13 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +// ReSharper disable RedundantTypeArgumentsOfMethod namespace Backend.Fx.EfCore6Persistence.Bootstrapping { public class EfCorePersistenceModule : IModule where TDbContext : DbContext - where TIdGenerator : IEntityIdGenerator + where TIdGenerator : class, IEntityIdGenerator { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; @@ -66,44 +67,27 @@ public EfCorePersistenceModule( public void Register(ICompositionRoot compositionRoot) { // singleton id generator - compositionRoot.Register( - new ServiceDescriptor( - typeof(IEntityIdGenerator), - typeof(TIdGenerator), - ServiceLifetime.Singleton)); + compositionRoot.Register(ServiceDescriptor.Singleton()); // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly - compositionRoot.Register( - new ServiceDescriptor( - typeof(IDbConnection), - _ => _dbConnectionFactory.Create(), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped(_ => _dbConnectionFactory.Create())); // EF core requires us to flush frequently, because of a missing identity map - compositionRoot.Register( - new ServiceDescriptor( - typeof(ICanFlush), - typeof(EfFlush), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped()); // DbContext is injected into repositories (not TDbContext!) - compositionRoot.Register( - new ServiceDescriptor( - typeof(DbContext), - typeof(TDbContext), - ServiceLifetime.Scoped)); + compositionRoot.Register(ServiceDescriptor.Scoped()); // TDbContext ctor requires DbContextOptions, which is configured to use a container managed db connection compositionRoot.Register( - new ServiceDescriptor(typeof(DbContextOptions), + ServiceDescriptor.Scoped>( sp => { var dbContextOptionsBuilder = new DbContextOptionsBuilder(); var dbConnection = sp.GetRequiredService(); _configure.Invoke(dbContextOptionsBuilder, dbConnection); - return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; - }, ServiceLifetime.Scoped)); + })); // loop through aggregate root types to... foreach (var aggregateRootType in _aggregateRootTypes) @@ -147,28 +131,13 @@ public void Register(ICompositionRoot compositionRoot) // flush // | // end invoke <- connection.close <- transaction.commit <-+ - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IOperation), - typeof(FlushOperationDecorator), - ServiceLifetime.Scoped)); - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IOperation), - typeof(DbContextTransactionOperationDecorator), - ServiceLifetime.Scoped)); - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IOperation), - typeof(DbConnectionOperationDecorator), - ServiceLifetime.Scoped)); - + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + // ensure everything dirty is flushed to the db before handling domain events - compositionRoot.RegisterDecorator( - new ServiceDescriptor( - typeof(IDomainEventAggregator), - typeof(FlushDomainEventAggregatorDecorator), - ServiceLifetime.Scoped)); + compositionRoot.RegisterDecorator(ServiceDescriptor + .Scoped()); } } } \ No newline at end of file From f391a88791e43f9610ae8531ed8a68b46ef3cb29 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 17:33:52 -0300 Subject: [PATCH 21/51] late configuration of Simple Injector container --- .../MicrosoftCompositionRoot.cs | 26 +-- .../MicrosoftServiceProviderModule.cs | 22 +++ .../SimpleInjectorCompositionRoot.cs | 159 +++++++++--------- .../Integration/TheMessageBusApplication.cs | 11 +- 4 files changed, 119 insertions(+), 99 deletions(-) create mode 100644 src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs index 2203095c..625cb31e 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -13,7 +13,6 @@ namespace Backend.Fx.MicrosoftDependencyInjection public class MicrosoftCompositionRoot : CompositionRoot { private static readonly ILogger Logger = Log.Create(); - private readonly IServiceCollection _serviceCollection = new ServiceCollection(); private readonly Lazy _serviceProvider; public MicrosoftCompositionRoot() @@ -21,15 +20,22 @@ public MicrosoftCompositionRoot() _serviceProvider = new Lazy(() => { Logger.LogInformation("Building Microsoft ServiceProvider"); - return _serviceCollection.BuildServiceProvider(); + return ServiceCollection.BuildServiceProvider( + new ServiceProviderOptions + { + ValidateScopes = true, + ValidateOnBuild = true + }); }); } + public IServiceCollection ServiceCollection { get; } = new ServiceCollection(); + public override IServiceProvider ServiceProvider => _serviceProvider.Value; public override void Verify() { - // ensure creation of lazy service provider + // ensure creation of lazy service provider, this will trigger the validation var unused = _serviceProvider.Value; } @@ -40,25 +46,25 @@ public override void Register(ServiceDescriptor serviceDescriptor) throw new InvalidOperationException("Service provider has been built and cannot be changed any more."); } - var existingRegistration = _serviceCollection + var existingRegistration = ServiceCollection .SingleOrDefault(sd => sd.ServiceType == serviceDescriptor.ServiceType); - + if (existingRegistration == null) { serviceDescriptor.LogDetails(Logger, "Adding"); - _serviceCollection.Add(serviceDescriptor); + ServiceCollection.Add(serviceDescriptor); } else { serviceDescriptor.LogDetails(Logger, "Replacing"); - _serviceCollection.Replace(serviceDescriptor); + ServiceCollection.Replace(serviceDescriptor); } } public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) { serviceDescriptor.LogDetails(Logger, "Adding decorator"); - _serviceCollection.Decorate(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType); + ServiceCollection.Decorate(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType); } public override void RegisterCollection(IEnumerable serviceDescriptors) @@ -70,11 +76,11 @@ public override void RegisterCollection(IEnumerable serviceDe Logger.Warn("Skipping registration of empty collection"); return; } - + foreach (var serviceDescriptor in serviceDescriptorArray) { serviceDescriptor.LogDetails(Logger, "Adding"); - _serviceCollection.Add(serviceDescriptor); + ServiceCollection.Add(serviceDescriptor); } } diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs new file mode 100644 index 00000000..2733e4dc --- /dev/null +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs @@ -0,0 +1,22 @@ +using Backend.Fx.Logging; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace Backend.Fx.MicrosoftDependencyInjection +{ + public abstract class MicrosoftServiceProviderModule : IModule + { + private static readonly ILogger Logger = Log.Create(); + + public virtual void Register(ICompositionRoot compositionRoot) + { + Logger.LogDebug("Registering {Module}", GetType().Name); + var microsoftCompositionRoot = (MicrosoftCompositionRoot) compositionRoot; + Register(microsoftCompositionRoot.ServiceCollection); + } + + protected abstract void Register(IServiceCollection serviceCollection); + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index 0a0077d6..c4dbd93a 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -18,7 +18,6 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection public class SimpleInjectorCompositionRoot : CompositionRoot { private static readonly ILogger Logger = Log.Create(); - private readonly Lazy _container; private readonly IList _services = new List(); private readonly IList _decorators = new List(); private readonly IList _serviceCollections = new List(); @@ -37,85 +36,23 @@ public SimpleInjectorCompositionRoot( { Logger.LogInformation("Initializing SimpleInjector"); ScopedLifestyle = scopedLifestyle; - _container = new Lazy(() => - { - Logger.LogInformation("Building SimpleInjector Container"); - var container = new Container(); - container.Options.LifestyleSelectionBehavior = lifestyleBehavior; - container.Options.DefaultScopedLifestyle = ScopedLifestyle; - - foreach (var serviceDescriptor in _services) - { - serviceDescriptor.LogDetails(Logger, "Adding"); - - if (serviceDescriptor.ImplementationType != null) - { - container.Register( - serviceDescriptor.ServiceType, - serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); - } - else if (serviceDescriptor.ImplementationFactory != null) - { - container.Register( - serviceDescriptor.ServiceType, - () => serviceDescriptor.ImplementationFactory(container), - serviceDescriptor.Lifetime.Translate()); - } - else if (serviceDescriptor.ImplementationInstance != null && - serviceDescriptor.Lifetime == ServiceLifetime.Singleton) - { - container.RegisterInstance(serviceDescriptor.ServiceType, - serviceDescriptor.ImplementationInstance); - } - else - { - throw new InvalidOperationException("Bad service descriptor"); - } - } - - foreach (var serviceDescriptor in _decorators) - { - serviceDescriptor.LogDetails(Logger, "Adding decorator"); - - container.RegisterDecorator( - serviceDescriptor.ServiceType, - serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); - } - - foreach (var serviceDescriptors in _serviceCollections) - { - Logger.Debug("Adding {Lifetime} collection registration: {ServiceType}: {ImplementationType}", - serviceDescriptors[0].Lifetime.ToString(), - serviceDescriptors[0].ServiceType.Name, - $"[{string.Join(",", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))}]"); - - foreach (var serviceDescriptor in serviceDescriptors) - { - container.Collection.Append( - serviceDescriptor.ServiceType, - serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); - } - } - // needed to support extension method IServiceProvider.CreateScope() - container.RegisterInstance(new SimpleInjectorServiceScopeFactory(container)); + Container.Options.LifestyleSelectionBehavior = lifestyleBehavior; + Container.Options.DefaultScopedLifestyle = scopedLifestyle; - return container; - }); + // required to support extension method IServiceProvider.CreateScope() + Container.RegisterInstance(new SimpleInjectorServiceScopeFactory(Container)); } public ScopedLifestyle ScopedLifestyle { get; } - public Container Container => _container.Value; + public Container Container { get; } = new Container(); #region ICompositionRoot implementation public override void Register(ServiceDescriptor serviceDescriptor) { - if (_container.IsValueCreated) + if (Container.IsLocked) { throw new InvalidOperationException("Container has been built and cannot be changed any more."); } @@ -130,7 +67,7 @@ public override void Register(ServiceDescriptor serviceDescriptor) public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) { - if (_container.IsValueCreated) + if (Container.IsLocked) { throw new InvalidOperationException("Container has been built and cannot be changed any more."); } @@ -140,7 +77,7 @@ public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) public override void RegisterCollection(IEnumerable serviceDescriptors) { - if (_container.IsValueCreated) + if (Container.IsLocked) { throw new InvalidOperationException("Container has been built and cannot be changed any more."); } @@ -164,22 +101,16 @@ public override void RegisterCollection(IEnumerable serviceDe public override void Verify() { - Logger.LogInformation("container is being verified"); - try - { - _container.Value.Verify(VerificationOption.VerifyAndDiagnose); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "container configuration invalid"); - throw; - } + FillContainer(); + + Logger.LogInformation("Verifying container"); + Container.Verify(VerificationOption.VerifyAndDiagnose); } /// public override IServiceScope BeginScope() { - return _container.Value.CreateScope(); + return Container.CreateScope(); } public override IServiceProvider ServiceProvider => Container; @@ -195,15 +126,75 @@ public Lifestyle SelectLifestyle(Type implementationType) } } + private void FillContainer() + { + Logger.LogInformation("Registering services with container"); + foreach (var serviceDescriptor in _services) + { + serviceDescriptor.LogDetails(Logger, "Adding"); + + if (serviceDescriptor.ImplementationType != null) + { + Container.Register( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + else if (serviceDescriptor.ImplementationFactory != null) + { + Container.Register( + serviceDescriptor.ServiceType, + () => serviceDescriptor.ImplementationFactory(Container), + serviceDescriptor.Lifetime.Translate()); + } + else if (serviceDescriptor.ImplementationInstance != null && + serviceDescriptor.Lifetime == ServiceLifetime.Singleton) + { + Container.RegisterInstance(serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationInstance); + } + else + { + throw new InvalidOperationException("Bad service descriptor"); + } + } + + foreach (var serviceDescriptor in _decorators) + { + serviceDescriptor.LogDetails(Logger, "Adding decorator"); + + Container.RegisterDecorator( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + + foreach (var serviceDescriptors in _serviceCollections) + { + Logger.Debug("Adding {Lifetime} collection registration: {ServiceType}: {ImplementationType}", + serviceDescriptors[0].Lifetime.ToString(), + serviceDescriptors[0].ServiceType.Name, + $"[{string.Join(",", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))}]"); + + foreach (var serviceDescriptor in serviceDescriptors) + { + Container.Collection.Append( + serviceDescriptor.ServiceType, + serviceDescriptor.ImplementationType, + serviceDescriptor.Lifetime.Translate()); + } + } + } + #endregion #region IDisposable implementation protected override void Dispose(bool disposing) { - if (disposing && _container.IsValueCreated) + if (disposing) { - _container.Value.Dispose(); + Container.Dispose(); } } diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs index 02fb40a9..66741df0 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs @@ -10,10 +10,12 @@ namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { public class TheMessageBusApplication { - [Fact] - public async Task Boots() + [Theory] + [InlineData(CompositionRootType.Microsoft)] + [InlineData(CompositionRootType.SimpleInjector)] + public async Task Boots(CompositionRootType compositionRootType) { - var app = new MessageBusTestApplication(CompositionRootType.Microsoft); + var app = new MessageBusTestApplication(compositionRootType); await app.BootAsync(); } } @@ -25,8 +27,7 @@ public MessageBusTestApplication(CompositionRootType compositionRootType) new InMemoryMessageBus(), new BackendFxApplication( compositionRootType.Create(), - A.Fake(), - typeof(MessageBusTestApplication).Assembly)) + A.Fake())) { } } From b08c11d09d5e5b71ae46a861b72412da5d72dd7f Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 18:46:00 -0300 Subject: [PATCH 22/51] r# greening --- .../BuildingBlocks/IAsyncRepository.cs | 2 + .../Backend.Fx/BuildingBlocks/IRepository.cs | 2 + .../Backend.Fx/BuildingBlocks/IView.cs | 3 + .../MultiTenancy/SingleTenantApplication.cs | 2 + .../Environment/MultiTenancy/TenantService.cs | 2 + .../Backend.Fx/Exceptions/ClientException.cs | 3 + .../Exceptions/ConflictedException.cs | 2 + .../Backend.Fx/Exceptions/Errors.cs | 2 + .../Backend.Fx/Exceptions/ExceptionBuilder.cs | 3 + .../Exceptions/ForbiddenException.cs | 2 + .../Exceptions/NotFoundException.cs | 5 +- .../Exceptions/TooManyRequestsException.cs | 2 + .../Exceptions/UnauthorizedException.cs | 2 + .../Exceptions/UnprocessableException.cs | 2 + .../Backend.Fx/Extensions/AsyncHelper.cs | 16 +++-- .../Extensions/MultipleDisposable.cs | 2 + .../ReaderWriterLockSlimExtensions.cs | 2 + .../Backend.Fx/Extensions/ReflectionEx.cs | 2 + .../Backend.Fx/Extensions/StringEx.cs | 2 + .../Extensions/TolerantDateTimeComparer.cs | 3 + .../Backend.Fx/Hacking/PrivateUtil.cs | 2 + .../Logging/DebugExceptionLogger.cs | 2 + .../Backend.Fx/Logging/DurationLogger.cs | 2 + .../Backend.Fx/Logging/ExceptionExtensions.cs | 2 + .../Backend.Fx/Logging/ExceptionLoggers.cs | 2 + src/abstractions/Backend.Fx/Logging/Log.cs | 2 + .../BackendFxToMicrosoftLoggingLogger.cs | 3 +- ...ackendFxToMicrosoftLoggingLoggerFactory.cs | 1 + .../Logging/Obsolete/DebugLogger.cs | 1 + .../Logging/Obsolete/DebugLoggerFactory.cs | 1 + .../Backend.Fx/Logging/Obsolete/ILogger.cs | 1 + .../Logging/Obsolete/ILoggerFactory.cs | 4 +- .../Logging/Obsolete/LegacyExceptionLogger.cs | 1 + .../Backend.Fx/Logging/Obsolete/LogManager.cs | 1 + .../Logging/Obsolete/LoggerExtensions.cs | 1 + .../Authorization/IAggregateAuthorization.cs | 2 + .../DependencyInjection/CurrentTHolder.cs | 2 + .../Patterns/DependencyInjection/Operation.cs | 6 +- .../Integration/IIntegrationMessageHandler.cs | 6 +- .../Integration/IMessageNameProvider.cs | 1 + .../Integration/IntegrationEvent.cs | 2 + .../Patterns/IdGeneration/ISequence.cs | 5 +- .../Patterns/Jobs/WithTenantWideMutex.cs | 2 + .../RandomData/LandLineGenerator.cs | 2 + .../Backend.Fx/RandomData/Letters.cs | 5 +- .../Backend.Fx/RandomData/LinqExtensions.cs | 8 +-- .../Backend.Fx/RandomData/LoremIpsum.cs | 7 +-- .../RandomData/MobileLineGenerator.cs | 2 + .../Backend.Fx/RandomData/Names.cs | 5 +- .../RandomData/TestAddressGenerator.cs | 2 + .../Backend.Fx/RandomData/TestChemical.cs | 2 + .../Backend.Fx/RandomData/TestPerson.cs | 2 + .../RandomData/TestPersonGenerator.cs | 4 +- .../Backend.Fx/RandomData/TestRandom.cs | 2 + .../Backend.Fx.AspNetCore.csproj | 4 ++ .../Configuration/ConfigurationEx.cs | 4 +- .../JsonErrorHandlingMiddleware.cs | 5 +- .../Backend.Fx.AspNetCore/HttpRequestEx.cs | 4 +- .../Backend.Fx.AspNetCore/HttpResponseEx.cs | 2 +- .../MultiTenancy/HttpContextEx.cs | 2 +- .../MultiTenancy/SingleTenantMiddleware.cs | 2 + .../MultiTenancy/TenantAdminMiddlewareBase.cs | 6 +- ...BackendFxApplicationControllerActivator.cs | 2 + .../BackendFxApplicationHubActivator.cs | 2 + ...kendFxApplicationViewComponentActivator.cs | 2 + .../Mvc/Execution/FlushFilter.cs | 21 ------- .../ExceptionThrottlingAttribute.cs | 4 +- .../Mvc/Throttling/ThrottlingAttribute.cs | 2 + .../Mvc/Validation/ModelValidationFilter.cs | 10 ++-- ...ectBackToGetActionModelValidationFilter.cs | 2 + ...rnModelStateAsJsonModelValidationFilter.cs | 2 + .../Security/ContentSecurityPolicyOptions.cs | 9 --- .../Security/SecurityHeadersMiddleware.cs | 59 ------------------- .../Security/SecurityHeadersOptions.cs | 8 --- .../Bootstrapping/EfCorePersistenceModule.cs | 1 + .../Backend.Fx.EfCore5Persistence/EfFlush.cs | 2 +- .../EfRepository.cs | 26 +++----- .../EntityQueryable.cs | 1 + .../Mssql/MsSqlSequence.cs | 2 + .../Oracle/OracleSequence.cs | 2 + .../PlainAggregateMapping.cs | 2 +- .../Postgres/PostgresSequence.cs | 2 + .../Backend.Fx.EfCore6Persistence/EfFlush.cs | 2 +- .../Mssql/MsSqlSequence.cs | 2 + .../Oracle/OracleSequence.cs | 2 + .../PlainAggregateMapping.cs | 2 +- .../Postgres/PostgresSequence.cs | 2 + .../InMemoryEntityIdGenerator.cs | 9 ++- .../InMemoryQueryable.cs | 2 + .../InMemoryRepository.cs | 2 + .../InMemoryView.cs | 2 + .../MicrosoftServiceProviderModule.cs | 2 + .../SimpleInjectorModule.cs | 2 + .../TheMultiTenantApplication.cs | 2 +- 94 files changed, 213 insertions(+), 162 deletions(-) delete mode 100644 src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs delete mode 100644 src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs delete mode 100644 src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs delete mode 100644 src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs index 9365d5c0..5af37ee6 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Backend.Fx.BuildingBlocks { @@ -10,6 +11,7 @@ namespace Backend.Fx.BuildingBlocks /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks /// /// + [PublicAPI] public interface IAsyncRepository where TAggregateRoot : AggregateRoot { Task SingleAsync(int id, CancellationToken cancellationToken = default); diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs index 19ba3187..675022cc 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.BuildingBlocks { @@ -8,6 +9,7 @@ namespace Backend.Fx.BuildingBlocks /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks /// /// + [PublicAPI] public interface IRepository where TAggregateRoot : AggregateRoot { TAggregateRoot Single(int id); diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs index 0121addb..71df6627 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs @@ -3,13 +3,16 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using JetBrains.Annotations; namespace Backend.Fx.BuildingBlocks { + [PublicAPI] public interface IView : IQueryable { } + [PublicAPI] public abstract class View : IView { private readonly IQueryable _viewImplementation; diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs index 95fc06a9..26de3bf9 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs @@ -1,10 +1,12 @@ using System.Linq; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Environment.MultiTenancy { + [PublicAPI] public class SingleTenantApplication : BackendFxApplicationDecorator { private static readonly ILogger Logger = Log.Create(); diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs index b89394cf..09fee0f1 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs +++ b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs @@ -2,6 +2,7 @@ using System.Linq; using Backend.Fx.Exceptions; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -11,6 +12,7 @@ namespace Backend.Fx.Environment.MultiTenancy /// Encapsulates the management of tenants /// Note that this should not use repositories and other building blocks, but access the persistence layer directly /// + [PublicAPI] public interface ITenantService { /// diff --git a/src/abstractions/Backend.Fx/Exceptions/ClientException.cs b/src/abstractions/Backend.Fx/Exceptions/ClientException.cs index dc32894d..9b199d6e 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ClientException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ClientException.cs @@ -5,6 +5,7 @@ namespace Backend.Fx.Exceptions { + [PublicAPI] public class ClientException : Exception { public ClientException() @@ -25,6 +26,7 @@ public ClientException(string message) /// to the client to not provide internal details to an attacker. Write the exception message with a developer in mind, since /// the application log will contain the message. To provide the user with functional feedback to correct their input, use /// the AddError(s) overloads. + /// public ClientException(string message, Exception innerException) : base(message, innerException) { @@ -68,6 +70,7 @@ public static IExceptionBuilder UseBuilder() } } + [PublicAPI] public static class ClientExceptionEx { public static TEx AddError(this TEx clientException, [LocalizationRequired] string errorMessage) where TEx : ClientException diff --git a/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs b/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs index 541d5ef9..b2c042cc 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ConflictedException.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public class ConflictedException : ClientException { public ConflictedException() diff --git a/src/abstractions/Backend.Fx/Exceptions/Errors.cs b/src/abstractions/Backend.Fx/Exceptions/Errors.cs index 1aeae363..432b48cd 100644 --- a/src/abstractions/Backend.Fx/Exceptions/Errors.cs +++ b/src/abstractions/Backend.Fx/Exceptions/Errors.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public class Errors : IReadOnlyDictionary { private const string GenericErrorKey = ""; diff --git a/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs b/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs index 79084a40..adefe44d 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public interface IExceptionBuilder : IDisposable { void Add(string error); @@ -11,6 +13,7 @@ public interface IExceptionBuilder : IDisposable void AddIf(string key, bool condition, string error); } + [PublicAPI] public class ExceptionBuilder : IExceptionBuilder where TEx : ClientException, new() { private readonly TEx _clientException = new TEx(); diff --git a/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs b/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs index e5db75a1..d174ba47 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public class ForbiddenException : ClientException { public ForbiddenException() diff --git a/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs b/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs index cc3b2350..85c4a170 100644 --- a/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/NotFoundException.cs @@ -1,5 +1,8 @@ -namespace Backend.Fx.Exceptions +using JetBrains.Annotations; + +namespace Backend.Fx.Exceptions { + [PublicAPI] public class NotFoundException : ClientException { public string EntityName { get; } diff --git a/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs b/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs index 42dd5f7c..0bf02898 100644 --- a/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/TooManyRequestsException.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public class TooManyRequestsException : ClientException { public TooManyRequestsException(int retryAfter) diff --git a/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs b/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs index f365a9f2..f97816c0 100644 --- a/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/UnauthorizedException.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public class UnauthorizedException : ClientException { public UnauthorizedException() diff --git a/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs b/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs index 018108a6..23b04088 100644 --- a/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/UnprocessableException.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Exceptions { + [PublicAPI] public class UnprocessableException : ClientException { public UnprocessableException() diff --git a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs b/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs index 2a9f552f..95270634 100644 --- a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs +++ b/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; namespace Backend.Fx.Extensions { + [PublicAPI] public static class AsyncHelper { /// @@ -20,9 +22,10 @@ public static void RunSync(Func task) SynchronizationContext oldContext = SynchronizationContext.Current; try { - var synch = new ExclusiveSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(synch); - synch.Post(async _ => + var exclusiveSynchronizationContext = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(exclusiveSynchronizationContext); + // ReSharper disable once AsyncVoidLambda + exclusiveSynchronizationContext.Post(async _ => { try { @@ -30,15 +33,15 @@ public static void RunSync(Func task) } catch (Exception e) { - synch.InnerException = e; + exclusiveSynchronizationContext.InnerException = e; throw; } finally { - synch.EndMessageLoop(); + exclusiveSynchronizationContext.EndMessageLoop(); } }, null); - synch.BeginMessageLoop(); + exclusiveSynchronizationContext.BeginMessageLoop(); } finally { @@ -60,6 +63,7 @@ public static T RunSync(Func> task) { var exclusiveSynchronizationContext = new ExclusiveSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(exclusiveSynchronizationContext); + // ReSharper disable once AsyncVoidLambda exclusiveSynchronizationContext.Post(async _ => { try diff --git a/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs b/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs index df5b80d5..c51bfc5d 100644 --- a/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs +++ b/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs @@ -1,7 +1,9 @@ using System; +using JetBrains.Annotations; namespace Backend.Fx.Extensions { + [PublicAPI] public class MultipleDisposable : IDisposable { private readonly IDisposable[] _disposables; diff --git a/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs b/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs index 7ad0645d..02100115 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs +++ b/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs @@ -1,8 +1,10 @@ using System; using System.Threading; +using JetBrains.Annotations; namespace Backend.Fx.Extensions { + [PublicAPI] public static class ReaderWriterLockSlimExtensions { private sealed class ReadLockToken : IDisposable diff --git a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs index a862570f..b3d90984 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using JetBrains.Annotations; namespace Backend.Fx.Extensions { + [PublicAPI] public static class ReflectionEx { public static IEnumerable GetImplementingTypes(this IEnumerable assemblies) diff --git a/src/abstractions/Backend.Fx/Extensions/StringEx.cs b/src/abstractions/Backend.Fx/Extensions/StringEx.cs index 54091267..18d7f27a 100644 --- a/src/abstractions/Backend.Fx/Extensions/StringEx.cs +++ b/src/abstractions/Backend.Fx/Extensions/StringEx.cs @@ -1,7 +1,9 @@ using System.Text.RegularExpressions; +using JetBrains.Annotations; namespace Backend.Fx.Extensions { + [PublicAPI] public static class StringEx { public static string Cut(this string s, int length) diff --git a/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs b/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs index cb401099..65910aeb 100644 --- a/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs +++ b/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; namespace Backend.Fx.Extensions { + [PublicAPI] public class TolerantDateTimeOffsetComparer : IEqualityComparer { private readonly TimeSpan _epsilon; @@ -27,6 +29,7 @@ public int GetHashCode(DateTimeOffset? obj) } } + [PublicAPI] public class TolerantDateTimeComparer : IEqualityComparer { private readonly TimeSpan _epsilon; diff --git a/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs b/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs index a2cb535c..dba42007 100644 --- a/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs +++ b/src/abstractions/Backend.Fx/Hacking/PrivateUtil.cs @@ -1,9 +1,11 @@ using System; using System.Linq; using System.Reflection; +using JetBrains.Annotations; namespace Backend.Fx.Hacking { + [PublicAPI] public static class PrivateUtil { public static T CreateInstanceFromPrivateDefaultConstructor() diff --git a/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs b/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs index 8db9eee5..11b1e703 100644 --- a/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DebugExceptionLogger.cs @@ -1,9 +1,11 @@ using System; using System.Diagnostics; using Backend.Fx.Exceptions; +using JetBrains.Annotations; namespace Backend.Fx.Logging { + [PublicAPI] public class DebugExceptionLogger : IExceptionLogger { public void LogException(Exception exception) diff --git a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs index 0fdf2be0..b4d9ac0d 100644 --- a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs @@ -1,9 +1,11 @@ using System; using System.Diagnostics; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; namespace Backend.Fx.Logging { + [PublicAPI] [DebuggerStepThrough] public class DurationLogger : IDisposable { diff --git a/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs b/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs index 2d4d1859..22c2a751 100644 --- a/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs +++ b/src/abstractions/Backend.Fx/Logging/ExceptionExtensions.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.Logging { + [PublicAPI] [DebuggerStepThrough] public static class ExceptionExtensions { diff --git a/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs b/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs index 18584133..7a6dd561 100644 --- a/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs +++ b/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs @@ -1,10 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; namespace Backend.Fx.Logging { + [PublicAPI] public class ExceptionLoggers : ICollection, IExceptionLogger { private static readonly Microsoft.Extensions.Logging.ILogger Logger = Log.Create(); diff --git a/src/abstractions/Backend.Fx/Logging/Log.cs b/src/abstractions/Backend.Fx/Logging/Log.cs index 17a2435b..57c5bb1c 100644 --- a/src/abstractions/Backend.Fx/Logging/Log.cs +++ b/src/abstractions/Backend.Fx/Logging/Log.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using Backend.Fx.Extensions; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -9,6 +10,7 @@ namespace Backend.Fx.Logging /// /// static class to keep an ILoggerFactory instance to use Microsoft.Extension.Logging without dependency injection /// + [PublicAPI] public static class Log { private static Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory = new NullLoggerFactory(); diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs index c4733bcf..4ac07425 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs @@ -2,10 +2,11 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [Obsolete] - public class BackendFxToMicrosoftLoggingLogger : Backend.Fx.Logging.ILogger + public class BackendFxToMicrosoftLoggingLogger : ILogger { private readonly Microsoft.Extensions.Logging.ILogger _logger; diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs index c42397cd..38c076b9 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs @@ -1,5 +1,6 @@ using System; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [Obsolete] diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs index 754213fa..8a271411 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [Obsolete] diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs index a53d24be..1b29b0bd 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [DebuggerStepThrough] diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs index 489076e8..ffe0354f 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs @@ -2,6 +2,7 @@ using System; using JetBrains.Annotations; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [Obsolete] diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs index 18179a4f..553734b3 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs @@ -1,8 +1,10 @@ using System; +using JetBrains.Annotations; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { - [Obsolete] + [Obsolete, PublicAPI] public interface ILoggerFactory { ILogger Create(string s); diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs index 964dff0a..6c2b4d8a 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs @@ -1,6 +1,7 @@ using System; using Backend.Fx.Exceptions; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [Obsolete] diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs index 22426643..6d22d982 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using Microsoft.Extensions.Logging.Abstractions; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs index 131c3235..c1b1bbe4 100644 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs +++ b/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs @@ -2,6 +2,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; +// ReSharper disable CheckNamespace namespace Backend.Fx.Logging { [Obsolete] diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs b/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs index 0eb6773b..f911e064 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Linq.Expressions; using Backend.Fx.BuildingBlocks; +using JetBrains.Annotations; namespace Backend.Fx.Patterns.Authorization { @@ -9,6 +10,7 @@ namespace Backend.Fx.Patterns.Authorization /// so that the repository never allows reading or writing of an aggregate without permissions. /// /// + [PublicAPI] public interface IAggregateAuthorization where TAggregateRoot : AggregateRoot { /// diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs index 5b9abc40..02baa49a 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs @@ -1,4 +1,5 @@ using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -8,6 +9,7 @@ namespace Backend.Fx.Patterns.DependencyInjection /// Holds a current instance of T that might be replaced during the scope /// /// + [PublicAPI] public interface ICurrentTHolder where T : class { T Current { get; } diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs index 56e0a2a2..62883929 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -10,6 +11,7 @@ namespace Backend.Fx.Patterns.DependencyInjection /// Decorate this interface to provide operation specific infrastructure services (like a database connection, a database transaction /// an entry-exit logging etc.) /// + [PublicAPI] public interface IOperation { void Begin(); @@ -27,7 +29,7 @@ public class Operation : IOperation private bool? _isActive; private IDisposable _lifetimeLogger; - public virtual void Begin() + public void Begin() { if (_isActive != null) { @@ -38,7 +40,7 @@ public virtual void Begin() _isActive = true; } - public virtual void Complete() + public void Complete() { Logger.LogInformation("Completing operation #{OperationId}", _instanceId); if (_isActive != true) diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs index 2c2a0e1c..8bc1a749 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs @@ -1,10 +1,14 @@ -namespace Backend.Fx.Patterns.EventAggregation.Integration +using JetBrains.Annotations; + +namespace Backend.Fx.Patterns.EventAggregation.Integration { + [PublicAPI] public interface IIntegrationMessageHandler { void Handle(dynamic eventData); } + [PublicAPI] public interface IIntegrationMessageHandler where TEvent : IIntegrationEvent { void Handle(TEvent eventData); diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs index cd7883a0..ec7f3007 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs @@ -3,6 +3,7 @@ namespace Backend.Fx.Patterns.EventAggregation.Integration { + [PublicAPI] public interface IMessageNameProvider { [NotNull] diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs index 6ea7cb8a..93689ee3 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs @@ -1,8 +1,10 @@ using System; using Backend.Fx.Environment.MultiTenancy; +using JetBrains.Annotations; namespace Backend.Fx.Patterns.EventAggregation.Integration { + [PublicAPI] public interface IIntegrationEvent { Guid Id { get; } diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs b/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs index 02a4516b..d806ef6a 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs +++ b/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs @@ -1,5 +1,8 @@ -namespace Backend.Fx.Patterns.IdGeneration +using JetBrains.Annotations; + +namespace Backend.Fx.Patterns.IdGeneration { + [PublicAPI] public interface ISequence { void EnsureSequence(); diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs b/src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs index 43bb1fcb..4aa3e1b3 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs +++ b/src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs @@ -1,11 +1,13 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Patterns.Jobs { + [PublicAPI] public class WithTenantWideMutex : IJob where TJob : IJob { private static readonly ILogger Logger = Log.Create>(); diff --git a/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs b/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs index 996f4f02..b8986a06 100644 --- a/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs @@ -1,7 +1,9 @@ using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public class LandLineGenerator : Generator { public static string Generate() diff --git a/src/abstractions/Backend.Fx/RandomData/Letters.cs b/src/abstractions/Backend.Fx/RandomData/Letters.cs index ee079429..675ca88d 100644 --- a/src/abstractions/Backend.Fx/RandomData/Letters.cs +++ b/src/abstractions/Backend.Fx/RandomData/Letters.cs @@ -1,5 +1,8 @@ -namespace Backend.Fx.RandomData +using JetBrains.Annotations; + +namespace Backend.Fx.RandomData { + [PublicAPI] public static class Letters { public static string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; diff --git a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs index 8fcce937..e9a3f331 100644 --- a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs +++ b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs @@ -4,9 +4,11 @@ using System.Linq.Dynamic.Core; using System.Reflection; using Backend.Fx.BuildingBlocks; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public static class LinqExtensions { /// @@ -25,9 +27,7 @@ public static IEnumerable Shuffle(this IEnumerable source) while (n > 1) { var k = TestRandom.Instance.Next(n--); - T temp = sourceAsArray[n]; - sourceAsArray[n] = sourceAsArray[k]; - sourceAsArray[k] = temp; + (sourceAsArray[n], sourceAsArray[k]) = (sourceAsArray[k], sourceAsArray[n]); } return sourceAsArray; @@ -83,7 +83,7 @@ private static bool TryAsQueryable(this IEnumerable source, out IQueryable count = sourceQueryable.Count(); if (count == 0) { - outQueryable = new T[0].AsQueryable(); + outQueryable = Array.Empty().AsQueryable(); return true; } diff --git a/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs b/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs index f4546be8..9caec52f 100644 --- a/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs +++ b/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs @@ -4,8 +4,7 @@ namespace Backend.Fx.RandomData { public class LoremIpsumGenerator : Generator { - private static string[] _words = new[] - { + private static readonly string[] Words = { "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "proin", "eget", "iaculis", "quam", "pellentesque", "elementum", "gravida", "nulla", "at", "tincidunt", "donec", "vulputate", "velit", "sapien", "a", "auctor", "justo", "id", "nunc", "et", "consequat", "magna", "in", "blandit", "ut", "eros", "tempus", "condimentum", "sem", "ac", "feugiat", "tellus", "curabitur", "aliquet", "ultrices", "arcu", "eu", "lacinia", "aliquam", "integer", "non", "venenatis", @@ -20,10 +19,10 @@ public class LoremIpsumGenerator : Generator protected override string Next() { - return _words[TestRandom.Instance.Next(_words.Length)]; + return Words[TestRandom.Instance.Next(Words.Length)]; } - public static string Generate(int minWords, int maxWords, bool asSentence) + public static string Generate(int minWords, int maxWords, bool asSentence = true) { int wordCount = TestRandom.Next(minWords, maxWords); string loremIpsumText = string.Join(" ", new LoremIpsumGenerator().Take(wordCount)); diff --git a/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs b/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs index 094e7a25..6088306a 100644 --- a/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs @@ -1,7 +1,9 @@ using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public class MobileLineGenerator : Generator { public static string Generate() diff --git a/src/abstractions/Backend.Fx/RandomData/Names.cs b/src/abstractions/Backend.Fx/RandomData/Names.cs index 31e5bdd4..caebebf6 100644 --- a/src/abstractions/Backend.Fx/RandomData/Names.cs +++ b/src/abstractions/Backend.Fx/RandomData/Names.cs @@ -1,5 +1,8 @@ -namespace Backend.Fx.RandomData +using JetBrains.Annotations; + +namespace Backend.Fx.RandomData { + [PublicAPI] public static class Names { public static readonly string[] LegalEntityTypes = diff --git a/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs index 588d99f2..6028f793 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs @@ -1,7 +1,9 @@ using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public class TestAddressGenerator : Generator { public static TestAddress Generate() diff --git a/src/abstractions/Backend.Fx/RandomData/TestChemical.cs b/src/abstractions/Backend.Fx/RandomData/TestChemical.cs index af09d77e..bd617b47 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestChemical.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestChemical.cs @@ -1,8 +1,10 @@ using System; using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public class TestChemical { public TestChemical(string name, string description, string alternativeNames, string formula, decimal molecularWeight, string casRegistryNumber, string molFile) diff --git a/src/abstractions/Backend.Fx/RandomData/TestPerson.cs b/src/abstractions/Backend.Fx/RandomData/TestPerson.cs index 94886676..ed253807 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestPerson.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestPerson.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using Backend.Fx.BuildingBlocks; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public class TestPerson : ValueObject { public enum Genders diff --git a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs index 7e623e20..9f1af527 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs @@ -1,16 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public class TestPersonGenerator : Generator { private const int Year = 365; private readonly Random _random = TestRandom.Instance; private readonly HashSet _uniqueNames = new HashSet(); - public bool EnforceUniqueNames { get; set; } = false; + public bool EnforceUniqueNames { get; set; } public int FemalePercentage { get; set; } = 55; public int MaximumAgeInDays { get; set; } = 80 * Year; public int MinimumAgeInDays { get; set; } = 18 * Year; diff --git a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs index eba65ed5..0e80d9af 100644 --- a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs +++ b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; namespace Backend.Fx.RandomData { + [PublicAPI] public static class TestRandom { public static Random Instance { get; set; } = new Random(429756); diff --git a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj index eb1c529b..13380ec4 100644 --- a/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj +++ b/src/environments/Backend.Fx.AspNetCore/Backend.Fx.AspNetCore.csproj @@ -38,4 +38,8 @@ + + + + diff --git a/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs b/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs index d01f9edf..4c3a6f6d 100644 --- a/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/Configuration/ConfigurationEx.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.Configuration; +using JetBrains.Annotations; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; namespace Backend.Fx.AspNetCore.Configuration { + [PublicAPI] public static class ConfigurationEx { public static TOptions Load(this IConfiguration configuration) where TOptions : class, new() diff --git a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs index 1267abb1..dc8445d5 100644 --- a/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/ErrorHandling/JsonErrorHandlingMiddleware.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Backend.Fx.Exceptions; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -14,6 +15,7 @@ namespace Backend.Fx.AspNetCore.ErrorHandling { + [PublicAPI] public class JsonErrorHandlingMiddleware : ErrorHandlingMiddleware { private readonly bool _showInternalServerErrorDetails; @@ -37,7 +39,7 @@ protected override Task ShouldHandle(HttpContext context) { // this middleware only handles requests that accept json as response IList accept = context.Request.GetTypedHeaders().Accept; - return Task.FromResult(accept?.Any(mth => mth.Type == "application" && mth.SubType == "json") == true); + return Task.FromResult(accept.Any(mth => mth.Type == "application" && mth.SubType == "json")); } protected override async Task HandleClientError(HttpContext context, int httpStatusCode, string message, ClientException exception) @@ -83,6 +85,7 @@ protected virtual string SerializeErrors(Errors errors) } } + [PublicAPI] public class ErrorShape { public Dictionary Errors { get; set; } diff --git a/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs b/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs index d1c337dd..39a19d2c 100644 --- a/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/HttpRequestEx.cs @@ -1,7 +1,9 @@ -using Microsoft.AspNetCore.Http; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Http; namespace Backend.Fx.AspNetCore { + [PublicAPI] public static class HttpRequestEx { /// diff --git a/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs b/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs index 01355ab0..02f83437 100644 --- a/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/HttpResponseEx.cs @@ -20,7 +20,7 @@ public static async Task WriteJsonAsync(this HttpResponse response, object o, Js public static async Task WriteJsonAsync(this HttpResponse response, string json, string contentType = null) { - response.ContentType = (contentType ?? "application/json; charset=UTF-8"); + response.ContentType = contentType ?? "application/json; charset=UTF-8"; await response.WriteAsync(json); await response.Body.FlushAsync(); } diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs index 09876ccb..73b3c011 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/HttpContextEx.cs @@ -12,7 +12,7 @@ public static void SetCurrentTenantId(this HttpContext httpContext, TenantId ten { if (httpContext.Items.TryGetValue(TenantId, out object untyped)) { - throw new InvalidOperationException($"TenantId has been set already in this HttpContext. Value: {(untyped ?? "null")}"); + throw new InvalidOperationException($"TenantId has been set already in this HttpContext. Value: {untyped ?? "null"}"); } httpContext.Items[TenantId] = tenantId; diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs index 92a18301..2f9c64f4 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/SingleTenantMiddleware.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using JetBrains.Annotations; using Microsoft.AspNetCore.Http; namespace Backend.Fx.AspNetCore.MultiTenancy @@ -7,6 +8,7 @@ namespace Backend.Fx.AspNetCore.MultiTenancy /// /// Always assumes TenantId: 1 for all requests. /// + [PublicAPI] public class SingleTenantMiddleware { private readonly RequestDelegate _next; diff --git a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs index 55a771d4..2574c8dd 100644 --- a/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs +++ b/src/environments/Backend.Fx.AspNetCore/MultiTenancy/TenantAdminMiddlewareBase.cs @@ -6,6 +6,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -13,6 +14,7 @@ namespace Backend.Fx.AspNetCore.MultiTenancy { + [PublicAPI] public abstract class TenantAdminMiddlewareBase { private static readonly ILogger Logger = Log.Create(); @@ -69,8 +71,8 @@ public async Task Invoke(HttpContext context) if (HttpMethods.IsGet(context.Request.Method)) { - var tenantIdStr = context.Request.Path.Value.Split('/').Last(); - if (int.TryParse(tenantIdStr, out int tenantId)) + var tenantIdStr = context.Request.Path.Value?.Split('/').Last(); + if (tenantIdStr != null && int.TryParse(tenantIdStr, out int tenantId)) { Logger.LogInformation("Getting Tenant[{TenantId}]", tenantId); diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs index 02209493..d86ca546 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationControllerActivator.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; @@ -13,6 +14,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators /// http context items dictionary. If non is to be found, the controller is activated /// using the default (without providing any ctor arguments). /// + [PublicAPI] public class BackendFxApplicationControllerActivator : IControllerActivator { private static readonly ILogger Logger = Log.Create(); diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs index aa04fa25..529953d2 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs @@ -1,6 +1,7 @@ using System; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -8,6 +9,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators { + [PublicAPI] public class BackendFxApplicationHubActivator : IHubActivator where T : Hub { private readonly IBackendFxApplication _backendFxApplication; diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs index 2bce7e08..15e42cde 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationViewComponentActivator.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -7,6 +8,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Activators { + [PublicAPI] public class BackendFxApplicationViewComponentActivator : IViewComponentActivator { private static readonly ILogger Logger = Log.Create(); diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs deleted file mode 100644 index 315a9247..00000000 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Execution/FlushFilter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Backend.Fx.Environment.Persistence; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.AspNetCore.Mvc.Execution -{ - /// - /// Makes sure that possible dirty objects are flushed to the persistence layer when the MVC action was executed. This will reveal - /// persistence related problems early and makes them easier to diagnose. - /// - public class FlushFilter : IActionFilter - { - public void OnActionExecuting(ActionExecutingContext context) - { } - - public void OnActionExecuted(ActionExecutedContext context) - { - context.HttpContext.GetServiceProvider().GetRequiredService().Flush(); - } - } -} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs index 4ed56721..0362aa46 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ExceptionThrottlingAttribute.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Exceptions; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -7,9 +8,10 @@ namespace Backend.Fx.AspNetCore.Mvc.Throttling { /// - /// returns HTTP 429 "Too many requests" when the attributed action get's called from the same IP address in less than + /// returns HTTP 429 "Too many requests" when the attributed action gets called from the same IP address in less than /// the configured interval and an exception was thrown. Useful to prevent brute force attacks.. /// + [PublicAPI] public class ExceptionThrottlingAttribute : ThrottlingBaseAttribute { public override void OnActionExecuted(ActionExecutedContext actionContext) diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs index 031a53f8..d4bf4a47 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Throttling/ThrottlingAttribute.cs @@ -1,5 +1,6 @@ using System; using Backend.Fx.Exceptions; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -10,6 +11,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Throttling /// returns HTTP 429 "Too many requests" when the attributed action get's called from the same IP address in less than /// the configured interval. Useful to prevent denial of service attacks. /// + [PublicAPI] public class ThrottlingAttribute : ThrottlingBaseAttribute { public override void OnActionExecuting(ActionExecutingContext actionContext) diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs index 247238d3..4bd473bc 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ModelValidationFilter.cs @@ -17,7 +17,7 @@ public abstract class ModelValidationFilter : IActionFilter public abstract void OnActionExecuting(ActionExecutingContext context); public abstract void OnActionExecuted(ActionExecutedContext context); - protected void LogErrors(FilterContext context, string controllerName, Errors errors) + protected static void LogErrors(FilterContext context, string controllerName, Errors errors) { ILogger logger = TryGetControllerType(controllerName, out Type controllerType) ? Log.Create(controllerType) @@ -28,16 +28,16 @@ protected void LogErrors(FilterContext context, string controllerName, Errors er errors); } - protected bool AcceptsJson(FilterContext context) + protected static bool AcceptsJson(FilterContext context) { IList accept = context.HttpContext.Request.GetTypedHeaders().Accept; - return accept?.Any(mth => mth.Type == "application" && mth.SubType == "json") == true; + return accept.Any(mth => mth.Type == "application" && mth.SubType == "json") == true; } - protected bool AcceptsHtml(FilterContext context) + protected static bool AcceptsHtml(FilterContext context) { IList accept = context.HttpContext.Request.GetTypedHeaders().Accept; - return accept?.Any(mth => mth.Type == "text" && mth.SubType == "html") == true; + return accept.Any(mth => mth.Type == "text" && mth.SubType == "html") == true; } private static bool TryGetControllerType(string controllerName, out Type type) diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs index 851e0b22..77e59604 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/RedirectBackToGetActionModelValidationFilter.cs @@ -1,4 +1,5 @@ using Backend.Fx.Exceptions; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -6,6 +7,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Validation { + [PublicAPI] public class RedirectBackToGetActionModelValidationFilter : ModelValidationFilter { private readonly IModelMetadataProvider _modelMetadataProvider; diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs index 43909a58..717afe97 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Validation/ReturnModelStateAsJsonModelValidationFilter.cs @@ -1,4 +1,5 @@ using Backend.Fx.Exceptions; +using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -8,6 +9,7 @@ namespace Backend.Fx.AspNetCore.Mvc.Validation /// Returns HTTP 400 "Bad Request" when model validation failed. In addition, the bad model state is converted into an instance of /// gets serialized to the body as JSON. /// + [PublicAPI] public class ReturnModelStateAsJsonModelValidationFilter : ModelValidationFilter { public override void OnActionExecuting(ActionExecutingContext context) diff --git a/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs b/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs deleted file mode 100644 index cb04455d..00000000 --- a/src/environments/Backend.Fx.AspNetCore/Security/ContentSecurityPolicyOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Backend.Fx.AspNetCore.Security -{ - public class ContentSecurityPolicyOptions - { - public string ContentSecurityPolicy { get; set; } - public bool ReportOnly { get; set; } - public string ReportUrl { get; set; } - } -} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs deleted file mode 100644 index 595bc347..00000000 --- a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersMiddleware.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading.Tasks; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; - -namespace Backend.Fx.AspNetCore.Security -{ - public class SecurityHeadersMiddleware - { - private readonly RequestDelegate _next; - private readonly IOptions _securityOptionsAccessor; - - - [UsedImplicitly] - public SecurityHeadersMiddleware(RequestDelegate next, IOptions securityOptionsAccessor) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - _securityOptionsAccessor = securityOptionsAccessor; - } - - [UsedImplicitly] - public async Task Invoke(HttpContext context) - { - ContentSecurityPolicyOptions csp = _securityOptionsAccessor.Value.ContentSecurityPolicy; - if (csp?.ContentSecurityPolicy != null && csp.ContentSecurityPolicy.Length > 0) - { - string cspHeaderKey = csp.ReportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy"; - - string completeCsp = csp.ContentSecurityPolicy; - - if (!string.IsNullOrEmpty(csp.ReportUrl) && ShouldAppendReportUri(context)) - { - completeCsp += "; report-uri " + csp.ReportUrl; - } - - context.Response.Headers.Add(cspHeaderKey, new StringValues(completeCsp)); - } - - if (_securityOptionsAccessor.Value.HstsExpiration > 0) - { - context.Response.Headers.Add("Strict-Transport-Security", new StringValues($"max-age={_securityOptionsAccessor.Value.HstsExpiration}")); - } - - await _next.Invoke(context); - } - - /// - /// Override this if you want the user to be able to permit/forbid reporting - /// - /// - /// - protected virtual bool ShouldAppendReportUri(HttpContext context) - { - return true; - } - } -} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs b/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs deleted file mode 100644 index e4a8a0b6..00000000 --- a/src/environments/Backend.Fx.AspNetCore/Security/SecurityHeadersOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Backend.Fx.AspNetCore.Security -{ - public class SecurityHeadersOptions - { - public int HstsExpiration { get; set; } - public ContentSecurityPolicyOptions ContentSecurityPolicy { get; set; } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index bac56b00..ab916025 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +// ReSharper disable RedundantTypeArgumentsOfMethod namespace Backend.Fx.EfCore5Persistence.Bootstrapping { diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs index 8a1f22d0..84a993f0 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs @@ -159,7 +159,7 @@ private static EntityEntry GetAggregateRootEntry(ChangeTracker changeTracker, En if (navigation.CurrentValue == null) { - var navigationMetadata = ((INavigation)navigation.Metadata); + var navigationMetadata = (INavigation)navigation.Metadata; // orphaned entity, original value contains the foreign key value if (navigationMetadata.ForeignKey.Properties.Count > 1) throw new InvalidOperationException("Foreign Keys with multiple properties are not supported."); diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs index 2b784efc..765b8400 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs @@ -19,6 +19,7 @@ namespace Backend.Fx.EfCore5Persistence { + [PublicAPI] public class EfRepository : Repository, IAsyncRepository where TAggregateRoot : AggregateRoot { private static readonly ILogger Logger = Log.Create>(); @@ -27,7 +28,7 @@ public class EfRepository : Repository, IAsyncRe private DbContext _dbContext; [SuppressMessage("ReSharper", "EF1001")] - public EfRepository([CanBeNull] DbContext dbContext, IAggregateMapping aggregateMapping, + public EfRepository(DbContext dbContext, IAggregateMapping aggregateMapping, ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) : base(currentTenantIdHolder, aggregateAuthorization) { @@ -36,25 +37,14 @@ public EfRepository([CanBeNull] DbContext dbContext, IAggregateMapping(); - localViewListener?.RegisterView(AuthorizeChanges); + var localViewListener = dbContext.GetService(); + localViewListener.RegisterView(AuthorizeChanges); } - [SuppressMessage("ReSharper", "EF1001")] - public DbContext DbContext - { - get => _dbContext ?? throw new InvalidOperationException( - "This EfRepository does not have a DbContext yet. You might either make sure a proper DbContext gets injected or the DbContext is initialized later using a derived class") - ; - protected set - { - if (_dbContext != null) throw new InvalidOperationException("This EfRepository has already a DbContext assigned. It is not allowed to change it later."); - _dbContext = value; - var localViewListener = _dbContext?.GetService(); - localViewListener?.RegisterView(AuthorizeChanges); - } - } - + public DbContext DbContext => + _dbContext ?? throw new InvalidOperationException( + "This EfRepository does not have a DbContext yet. You might either make sure a proper DbContext gets injected or the DbContext is initialized later using a derived class"); + public async Task SingleAsync(int id, CancellationToken cancellationToken = default) { return await AggregateQueryable.SingleAsync(agg => agg.Id == id, cancellationToken); diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs index f3d60231..87e90311 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs @@ -9,6 +9,7 @@ namespace Backend.Fx.EfCore5Persistence { + [PublicAPI] public class EntityQueryable : IQueryable where TEntity : Entity { [CanBeNull] private DbContext _dbContext; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs index 041671e9..e2cd28ef 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs @@ -3,11 +3,13 @@ using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore5Persistence.Mssql { + [PublicAPI] public abstract class MsSqlSequence : ISequence { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs index 9bcc3592..3ba1e455 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs @@ -3,11 +3,13 @@ using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore5Persistence.Oracle { + [PublicAPI] public abstract class OracleSequence : ISequence { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs index fe15600c..5e07ff6d 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.EfCore5Persistence public class PlainAggregateMapping : AggregateMapping where TAggregateRoot : AggregateRoot { - public override IEnumerable>> IncludeDefinitions => new Expression>[0]; + public override IEnumerable>> IncludeDefinitions => Array.Empty>>(); public override void ApplyEfMapping(ModelBuilder modelBuilder) { diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs index e759bdca..0d087cef 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs @@ -3,11 +3,13 @@ using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore5Persistence.Postgres { + [PublicAPI] public abstract class PostgresSequence : ISequence { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs index 1ba2584f..e3da4708 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs @@ -159,7 +159,7 @@ private static EntityEntry GetAggregateRootEntry(ChangeTracker changeTracker, En if (navigation.CurrentValue == null) { - var navigationMetadata = ((INavigation)navigation.Metadata); + var navigationMetadata = (INavigation)navigation.Metadata; // orphaned entity, original value contains the foreign key value if (navigationMetadata.ForeignKey.Properties.Count > 1) throw new InvalidOperationException("Foreign Keys with multiple properties are not supported."); diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs index 1642e25b..df0102a1 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs @@ -3,11 +3,13 @@ using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore6Persistence.Mssql { + [PublicAPI] public abstract class MsSqlSequence : ISequence { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs index 30ed350c..28a1771b 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs @@ -3,11 +3,13 @@ using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore6Persistence.Oracle { + [PublicAPI] public abstract class OracleSequence : ISequence { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs index 880177df..3f198947 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.EfCore6Persistence public class PlainAggregateMapping : AggregateMapping where TAggregateRoot : AggregateRoot { - public override IEnumerable>> IncludeDefinitions => new Expression>[0]; + public override IEnumerable>> IncludeDefinitions => Array.Empty>>(); public override void ApplyEfMapping(ModelBuilder modelBuilder) { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs index cda6b248..e66142a0 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs @@ -3,11 +3,13 @@ using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.Logging; using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore6Persistence.Postgres { + [PublicAPI] public abstract class PostgresSequence : ISequence { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs index 1f9f54fd..56ea0b55 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs @@ -1,14 +1,13 @@ using Backend.Fx.Patterns.IdGeneration; +using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence { - public class InMemoryEntityIdGenerator : IEntityIdGenerator + [PublicAPI] + public class InMemoryEntityIdGenerator : SequenceHiLoIdGenerator { - private int _nextId = 1; - - public int NextId() + public InMemoryEntityIdGenerator() : base(new InMemorySequence()) { - return _nextId++; } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs index 9797c793..9435fbcc 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs @@ -4,9 +4,11 @@ using System.Linq; using System.Linq.Expressions; using Backend.Fx.BuildingBlocks; +using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence { + [PublicAPI] public class InMemoryQueryable : IQueryable where TAggregateRoot : AggregateRoot { private readonly IQueryable _queryableImplementation; diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs index 07708f7a..ee777a23 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs @@ -5,9 +5,11 @@ using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.RandomData; +using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence { + [PublicAPI] public class InMemoryRepository : Repository where T : AggregateRoot { public InMemoryRepository(IInMemoryStore store, ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs index cd4ff074..8ef21b5c 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs @@ -4,9 +4,11 @@ using System.Linq; using System.Linq.Expressions; using Backend.Fx.BuildingBlocks; +using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence { + [PublicAPI] public class InMemoryView : IView { private readonly IList _list; diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs index 2733e4dc..8a53318a 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs @@ -1,11 +1,13 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.MicrosoftDependencyInjection { + [PublicAPI] public abstract class MicrosoftServiceProviderModule : IModule { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs index 3d1f699b..082c98fd 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs @@ -1,11 +1,13 @@ using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; using SimpleInjector; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.SimpleInjectorDependencyInjection { + [PublicAPI] public abstract class SimpleInjectorModule : IModule { private static readonly ILogger Logger = Log.Create(); diff --git a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs b/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs index 1057d5e5..185cccbf 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/TheMultiTenantApplication.cs @@ -15,7 +15,7 @@ public class TheMultiTenantApplication: TestWithLogging public TheMultiTenantApplication(ITestOutputHelper output) : base(output) { - _factory = new SampleAppWebApplicationFactory(base.Logger); + _factory = new SampleAppWebApplicationFactory(Logger); } [Fact] From 1d5b1aa3e6aac0bc58138bbcc9a454d4d493a863 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 19:17:47 -0300 Subject: [PATCH 23/51] injection of IDbConnectionFactory required --- .../Bootstrapping/EfCorePersistenceModule.cs | 3 +++ .../Bootstrapping/EfCorePersistenceModule.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index ab916025..c6563fe0 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -68,6 +68,9 @@ public void Register(ICompositionRoot compositionRoot) { // singleton id generator compositionRoot.Register(ServiceDescriptor.Singleton()); + + // at least the id generator implementation requires the IDbConnectionFactory + compositionRoot.Register(ServiceDescriptor.Singleton(_dbConnectionFactory)); // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly compositionRoot.Register(ServiceDescriptor.Scoped(_ => _dbConnectionFactory.Create())); diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index cda52290..995b832f 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -69,6 +69,9 @@ public void Register(ICompositionRoot compositionRoot) // singleton id generator compositionRoot.Register(ServiceDescriptor.Singleton()); + // at least the id generator implementation requires the IDbConnectionFactory + compositionRoot.Register(ServiceDescriptor.Singleton(_dbConnectionFactory)); + // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly compositionRoot.Register(ServiceDescriptor.Scoped(_ => _dbConnectionFactory.Create())); From 79f9bfdde07185c95a1db99181c3e8a214e5f4c8 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 19:31:24 -0300 Subject: [PATCH 24/51] Settings feature switchable --- .../Persistence/PersistentApplication.cs | 3 ++- .../Bootstrapping/EfCorePersistenceModule.cs | 16 ++++++++++++++-- .../Bootstrapping/EfCorePersistenceModule.cs | 12 ++++++++++++ .../SampleApp/Runtime/SampleAppBuilder.cs | 1 + .../SampleApp/Runtime/SampleAppBuilder.cs | 1 + 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs b/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs index 2a52975d..c7f7d8f0 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs +++ b/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs @@ -14,7 +14,8 @@ public class PersistentApplication : BackendFxApplicationDecorator private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; private readonly IDatabaseBootstrapper _databaseBootstrapper; - public PersistentApplication(IDatabaseBootstrapper databaseBootstrapper, + public PersistentApplication( + IDatabaseBootstrapper databaseBootstrapper, IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, IModule persistenceModule, IBackendFxApplication application) : base(application) diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index c6563fe0..77ced0af 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Backend.Fx.BuildingBlocks; +using Backend.Fx.ConfigurationSettings; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; @@ -21,6 +22,7 @@ public class EfCorePersistenceModule : IModule { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; + private readonly bool _withPersistentSettings; private readonly IDbConnectionFactory _dbConnectionFactory; private readonly Type[] _aggregateRootTypes; private readonly Type[] _entityTypes; @@ -30,11 +32,13 @@ public EfCorePersistenceModule( IDbConnectionFactory dbConnectionFactory, ILoggerFactory loggerFactory, Action, IDbConnection> configure, + bool withPersistentSettings, params Assembly[] assemblies) { _dbConnectionFactory = dbConnectionFactory; _loggerFactory = loggerFactory; _configure = configure; + _withPersistentSettings = withPersistentSettings; _aggregateRootTypes = assemblies .SelectMany(ass => ass @@ -68,10 +72,10 @@ public void Register(ICompositionRoot compositionRoot) { // singleton id generator compositionRoot.Register(ServiceDescriptor.Singleton()); - + // at least the id generator implementation requires the IDbConnectionFactory compositionRoot.Register(ServiceDescriptor.Singleton(_dbConnectionFactory)); - + // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly compositionRoot.Register(ServiceDescriptor.Scoped(_ => _dbConnectionFactory.Create())); @@ -92,6 +96,14 @@ public void Register(ICompositionRoot compositionRoot) return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; })); + if (_withPersistentSettings) + { + compositionRoot.Register( + ServiceDescriptor.Scoped, EfRepository>()); + compositionRoot.Register( + ServiceDescriptor.Scoped, PlainAggregateMapping>()); + } + // loop through aggregate root types to... foreach (var aggregateRootType in _aggregateRootTypes) { diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index 995b832f..7eba4bc5 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Backend.Fx.BuildingBlocks; +using Backend.Fx.ConfigurationSettings; using Backend.Fx.Environment.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; @@ -21,6 +22,7 @@ public class EfCorePersistenceModule : IModule { private readonly ILoggerFactory _loggerFactory; private readonly Action, IDbConnection> _configure; + private readonly bool _withPersistentSettings; private readonly IDbConnectionFactory _dbConnectionFactory; private readonly Type[] _aggregateRootTypes; private readonly Type[] _entityTypes; @@ -30,11 +32,13 @@ public EfCorePersistenceModule( IDbConnectionFactory dbConnectionFactory, ILoggerFactory loggerFactory, Action, IDbConnection> configure, + bool withPersistentSettings, params Assembly[] assemblies) { _dbConnectionFactory = dbConnectionFactory; _loggerFactory = loggerFactory; _configure = configure; + _withPersistentSettings = withPersistentSettings; _aggregateRootTypes = assemblies .SelectMany(ass => ass @@ -92,6 +96,14 @@ public void Register(ICompositionRoot compositionRoot) return dbContextOptionsBuilder.UseLoggerFactory(_loggerFactory).Options; })); + if (_withPersistentSettings) + { + compositionRoot.Register( + ServiceDescriptor.Scoped, EfRepository>()); + compositionRoot.Register( + ServiceDescriptor.Scoped, PlainAggregateMapping>()); + } + // loop through aggregate root types to... foreach (var aggregateRootType in _aggregateRootTypes) { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index 3a1764ef..1ab0ed9f 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -33,6 +33,7 @@ public static IBackendFxApplication Build(CompositionRootType compositionRootTyp dbConnectionFactory, A.Fake(), (builder, connection) => builder.UseSqlite((DbConnection)connection), + false, application.Assemblies ), application); diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index 544eb782..0624381f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -33,6 +33,7 @@ public static IBackendFxApplication Build(CompositionRootType compositionRootTyp dbConnectionFactory, A.Fake(), (builder, connection) => builder.UseSqlite((DbConnection)connection), + false, application.Assemblies ), application); From 4e57954bfbdf04d732ff6649e12c6169344fcd64 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 19:37:16 -0300 Subject: [PATCH 25/51] auto register authorization --- .../Bootstrapping/EfCorePersistenceModule.cs | 3 +++ .../Bootstrapping/EfCorePersistenceModule.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index 77ced0af..41e770e0 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -6,6 +6,7 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; @@ -102,6 +103,8 @@ public void Register(ICompositionRoot compositionRoot) ServiceDescriptor.Scoped, EfRepository>()); compositionRoot.Register( ServiceDescriptor.Scoped, PlainAggregateMapping>()); + compositionRoot.Register( + ServiceDescriptor.Scoped, AllowAll>()); } // loop through aggregate root types to... diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index 7eba4bc5..c0338188 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -6,6 +6,7 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.IdGeneration; @@ -102,6 +103,8 @@ public void Register(ICompositionRoot compositionRoot) ServiceDescriptor.Scoped, EfRepository>()); compositionRoot.Register( ServiceDescriptor.Scoped, PlainAggregateMapping>()); + compositionRoot.Register( + ServiceDescriptor.Scoped, AllowAll>()); } // loop through aggregate root types to... From efc1bf7f17a1a97146d2164e638c499d29f3ad30 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 19:50:19 -0300 Subject: [PATCH 26/51] InMem Persistence Module --- .../InMemoryEntityIdGenerator.cs | 2 +- .../InMemoryFlush.cs | 10 +++ .../InMemoryPersistenceModule.cs | 71 +++++++++++++++++++ .../SampleApp/Runtime/SampleAppBuilder.cs | 1 + 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs create mode 100644 src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs index 56ea0b55..d8b105a7 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs @@ -4,7 +4,7 @@ namespace Backend.Fx.InMemoryPersistence { [PublicAPI] - public class InMemoryEntityIdGenerator : SequenceHiLoIdGenerator + public class InMemoryEntityIdGenerator : SequenceHiLoIdGenerator, IEntityIdGenerator { public InMemoryEntityIdGenerator() : base(new InMemorySequence()) { diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs new file mode 100644 index 00000000..9308a23c --- /dev/null +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs @@ -0,0 +1,10 @@ +using Backend.Fx.Environment.Persistence; + +namespace Backend.Fx.InMemoryPersistence +{ + public class InMemoryFlush : ICanFlush + { + public void Flush() + { } + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs new file mode 100644 index 00000000..447d2012 --- /dev/null +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs @@ -0,0 +1,71 @@ +using System; +using System.Linq; +using System.Reflection; +using Backend.Fx.BuildingBlocks; +using Backend.Fx.ConfigurationSettings; +using Backend.Fx.Environment.Persistence; +using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.IdGeneration; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.InMemoryPersistence +{ + public class InMemoryPersistenceModule : IModule + { + private readonly bool _withPersistentSettings; + private readonly Type[] _aggregateRootTypes; + + public InMemoryPersistenceModule(bool withPersistentSettings, params Assembly[] assemblies) + { + _withPersistentSettings = withPersistentSettings; + + _aggregateRootTypes = assemblies + .SelectMany(ass => ass + .GetExportedTypes() + .Where(t => !t.IsAbstract && t.IsClass) + .Where(t => typeof(AggregateRoot).IsAssignableFrom(t))) + .ToArray(); + } + + public void Register(ICompositionRoot compositionRoot) + { + if (_withPersistentSettings) + { + compositionRoot.Register( + ServiceDescriptor.Scoped, InMemoryRepository>()); + compositionRoot.Register( + ServiceDescriptor.Scoped, AllowAll>()); + compositionRoot.Register( + ServiceDescriptor.Singleton, InMemoryStore>()); + } + + compositionRoot.Register(ServiceDescriptor.Scoped()); + + compositionRoot.Register( + ServiceDescriptor.Singleton()); + + // loop through aggregate root types to... + foreach (var aggregateRootType in _aggregateRootTypes) + { + // register the singleton store + var genericStoreInterface = typeof(IInMemoryStore<>).MakeGenericType(aggregateRootType); + var genericStoreImplementation = typeof(InMemoryStore<>).MakeGenericType(aggregateRootType); + compositionRoot.Register( + new ServiceDescriptor( + genericStoreInterface, + genericStoreImplementation, + ServiceLifetime.Singleton)); + + // ... register the Entity Framework implementation of IRepository + var genericRepositoryInterface = typeof(IRepository<>).MakeGenericType(aggregateRootType); + var genericRepositoryImplementation = typeof(InMemoryRepository<>).MakeGenericType(aggregateRootType); + compositionRoot.Register( + new ServiceDescriptor( + genericRepositoryInterface, + genericRepositoryImplementation, + ServiceLifetime.Scoped)); + } + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index 0624381f..08fe6fbf 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -2,6 +2,7 @@ using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Persistence; +using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; From 2e80cad87bb2fed4d9cc922c97bf642089502661 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Fri, 17 Jun 2022 20:07:16 -0300 Subject: [PATCH 27/51] fix: raising messages after operation completion --- .../Integration/MessageBusApplication.cs | 7 --- .../Integration/MessageBusModule.cs | 4 ++ .../RaiseIntegrationEventsInvokerDecorator.cs | 58 ------------------- ...aiseIntegrationEventsOperationDecorator.cs | 37 ++++++++++++ 4 files changed, 41 insertions(+), 65 deletions(-) delete mode 100644 src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs create mode 100644 src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs index 679ba2e2..480a47a1 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs @@ -11,9 +11,6 @@ public class MessageBusApplication : BackendFxApplicationDecorator public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication application) : base(application) { - Invoker = new RaiseIntegrationEventsInvokerDecorator(application.CompositionRoot, base.Invoker); - AsyncInvoker = new RaiseIntegrationEventsAsyncInvokerDecorator(application.CompositionRoot, base.AsyncInvoker); - application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); _messageBus = messageBus; _messageBus.ProvideInvoker( @@ -27,10 +24,6 @@ public override async Task BootAsync(CancellationToken cancellationToken = defau _messageBus.Connect(); } - public override IBackendFxApplicationInvoker Invoker { get; } - - public override IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } - protected override void Dispose(bool disposing) { if (disposing) diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs index eac556c2..c1db1a1a 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs @@ -44,6 +44,10 @@ public void Register(ICompositionRoot compositionRoot) ServiceLifetime.Scoped)); } } + + // make sure all integration events are raised after completing an operation, but before ending the scope + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs deleted file mode 100644 index 4d40cf8e..00000000 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsInvokerDecorator.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.Patterns.EventAggregation.Integration -{ - public class RaiseIntegrationEventsInvokerDecorator : IBackendFxApplicationInvoker - { - private readonly ICompositionRoot _compositionRoot; - private readonly IBackendFxApplicationInvoker _invoker; - - public RaiseIntegrationEventsInvokerDecorator( - ICompositionRoot compositionRoot, - IBackendFxApplicationInvoker invoker) - { - _compositionRoot = compositionRoot; - _invoker = invoker; - } - - public void Invoke( - Action action, - IIdentity identity, - TenantId tenantId, - Guid? correlationId = null) - { - _invoker.Invoke(action, identity, tenantId, correlationId); - AsyncHelper.RunSync(() => _compositionRoot.ServiceProvider.GetRequiredService().RaiseEvents()); - } - } - - public class RaiseIntegrationEventsAsyncInvokerDecorator : IBackendFxApplicationAsyncInvoker - { - private readonly ICompositionRoot _compositionRoot; - private readonly IBackendFxApplicationAsyncInvoker _invoker; - - public RaiseIntegrationEventsAsyncInvokerDecorator( - ICompositionRoot compositionRoot, - IBackendFxApplicationAsyncInvoker invoker) - { - _compositionRoot = compositionRoot; - _invoker = invoker; - } - - public async Task InvokeAsync( - Func awaitableAsyncAction, - IIdentity identity, - TenantId tenantId, - Guid? correlationId = null) - { - await _invoker.InvokeAsync(awaitableAsyncAction, identity, tenantId, correlationId).ConfigureAwait(false); - await _compositionRoot.ServiceProvider.GetRequiredService().RaiseEvents().ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs new file mode 100644 index 00000000..ccfa203a --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs @@ -0,0 +1,37 @@ +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; + +namespace Backend.Fx.Patterns.EventAggregation.Integration +{ + [UsedImplicitly] + public class RaiseIntegrationEventsOperationDecorator : IOperation + { + private readonly IMessageBusScope _messageBusScope; + private readonly IOperation _operation; + + public RaiseIntegrationEventsOperationDecorator( + IMessageBusScope messageBusScope, + IOperation operation) + { + _messageBusScope = messageBusScope; + _operation = operation; + } + + public void Begin() + { + _operation.Begin(); + } + + public void Complete() + { + _operation.Complete(); + AsyncHelper.RunSync(() => _messageBusScope.RaiseEvents()); + } + + public void Cancel() + { + _operation.Cancel(); + } + } +} \ No newline at end of file From fc97eec227102c6afaeba3c69ff9a4aaacc0332f Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Sun, 3 Jul 2022 22:13:40 -0300 Subject: [PATCH 28/51] Organization of Features --- src/abstractions/Backend.Fx/Backend.Fx.csproj | 4 ++ .../Backend.Fx/BuildingBlocks/Repository.cs | 2 +- .../ConfigurationSettings/SettingsService.cs | 2 +- .../Authorization/AggregateAuthorization.cs | 2 +- .../Authorization/AllowAll.cs | 2 +- .../Authorization/AuthorizingApplication.cs | 2 +- .../Authorization/DenyAll.cs | 2 +- .../Authorization/IAggregateAuthorization.cs | 2 +- .../DataGeneratingApplication.cs | 2 +- .../DataGeneration/DataGenerationContext.cs | 2 +- .../DataGeneration/DataGenerationModule.cs | 2 +- .../DataGeneration/DataGenerator.cs | 7 ++-- .../DataGeneration/IDemoDataGenerator.cs | 2 +- .../IProductiveDataGenerator.cs | 2 +- .../DomainEvents}/DomainEventAggregator.cs | 2 +- .../DomainEvents/DomainEventsApplication.cs | 12 ++++++ .../DomainEvents/DomainEventsModule.cs | 41 +++++++++++++++++++ .../DomainEvents}/IDomainEvent.cs | 2 +- .../DomainEvents}/IDomainEventAggregator.cs | 2 +- .../DomainEvents}/IDomainEventHandler.cs | 2 +- .../RaiseDomainEventsOperationDecorator.cs | 36 ++++++++++++++++ .../Jobs/ApplicationWithJobs.cs | 2 +- .../{Patterns => Features}/Jobs/IJob.cs | 2 +- .../{Patterns => Features}/Jobs/JobModule.cs | 2 +- .../Jobs/WithTenantWideMutex.cs | 2 +- .../DelegateIntegrationMessageHandler.cs | 2 +- .../MessageBus}/DynamicSubscription.cs | 2 +- .../MessageBus}/EventProcessingContext.cs | 2 +- .../MessageBus}/IIntegrationMessageHandler.cs | 2 +- .../MessageBus}/IMessageBus.cs | 2 +- .../MessageBus}/IMessageNameProvider.cs | 2 +- .../MessageBus}/ISubscription.cs | 2 +- .../MessageBus}/InMemoryMessageBus.cs | 10 ++--- .../MessageBus}/InMemoryMessageBusChannel.cs | 9 ++-- .../MessageBus}/IntegrationEvent.cs | 2 +- .../MessageBus}/MessageBus.cs | 2 +- .../MessageBus}/MessageBusApplication.cs | 2 +- .../MessageBus}/MessageBusModule.cs | 2 +- .../MessageBus}/MessageBusScope.cs | 9 ++-- ...aiseIntegrationEventsOperationDecorator.cs | 2 +- ...uentializingBackendFxApplicationInvoker.cs | 2 +- .../MessageBus}/SingletonSubscription.cs | 2 +- .../MessageBus}/TypedSubscription.cs | 2 +- .../DbConnectionOperationDecorator.cs | 2 +- .../DbTransactionOperationDecorator.cs | 2 +- .../FlushDomainEventAggregatorDecorator.cs | 4 +- .../Persistence/FlushOperationDecorator.cs | 2 +- .../Persistence}/HiLoIdGenerator.cs | 2 +- .../Persistence/ICanFlush.cs | 2 +- .../IDatabaseAvailabilityAwaiter.cs | 2 +- .../Persistence/IDatabaseBootstrapper.cs | 2 +- .../Persistence}/IEntityIdGenerator.cs | 2 +- .../Persistence}/IIdGenerator.cs | 2 +- .../Persistence}/ISequence.cs | 2 +- .../Persistence/PersistentApplication.cs | 2 +- ...ReadonlyDbTransactionOperationDecorator.cs | 2 +- .../Persistence}/SequenceHiLoIdGenerator.cs | 2 +- .../Persistence}/SequenceIdGenerator.cs | 2 +- .../BackendFxApplicationInvoker.cs | 3 -- .../DependencyInjection/DomainModule.cs | 23 +---------- .../DbContextTransactionOperationDecorator.cs | 2 +- .../Bootstrapping/EfCorePersistenceModule.cs | 7 ++-- .../Backend.Fx.EfCore5Persistence/EfFlush.cs | 2 +- .../EfRepository.cs | 2 +- .../Mssql/MsSqlSequence.cs | 2 +- .../Oracle/OracleSequence.cs | 2 +- .../Postgres/PostgresSequence.cs | 2 +- .../DbContextTransactionOperationDecorator.cs | 2 +- .../Bootstrapping/EfCorePersistenceModule.cs | 7 ++-- .../Backend.Fx.EfCore6Persistence/EfFlush.cs | 2 +- .../EfRepository.cs | 2 +- .../Mssql/MsSqlSequence.cs | 2 +- .../Oracle/OracleSequence.cs | 2 +- .../Postgres/PostgresSequence.cs | 2 +- .../InMemoryEntityIdGenerator.cs | 2 +- .../InMemoryFlush.cs | 2 +- .../InMemoryPersistenceModule.cs | 5 +-- .../InMemoryRepository.cs | 2 +- .../InMemorySequence.cs | 2 +- .../Backend.Fx.RabbitMq/RabbitMQChannel.cs | 2 +- .../Backend.Fx.RabbitMq/RabbitMqMessageBus.cs | 2 +- .../Fixtures/DatabaseFixture.cs | 2 +- .../Fixtures/SqlServerDatabaseFixture.cs | 2 +- .../Fixtures/SqliteDatabaseFixture.cs | 2 +- .../Fixtures/TestDbSession.cs | 2 +- .../Persistence/SampleAppDbBootstrapper.cs | 2 +- .../Persistence/SampleAppIdGenerator.cs | 4 +- .../SampleApp/Runtime/SampleAppBuilder.cs | 2 +- .../TheDbApplicationWithEfCore.cs | 5 +-- .../TheRepositoryOfComposedAggregate.cs | 4 +- .../TheRepositoryOfPlainAggregate.cs | 2 +- .../Fixtures/DatabaseFixture.cs | 2 +- .../Fixtures/SqlServerDatabaseFixture.cs | 2 +- .../Fixtures/SqliteDatabaseFixture.cs | 2 +- .../Fixtures/TestDbSession.cs | 2 +- .../Persistence/SampleAppDbBootstrapper.cs | 2 +- .../Persistence/SampleAppIdGenerator.cs | 4 +- .../SampleApp/Runtime/SampleAppBuilder.cs | 2 +- .../TheDbApplicationWithEfCore.cs | 5 +-- .../TheRepositoryOfComposedAggregate.cs | 4 +- .../TheRepositoryOfPlainAggregate.cs | 2 +- .../TheRabbitMqMessageBus.cs | 2 +- .../BuildingBlocks/TheRepository.cs | 2 +- .../ConfigurationSettings/TheSetting.cs | 2 +- .../TheSettingsService.cs | 4 +- .../MultiTenancy/TheTenantService.cs | 2 +- .../Persistence/ThePersistentApplication.cs | 2 +- .../TheAllowAllImplementation.cs | 2 +- .../TheAuthorizingApplication.cs | 2 +- .../Authorization/TheDenyAllImplementation.cs | 2 +- .../TheDataGenerationContext.cs | 2 +- .../TheGenerateDataOnBootDecorator.cs | 2 +- .../DataGeneration/TheInitialDataGenerator.cs | 2 +- .../DependencyInjection/DITestFakes.cs | 2 +- .../TheBackendFxApplication.cs | 6 +-- .../DependencyInjection/TheDomainModule.cs | 4 +- ...uentializingBackendFxApplicationInvoker.cs | 2 +- .../Domain/FailingDomainEventHandler.cs | 2 +- .../Domain/TestDomainEvent.cs | 2 +- .../Domain/TestDomainEventHandler.cs | 2 +- .../Domain/TestIntegrationEvent.cs | 2 +- .../Domain/TheEventAggregator.cs | 2 +- .../Integration/BlockingMessageHandler.cs | 2 +- .../Integration/DynamicMessageHandler.cs | 2 +- .../Integration/LongRunningMessageHandler.cs | 2 +- .../Integration/SerializingMessageBus.cs | 2 +- .../Integration/TestIntegrationEvent.cs | 2 +- .../TheInMemoryMessageBusChannel.cs | 2 +- .../Integration/TheMessageBus.cs | 2 +- .../Integration/TheMessageBusApplication.cs | 2 +- .../Integration/TheMessageBusScope.cs | 2 +- .../ThrowingDynamicEventHandler.cs | 2 +- .../ThrowingTypedMessageHandler.cs | 2 +- .../Integration/TypedMessageHandler.cs | 2 +- .../IdGeneration/TheHiLoIdGenerator.cs | 2 +- .../TheSequenceHiLoIdGenerator.cs | 2 +- .../IdGeneration/TheSequenceIdGenerator.cs | 2 +- .../Patterns/Jobs/TheApplicationWithJobs.cs | 6 +-- .../TheDbTransactionOperationDecorator.cs | 2 +- .../Transactions/TheReadonlyDecorator.cs | 2 +- tests/SampleApp.Domain/Blog.cs | 2 +- tests/SampleApp.Domain/BlogAuthorization.cs | 2 +- tests/SampleApp.Domain/BlogCreated.cs | 2 +- tests/SampleApp.Domain/BlogCreatedHandler.cs | 2 +- .../SampleApp.Domain/BloggerAuthorization.cs | 2 +- 145 files changed, 264 insertions(+), 201 deletions(-) rename src/abstractions/Backend.Fx/{Patterns => Features}/Authorization/AggregateAuthorization.cs (97%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Authorization/AllowAll.cs (91%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Authorization/AuthorizingApplication.cs (98%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Authorization/DenyAll.cs (91%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Authorization/IAggregateAuthorization.cs (97%) rename src/abstractions/Backend.Fx/{Patterns => Features}/DataGeneration/DataGeneratingApplication.cs (98%) rename src/abstractions/Backend.Fx/{Patterns => Features}/DataGeneration/DataGenerationContext.cs (98%) rename src/abstractions/Backend.Fx/{Patterns => Features}/DataGeneration/DataGenerationModule.cs (94%) rename src/abstractions/Backend.Fx/{Patterns => Features}/DataGeneration/DataGenerator.cs (95%) rename src/abstractions/Backend.Fx/{Patterns => Features}/DataGeneration/IDemoDataGenerator.cs (80%) rename src/abstractions/Backend.Fx/{Patterns => Features}/DataGeneration/IProductiveDataGenerator.cs (79%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Domain => Features/DomainEvents}/DomainEventAggregator.cs (98%) create mode 100644 src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs create mode 100644 src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Domain => Features/DomainEvents}/IDomainEvent.cs (82%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Domain => Features/DomainEvents}/IDomainEventAggregator.cs (87%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Domain => Features/DomainEvents}/IDomainEventHandler.cs (72%) create mode 100644 src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs rename src/abstractions/Backend.Fx/{Patterns => Features}/Jobs/ApplicationWithJobs.cs (91%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Jobs/IJob.cs (83%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Jobs/JobModule.cs (95%) rename src/abstractions/Backend.Fx/{Patterns => Features}/Jobs/WithTenantWideMutex.cs (97%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/DelegateIntegrationMessageHandler.cs (90%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/DynamicSubscription.cs (95%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/EventProcessingContext.cs (85%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/IIntegrationMessageHandler.cs (84%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/IMessageBus.cs (97%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/IMessageNameProvider.cs (84%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/ISubscription.cs (76%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/InMemoryMessageBus.cs (93%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/InMemoryMessageBusChannel.cs (85%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/IntegrationEvent.cs (95%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/MessageBus.cs (99%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/MessageBusApplication.cs (95%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/MessageBusModule.cs (97%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/MessageBusScope.cs (90%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/RaiseIntegrationEventsOperationDecorator.cs (93%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/SequentializingBackendFxApplicationInvoker.cs (94%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/SingletonSubscription.cs (94%) rename src/abstractions/Backend.Fx/{Patterns/EventAggregation/Integration => Features/MessageBus}/TypedSubscription.cs (96%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/DbConnectionOperationDecorator.cs (97%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/DbTransactionOperationDecorator.cs (99%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/FlushDomainEventAggregatorDecorator.cs (92%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/FlushOperationDecorator.cs (95%) rename src/abstractions/Backend.Fx/{Patterns/IdGeneration => Features/Persistence}/HiLoIdGenerator.cs (96%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/ICanFlush.cs (58%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/IDatabaseAvailabilityAwaiter.cs (80%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/IDatabaseBootstrapper.cs (86%) rename src/abstractions/Backend.Fx/{Patterns/IdGeneration => Features/Persistence}/IEntityIdGenerator.cs (62%) rename src/abstractions/Backend.Fx/{Patterns/IdGeneration => Features/Persistence}/IIdGenerator.cs (60%) rename src/abstractions/Backend.Fx/{Patterns/IdGeneration => Features/Persistence}/ISequence.cs (81%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/PersistentApplication.cs (96%) rename src/abstractions/Backend.Fx/{Environment => Features}/Persistence/ReadonlyDbTransactionOperationDecorator.cs (95%) rename src/abstractions/Backend.Fx/{Patterns/IdGeneration => Features/Persistence}/SequenceHiLoIdGenerator.cs (90%) rename src/abstractions/Backend.Fx/{Patterns/IdGeneration => Features/Persistence}/SequenceIdGenerator.cs (88%) diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index 0f0a4ac1..a8795bd2 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -42,4 +42,8 @@ + + + + \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs index 66be9b8b..fc607fe8 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs +++ b/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs @@ -4,8 +4,8 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; using Backend.Fx.Extensions; +using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs index 26476479..98649a6d 100644 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs +++ b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs @@ -1,6 +1,6 @@ using System.Linq; using Backend.Fx.BuildingBlocks; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; namespace Backend.Fx.ConfigurationSettings { diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs b/src/abstractions/Backend.Fx/Features/Authorization/AggregateAuthorization.cs similarity index 97% rename from src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs rename to src/abstractions/Backend.Fx/Features/Authorization/AggregateAuthorization.cs index a5045f44..c156e053 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/AggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AggregateAuthorization.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.Authorization +namespace Backend.Fx.Features.Authorization { public abstract class AggregateAuthorization : IAggregateAuthorization where TAggregateRoot : AggregateRoot { diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs b/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs similarity index 91% rename from src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs rename to src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs index 6b416d79..43d1c99b 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/AllowAll.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs @@ -2,7 +2,7 @@ using System.Linq.Expressions; using Backend.Fx.BuildingBlocks; -namespace Backend.Fx.Patterns.Authorization +namespace Backend.Fx.Features.Authorization { public class AllowAll : AggregateAuthorization where TAggregateRoot : AggregateRoot { diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingApplication.cs similarity index 98% rename from src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs rename to src/abstractions/Backend.Fx/Features/Authorization/AuthorizingApplication.cs index a4932f46..18807d74 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/AuthorizingApplication.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingApplication.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.Authorization +namespace Backend.Fx.Features.Authorization { public class AuthorizingApplication : BackendFxApplicationDecorator { diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs b/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs similarity index 91% rename from src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs rename to src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs index 3e99e961..b8828e9c 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/DenyAll.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs @@ -2,7 +2,7 @@ using System.Linq.Expressions; using Backend.Fx.BuildingBlocks; -namespace Backend.Fx.Patterns.Authorization +namespace Backend.Fx.Features.Authorization { public class DenyAll : AggregateAuthorization where TAggregateRoot : AggregateRoot { diff --git a/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs b/src/abstractions/Backend.Fx/Features/Authorization/IAggregateAuthorization.cs similarity index 97% rename from src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs rename to src/abstractions/Backend.Fx/Features/Authorization/IAggregateAuthorization.cs index f911e064..c33ddab0 100644 --- a/src/abstractions/Backend.Fx/Patterns/Authorization/IAggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/IAggregateAuthorization.cs @@ -3,7 +3,7 @@ using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; -namespace Backend.Fx.Patterns.Authorization +namespace Backend.Fx.Features.Authorization { /// /// Implements permissions on aggregate level. The respective instance is applied when creating an , diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs similarity index 98% rename from src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs index f686177f..7162d2a8 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGeneratingApplication.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs @@ -6,7 +6,7 @@ using JetBrains.Annotations; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DataGeneration +namespace Backend.Fx.Features.DataGeneration { /// /// Enriches the by calling all data generators for all tenants diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs similarity index 98% rename from src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs index 1306758a..a30bc31c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationContext.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DataGeneration +namespace Backend.Fx.Features.DataGeneration { public interface IDataGenerationContext { diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs similarity index 94% rename from src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs index 7f920e59..f9c44b0e 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerationModule.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs @@ -4,7 +4,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Extensions.DependencyInjection; -namespace Backend.Fx.Patterns.DataGeneration +namespace Backend.Fx.Features.DataGeneration { public class DataGenerationModule : IModule { diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs similarity index 95% rename from src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs index 301c1383..75efa4ce 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/DataGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs @@ -1,9 +1,8 @@ -using Microsoft.Extensions.Logging; +using Backend.Fx.Logging; +using Microsoft.Extensions.Logging; -namespace Backend.Fx.Patterns.DataGeneration +namespace Backend.Fx.Features.DataGeneration { - using Logging; - public interface IDataGenerator { /// diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs similarity index 80% rename from src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs index 6908aba5..63f28de0 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IDemoDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.DataGeneration +namespace Backend.Fx.Features.DataGeneration { /// /// Marks an as active in development environments only diff --git a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs similarity index 79% rename from src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs index 17733651..255fd759 100644 --- a/src/abstractions/Backend.Fx/Patterns/DataGeneration/IProductiveDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.DataGeneration +namespace Backend.Fx.Features.DataGeneration { /// /// Marks an as active in all environments diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs similarity index 98% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs rename to src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs index aec2f398..93dcf290 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/DomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.EventAggregation.Domain +namespace Backend.Fx.Features.DomainEvents { public class DomainEventAggregator : IDomainEventAggregator { diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs new file mode 100644 index 00000000..6216485a --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs @@ -0,0 +1,12 @@ +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.Features.DomainEvents +{ + public class DomainEventsApplication : BackendFxApplicationDecorator + { + public DomainEventsApplication(IBackendFxApplication application) : base(application) + { + application.CompositionRoot.RegisterModules(new DomainEventsModule(application.Assemblies)); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs new file mode 100644 index 00000000..c0283ff4 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Reflection; +using Backend.Fx.Extensions; +using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.DomainEvents +{ + public class DomainEventsModule : IModule + { + private readonly Assembly[] _assemblies; + + public DomainEventsModule(params Assembly[] assemblies) + { + _assemblies = assemblies; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.Register(ServiceDescriptor.Scoped(sp => new DomainEventAggregator(sp))); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + RegisterDomainEventHandlers(compositionRoot); + } + + private void RegisterDomainEventHandlers(ICompositionRoot compositionRoot) + { + foreach (Type domainEventType in _assemblies.GetImplementingTypes(typeof(IDomainEvent))) + { + Type handlerTypeForThisDomainEventType = typeof(IDomainEventHandler<>).MakeGenericType(domainEventType); + + var serviceDescriptors = _assemblies + .GetImplementingTypes(handlerTypeForThisDomainEventType) + .Select(t => new ServiceDescriptor(handlerTypeForThisDomainEventType, t, ServiceLifetime.Scoped)) + .ToArray(); + + compositionRoot.RegisterCollection(serviceDescriptors); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEvent.cs similarity index 82% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs rename to src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEvent.cs index 8f9af72b..9a5f5a5c 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEvent.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEvent.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.EventAggregation.Domain +namespace Backend.Fx.Features.DomainEvents { /// /// Marker interface for domain events that must be handled in the same scope and transaction of the publishing logic. diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs similarity index 87% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs rename to src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs index 42891ec5..0087c1e6 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.EventAggregation.Domain +namespace Backend.Fx.Features.DomainEvents { /// /// Channel events from multiple objects into a single object to simplify registration for clients. diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventHandler.cs similarity index 72% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs rename to src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventHandler.cs index eb5b4a24..536dd858 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Domain/IDomainEventHandler.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventHandler.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.EventAggregation.Domain +namespace Backend.Fx.Features.DomainEvents { public interface IDomainEventHandler where TDomainEvent : IDomainEvent { diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs new file mode 100644 index 00000000..b174c45f --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs @@ -0,0 +1,36 @@ +using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.DomainEvents +{ + [UsedImplicitly] + public class RaiseDomainEventsOperationDecorator : IOperation + { + private readonly IDomainEventAggregator _domainEventAggregator; + private readonly IOperation _operation; + + public RaiseDomainEventsOperationDecorator( + IDomainEventAggregator domainEventAggregator, + IOperation operation) + { + _domainEventAggregator = domainEventAggregator; + _operation = operation; + } + + public void Begin() + { + _operation.Begin(); + } + + public void Complete() + { + _domainEventAggregator.RaiseEvents(); + _operation.Complete(); + } + + public void Cancel() + { + _operation.Cancel(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/ApplicationWithJobs.cs b/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs similarity index 91% rename from src/abstractions/Backend.Fx/Patterns/Jobs/ApplicationWithJobs.cs rename to src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs index d060d98a..9971deee 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/ApplicationWithJobs.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs @@ -1,6 +1,6 @@ using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.Patterns.Jobs +namespace Backend.Fx.Features.Jobs { public abstract class ApplicationWithJobs : BackendFxApplicationDecorator { diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs b/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs similarity index 83% rename from src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs rename to src/abstractions/Backend.Fx/Features/Jobs/IJob.cs index 552e21eb..63225f2c 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/IJob.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.Jobs +namespace Backend.Fx.Features.Jobs { /// /// This interface describes a job that can be executed directly or by a scheduler. diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs b/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs similarity index 95% rename from src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs rename to src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs index 8d881a2b..d556cdea 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/JobModule.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs @@ -4,7 +4,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Extensions.DependencyInjection; -namespace Backend.Fx.Patterns.Jobs +namespace Backend.Fx.Features.Jobs { public class JobModule : IModule { diff --git a/src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs b/src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs similarity index 97% rename from src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs rename to src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs index 4aa3e1b3..3a3f5c25 100644 --- a/src/abstractions/Backend.Fx/Patterns/Jobs/WithTenantWideMutex.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.Jobs +namespace Backend.Fx.Features.Jobs { [PublicAPI] public class WithTenantWideMutex : IJob where TJob : IJob diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs similarity index 90% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs index 043bec4e..ff454688 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DelegateIntegrationMessageHandler.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs @@ -1,6 +1,6 @@ using System; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public class DelegateIntegrationMessageHandler : IIntegrationMessageHandler where TIntegrationEvent : IIntegrationEvent diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs similarity index 95% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs index 9a545064..ca4a0dd3 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/DynamicSubscription.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs @@ -6,7 +6,7 @@ using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public class DynamicSubscription : ISubscription { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs b/src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs similarity index 85% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs index eb913e8f..47c06329 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/EventProcessingContext.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs @@ -1,7 +1,7 @@ using System; using Backend.Fx.Environment.MultiTenancy; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public abstract class EventProcessingContext { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs similarity index 84% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs index 8bc1a749..d099f655 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IIntegrationMessageHandler.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { [PublicAPI] public interface IIntegrationMessageHandler diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs similarity index 97% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs index 0e4be87b..63e9cb8b 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageBus.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public interface IMessageBus : IDisposable { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs similarity index 84% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs index ec7f3007..d3ec5a41 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IMessageNameProvider.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs @@ -1,7 +1,7 @@ using System; using JetBrains.Annotations; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { [PublicAPI] public interface IMessageNameProvider diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs similarity index 76% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs index eb61d569..77c934c0 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/ISubscription.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs @@ -1,6 +1,6 @@ using System; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public interface ISubscription { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs similarity index 93% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs index 10b1ea3c..ed6848c7 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBus.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs @@ -1,9 +1,9 @@ -namespace Backend.Fx.Patterns.EventAggregation.Integration -{ - using System; - using System.Threading.Tasks; - using Environment.MultiTenancy; +using System; +using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; +namespace Backend.Fx.Features.MessageBus +{ public class InMemoryMessageBus : MessageBus { private readonly InMemoryMessageBusChannel _channel; diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs b/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBusChannel.cs similarity index 85% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBusChannel.cs index 7e88a60f..60084eb5 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/InMemoryMessageBusChannel.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBusChannel.cs @@ -1,10 +1,9 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { - using System; - using System.Threading.Tasks; - public class InMemoryMessageBusChannel { private readonly ConcurrentBag _messageHandlingTasks = new ConcurrentBag(); diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs similarity index 95% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs index 93689ee3..58e8654c 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/IntegrationEvent.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs @@ -2,7 +2,7 @@ using Backend.Fx.Environment.MultiTenancy; using JetBrains.Annotations; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { [PublicAPI] public interface IIntegrationEvent diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs similarity index 99% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs index 0a5b1fd2..e8b2ed3f 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBus.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public abstract class MessageBus : IMessageBus { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs similarity index 95% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs index 480a47a1..99634876 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusApplication.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public class MessageBusApplication : BackendFxApplicationDecorator { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs similarity index 97% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs index c1db1a1a..62bd5d6f 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusModule.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs @@ -6,7 +6,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Extensions.DependencyInjection; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public class MessageBusModule : IModule { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs similarity index 90% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs index 40e90e42..24e40bf9 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/MessageBusScope.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs @@ -1,11 +1,10 @@ -using Backend.Fx.Environment.MultiTenancy; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { - using System.Collections.Concurrent; - using System.Threading.Tasks; - public interface IMessageBusScope { /// diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs similarity index 93% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs index ccfa203a..03ac2799 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/RaiseIntegrationEventsOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs @@ -2,7 +2,7 @@ using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { [UsedImplicitly] public class RaiseIntegrationEventsOperationDecorator : IOperation diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SequentializingBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs similarity index 94% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SequentializingBackendFxApplicationInvoker.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs index e56a4572..69191f8e 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SequentializingBackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs @@ -3,7 +3,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Patterns.DependencyInjection; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { /// /// Decorates the to prevent parallel invocation. diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs similarity index 94% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs index 1210f2f4..cabf653f 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/SingletonSubscription.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs @@ -2,7 +2,7 @@ using Backend.Fx.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public class SingletonSubscription : ISubscription where TEvent : IIntegrationEvent { diff --git a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs similarity index 96% rename from src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs index f802a63b..72fac18a 100644 --- a/src/abstractions/Backend.Fx/Patterns/EventAggregation/Integration/TypedSubscription.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.EventAggregation.Integration +namespace Backend.Fx.Features.MessageBus { public class TypedSubscription : ISubscription { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/DbConnectionOperationDecorator.cs similarity index 97% rename from src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/DbConnectionOperationDecorator.cs index 93d6958b..daa1f31e 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/DbConnectionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/DbConnectionOperationDecorator.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public class DbConnectionOperationDecorator : IOperation { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/DbTransactionOperationDecorator.cs similarity index 99% rename from src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/DbTransactionOperationDecorator.cs index 3960d689..528db2b9 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/DbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/DbTransactionOperationDecorator.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { /// /// Enriches the operation to use a database transaction during lifetime. The transaction gets started, before IOperation.Begin() diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs similarity index 92% rename from src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs index d9c70632..9207935d 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/FlushDomainEventAggregatorDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs @@ -1,9 +1,9 @@ +using Backend.Fx.Features.DomainEvents; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Domain; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public class FlushDomainEventAggregatorDecorator : IDomainEventAggregator { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs similarity index 95% rename from src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs index 3f3d00c7..2e96e8b6 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/FlushOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public class FlushOperationDecorator : IOperation { diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Features/Persistence/HiLoIdGenerator.cs similarity index 96% rename from src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/HiLoIdGenerator.cs index 3f61085c..438f3312 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/HiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/HiLoIdGenerator.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.IdGeneration +namespace Backend.Fx.Features.Persistence { public abstract class HiLoIdGenerator : IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs b/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs similarity index 58% rename from src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs rename to src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs index 7e27683e..8e13936f 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/ICanFlush.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public interface ICanFlush { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs similarity index 80% rename from src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs rename to src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs index 7b21926e..40e9b2ce 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseAvailabilityAwaiter.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public interface IDatabaseAvailabilityAwaiter { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs similarity index 86% rename from src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs rename to src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs index 65f1049a..62b6172e 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/IDatabaseBootstrapper.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs @@ -1,6 +1,6 @@ using System; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { /// /// Encapsulates database bootstrapping. This interface hides the implementation details for creating/migrating the database diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/IEntityIdGenerator.cs b/src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs similarity index 62% rename from src/abstractions/Backend.Fx/Patterns/IdGeneration/IEntityIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs index cca401b2..e9389a16 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/IEntityIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.IdGeneration +namespace Backend.Fx.Features.Persistence { public interface IEntityIdGenerator { diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs b/src/abstractions/Backend.Fx/Features/Persistence/IIdGenerator.cs similarity index 60% rename from src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/IIdGenerator.cs index 5c59c6d2..fddebf5e 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/IIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/IIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.IdGeneration +namespace Backend.Fx.Features.Persistence { public interface IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs b/src/abstractions/Backend.Fx/Features/Persistence/ISequence.cs similarity index 81% rename from src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs rename to src/abstractions/Backend.Fx/Features/Persistence/ISequence.cs index d806ef6a..fedbd1c4 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/ISequence.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/ISequence.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Backend.Fx.Patterns.IdGeneration +namespace Backend.Fx.Features.Persistence { [PublicAPI] public interface ISequence diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs b/src/abstractions/Backend.Fx/Features/Persistence/PersistentApplication.cs similarity index 96% rename from src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs rename to src/abstractions/Backend.Fx/Features/Persistence/PersistentApplication.cs index c7f7d8f0..a527e4f9 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/PersistentApplication.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/PersistentApplication.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public class PersistentApplication : BackendFxApplicationDecorator { diff --git a/src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs similarity index 95% rename from src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs index a33b85c3..51d43333 100644 --- a/src/abstractions/Backend.Fx/Environment/Persistence/ReadonlyDbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Environment.Persistence +namespace Backend.Fx.Features.Persistence { public class ReadonlyDbTransactionOperationDecorator : IOperation { diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Features/Persistence/SequenceHiLoIdGenerator.cs similarity index 90% rename from src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/SequenceHiLoIdGenerator.cs index dd154181..f01a711e 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceHiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/SequenceHiLoIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.IdGeneration +namespace Backend.Fx.Features.Persistence { public class SequenceHiLoIdGenerator : HiLoIdGenerator { diff --git a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs b/src/abstractions/Backend.Fx/Features/Persistence/SequenceIdGenerator.cs similarity index 88% rename from src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/SequenceIdGenerator.cs index ff1f44c5..e371dcfa 100644 --- a/src/abstractions/Backend.Fx/Patterns/IdGeneration/SequenceIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/SequenceIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Patterns.IdGeneration +namespace Backend.Fx.Features.Persistence { public class SequenceIdGenerator : IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs index f972dbbe..3f0ba432 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs @@ -4,7 +4,6 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Domain; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -54,7 +53,6 @@ public void Invoke(Action action, IIdentity identity, TenantId { operation.Begin(); action.Invoke(serviceScope.ServiceProvider); - serviceScope.ServiceProvider.GetRequiredService().RaiseEvents(); operation.Complete(); } catch @@ -78,7 +76,6 @@ public async Task InvokeAsync(Func awaitableAsyncAction, { operation.Begin(); await awaitableAsyncAction.Invoke(serviceScope.ServiceProvider).ConfigureAwait(false); - serviceScope.ServiceProvider.GetRequiredService().RaiseEvents(); operation.Complete(); } catch diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs index 8ead6efc..47d789d2 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Reflection; using System.Security.Principal; @@ -7,9 +6,9 @@ using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.DomainEvents; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.EventAggregation.Domain; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; @@ -41,8 +40,6 @@ public void Register(ICompositionRoot compositionRoot) RegisterDomainAndApplicationServices(compositionRoot); RegisterPermissiveAuthorization(compositionRoot); - - RegisterDomainEventHandlers(compositionRoot); } private static void RegisterDomainInfrastructureServices(ICompositionRoot compositionRoot) @@ -52,7 +49,6 @@ private static void RegisterDomainInfrastructureServices(ICompositionRoot compos compositionRoot.Register(ServiceDescriptor.Scoped,CurrentCorrelationHolder>()); compositionRoot.Register(ServiceDescriptor.Scoped,CurrentIdentityHolder>()); compositionRoot.Register(ServiceDescriptor.Scoped,CurrentTenantIdHolder>()); - compositionRoot.Register(ServiceDescriptor.Scoped(sp => new DomainEventAggregator(sp))); } private void RegisterDomainAndApplicationServices(ICompositionRoot container) @@ -95,20 +91,5 @@ private void RegisterPermissiveAuthorization(ICompositionRoot compositionRoot) ServiceLifetime.Singleton)); } } - - private void RegisterDomainEventHandlers(ICompositionRoot compositionRoot) - { - foreach (Type domainEventType in _assemblies.GetImplementingTypes(typeof(IDomainEvent))) - { - Type handlerTypeForThisDomainEventType = typeof(IDomainEventHandler<>).MakeGenericType(domainEventType); - - var serviceDescriptors = _assemblies - .GetImplementingTypes(handlerTypeForThisDomainEventType) - .Select(t => new ServiceDescriptor(handlerTypeForThisDomainEventType, t, ServiceLifetime.Scoped)) - .ToArray(); - - compositionRoot.RegisterCollection(serviceDescriptors); - } - } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs index 32813a63..34a480c5 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs @@ -1,6 +1,6 @@ using System.Data; using System.Data.Common; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index 41e770e0..ecf1f38b 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -5,11 +5,10 @@ using System.Reflection; using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.DomainEvents; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs index 84a993f0..5729d037 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs @@ -5,9 +5,9 @@ using System.Security.Principal; using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; using Backend.Fx.Exceptions; using Backend.Fx.Extensions; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs index 765b8400..7a95f2d0 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs @@ -7,8 +7,8 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; +using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs index e2cd28ef..eb87bd96 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs @@ -1,8 +1,8 @@ using System; using System.Data; using Backend.Fx.EfCore5Persistence.Bootstrapping; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs index 3ba1e455..023fa1aa 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs @@ -1,8 +1,8 @@ using System; using System.Data; using Backend.Fx.EfCore5Persistence.Bootstrapping; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs index 0d087cef..2565ab57 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs @@ -1,8 +1,8 @@ using System; using System.Data; using Backend.Fx.EfCore5Persistence.Bootstrapping; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs index 616d5d6c..63f05dc1 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs @@ -1,6 +1,6 @@ using System.Data; using System.Data.Common; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index c0338188..a66dd85c 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -5,11 +5,10 @@ using System.Reflection; using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.DomainEvents; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.IdGeneration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs index e3da4708..e372b0cc 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs @@ -5,9 +5,9 @@ using System.Security.Principal; using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; using Backend.Fx.Exceptions; using Backend.Fx.Extensions; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs index 774b9899..cf42b9ec 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs @@ -7,8 +7,8 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; +using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs index df0102a1..789d79e7 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs @@ -1,8 +1,8 @@ using System; using System.Data; using Backend.Fx.EfCore6Persistence.Bootstrapping; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs index 28a1771b..a36115d9 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs @@ -1,8 +1,8 @@ using System; using System.Data; using Backend.Fx.EfCore6Persistence.Bootstrapping; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs index e66142a0..a62350a0 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs @@ -1,8 +1,8 @@ using System; using System.Data; using Backend.Fx.EfCore6Persistence.Bootstrapping; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.IdGeneration; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs index d8b105a7..77b6d80d 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs index 9308a23c..36bd4daa 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; namespace Backend.Fx.InMemoryPersistence { diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs index 447d2012..1df0b0c5 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs @@ -3,10 +3,9 @@ using System.Reflection; using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.IdGeneration; using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.InMemoryPersistence diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs index ee777a23..a53587ec 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs @@ -2,7 +2,7 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.RandomData; using JetBrains.Annotations; diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs index ab7c9850..16fe65fc 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; namespace Backend.Fx.InMemoryPersistence { diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs b/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs index 9010cc7c..c280e6e4 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs +++ b/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Net.Sockets; using System.Text; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Integration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Polly; diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs b/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs index 25b08ac6..c8fac43d 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs +++ b/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs @@ -2,8 +2,8 @@ using System.Text; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; -using Backend.Fx.Patterns.EventAggregation.Integration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs index ec1db9e1..2cd1b2e5 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -3,7 +3,7 @@ using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 2a7e30e4..06176e35 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -2,7 +2,7 @@ using System.Data; using System.Data.SqlClient; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 0c8d9d20..5c893b3c 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,7 +1,7 @@ using System.Data; using System.IO; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs index 8582111c..0342c856 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs @@ -3,7 +3,7 @@ using System.Security.Principal; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs index b908e82d..110a9b90 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs index 28d7d18d..7978a465 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs @@ -1,5 +1,5 @@ -using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; +using Backend.Fx.InMemoryPersistence; namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index 1ab0ed9f..c25e3b9d 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -1,7 +1,7 @@ using System.Data.Common; using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs index 3c786a7c..3b3d9dfb 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -7,9 +7,8 @@ using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Runtime; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.DomainEvents; +using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs index ab91146c..7f181ff2 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -7,8 +7,8 @@ using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using FakeItEasy; using SampleApp.Domain; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs index 55e79941..62cc12f6 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -5,7 +5,7 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using Backend.Fx.TestUtil; using SampleApp.Domain; using Xunit; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs index 487ce089..fb78a6db 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -3,7 +3,7 @@ using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 83125a47..72892469 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -2,7 +2,7 @@ using System.Data; using System.Data.SqlClient; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index fc775591..28e35a4d 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,7 +1,7 @@ using System.Data; using System.IO; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs index 6abbbe87..4bc0ea5f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs @@ -3,7 +3,7 @@ using System.Security.Principal; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs index a130665e..1e6ee53f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs index 2c1fe17a..36a34113 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs @@ -1,5 +1,5 @@ -using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; +using Backend.Fx.InMemoryPersistence; namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index 08fe6fbf..1ae970c7 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -1,7 +1,7 @@ using System.Data.Common; using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs index e510bb80..2a3473fa 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -7,9 +7,8 @@ using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Runtime; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Environment.Persistence; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.DomainEvents; +using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs index 5ca0fa86..2014bf14 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -7,8 +7,8 @@ using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using FakeItEasy; using SampleApp.Domain; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs index d78ece33..e322d394 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -5,7 +5,7 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using Backend.Fx.TestUtil; using SampleApp.Domain; using Xunit; diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index 3dab75d1..b8991a36 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics; using System.Threading; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs index f158e027..44e1c66a 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs @@ -2,8 +2,8 @@ using System.Linq; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; +using Backend.Fx.Features.Authorization; using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs index a27b0d9c..a88c62ab 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs @@ -1,7 +1,7 @@ using System; using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using JetBrains.Annotations; using Xunit; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs index 54d96c9c..f2d9e177 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs @@ -2,10 +2,10 @@ using Backend.Fx.BuildingBlocks; using Backend.Fx.ConfigurationSettings; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.Persistence; using Backend.Fx.Hacking; using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.Authorization; -using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs index aac1ed46..715a0623 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs @@ -1,8 +1,8 @@ using System; using System.Linq; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.MessageBus; using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs b/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs index 02eb40d1..5d9a9f03 100644 --- a/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs +++ b/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs @@ -1,6 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs index e2255d97..8823bbb0 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs @@ -1,5 +1,5 @@ using System.Linq; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using Backend.Fx.Tests.BuildingBlocks; using Backend.Fx.TestUtil; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs index 6b4c9e82..e66209e1 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs @@ -3,8 +3,8 @@ using System.Threading.Tasks; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs index 4d8857f9..45da256e 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs @@ -1,5 +1,5 @@ using System.Linq; -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using Backend.Fx.Tests.BuildingBlocks; using Backend.Fx.TestUtil; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index 14e2a01f..41ce18cb 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.DataGeneration; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs index 4c061ff1..b48fda6b 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.DataGeneration; using Backend.Fx.Hacking; -using Backend.Fx.Patterns.DataGeneration; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs index 72e6db68..b5c14c67 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.DataGeneration; +using Backend.Fx.Features.DataGeneration; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs index 228dd226..d37f466d 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs @@ -1,8 +1,8 @@ using System; using System.Security.Principal; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.DomainEvents; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index 21cd3237..182868a4 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -4,11 +4,11 @@ using System.Threading.Tasks; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.DomainEvents; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.Tests.Patterns.Authorization; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs index 7437c64f..ad382572 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs @@ -4,10 +4,10 @@ using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.DomainEvents; using Backend.Fx.Logging; -using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index a182781a..09268153 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -5,8 +5,8 @@ using System.Threading.Tasks; using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs index f4c2f8d0..ea7235b6 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs @@ -1,5 +1,5 @@ using System; -using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Features.DomainEvents; namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs index c9fb4e59..810af00a 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Features.DomainEvents; namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs index faa65c19..60461560 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Features.DomainEvents; namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs index 0e121fcb..206fe9da 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; using JetBrains.Annotations; namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs index 3d2b1353..e4c140cb 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Features.DomainEvents; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs index 52fa0fa5..ee2977a6 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs @@ -1,6 +1,6 @@ using System.Diagnostics; using System.Threading; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; using Xunit; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs index e0cafc60..808d5cd7 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs index 2cf3567c..33df7219 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs @@ -1,5 +1,5 @@ using System.Threading; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs index 77af8d4d..b0d5d79d 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs index 1393463c..4196e61a 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs @@ -1,5 +1,5 @@ using System.Threading; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs index 25b33a5d..3fc1f043 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs @@ -1,6 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs index be0e4cb7..ca6179aa 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs @@ -4,8 +4,8 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.TestUtil; using FakeItEasy; using JetBrains.Annotations; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs index 66741df0..696d46a2 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs index 6cd753e9..93920d6c 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs @@ -1,6 +1,6 @@ using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.EventAggregation.Integration; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs index a5dd498e..2d41160d 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs @@ -1,5 +1,5 @@ using System; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs index 3bdd71d9..6b7b145a 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs @@ -1,5 +1,5 @@ using System; -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs index e76416b8..2d189cd6 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Features.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs index 42fbc32d..a7530d22 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs @@ -1,6 +1,6 @@ using System.Linq; +using Backend.Fx.Features.Persistence; using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs index 074efa55..d916b033 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs index 7f69dca3..635e389a 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs @@ -1,6 +1,6 @@ using System.Linq; +using Backend.Fx.Features.Persistence; using Backend.Fx.InMemoryPersistence; -using Backend.Fx.Patterns.IdGeneration; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs index 43470ef8..a3a3037d 100644 --- a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -3,16 +3,16 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.Features.Jobs; using Backend.Fx.Logging; using Backend.Fx.Patterns.DependencyInjection; -using Backend.Fx.Patterns.Jobs; using Backend.Fx.TestUtil; using FakeItEasy; using FluentScheduler; using Microsoft.Extensions.DependencyInjection; using Xunit; using Xunit.Abstractions; -using IJob = Backend.Fx.Patterns.Jobs.IJob; +using IJob = Backend.Fx.Features.Jobs.IJob; namespace Backend.Fx.Tests.Patterns.Jobs { @@ -89,7 +89,7 @@ public JobSchedule(IBackendFxApplication application, ITenantIdProvider tenantId } } - public class SayHelloJob : IJob + public class SayHelloJob : Features.Jobs.IJob { private readonly ICurrentTHolder _tenantIdHolder; public static List Invocations = new List(); diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs b/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs index 224274c8..261aabc0 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs @@ -1,6 +1,6 @@ using System; using System.Data; -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs b/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs index c54a5865..947d4d14 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Persistence; +using Backend.Fx.Features.Persistence; using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/SampleApp.Domain/Blog.cs b/tests/SampleApp.Domain/Blog.cs index 83007b19..b6214e41 100644 --- a/tests/SampleApp.Domain/Blog.cs +++ b/tests/SampleApp.Domain/Blog.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using Backend.Fx.BuildingBlocks; -using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Features.Persistence; using JetBrains.Annotations; namespace SampleApp.Domain diff --git a/tests/SampleApp.Domain/BlogAuthorization.cs b/tests/SampleApp.Domain/BlogAuthorization.cs index 9b32ce3e..cded74a9 100644 --- a/tests/SampleApp.Domain/BlogAuthorization.cs +++ b/tests/SampleApp.Domain/BlogAuthorization.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using JetBrains.Annotations; namespace SampleApp.Domain diff --git a/tests/SampleApp.Domain/BlogCreated.cs b/tests/SampleApp.Domain/BlogCreated.cs index 2655a758..3ee935ed 100644 --- a/tests/SampleApp.Domain/BlogCreated.cs +++ b/tests/SampleApp.Domain/BlogCreated.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Features.DomainEvents; namespace SampleApp.Domain { diff --git a/tests/SampleApp.Domain/BlogCreatedHandler.cs b/tests/SampleApp.Domain/BlogCreatedHandler.cs index 185da711..ff9a2631 100644 --- a/tests/SampleApp.Domain/BlogCreatedHandler.cs +++ b/tests/SampleApp.Domain/BlogCreatedHandler.cs @@ -1,5 +1,5 @@ using Backend.Fx.BuildingBlocks; -using Backend.Fx.Patterns.EventAggregation.Domain; +using Backend.Fx.Features.DomainEvents; using Xunit; namespace SampleApp.Domain diff --git a/tests/SampleApp.Domain/BloggerAuthorization.cs b/tests/SampleApp.Domain/BloggerAuthorization.cs index 882429f1..c8d924f6 100644 --- a/tests/SampleApp.Domain/BloggerAuthorization.cs +++ b/tests/SampleApp.Domain/BloggerAuthorization.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Features.Authorization; using JetBrains.Annotations; namespace SampleApp.Domain From 8f9123f3821d63cd994ec3aed15e563187608c99 Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Sun, 3 Jul 2022 22:33:04 -0300 Subject: [PATCH 29/51] remove dynamic linq dependency --- src/abstractions/Backend.Fx/Backend.Fx.csproj | 6 +----- .../Backend.Fx/RandomData/LinqExtensions.cs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index a8795bd2..7d1d838e 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -34,16 +34,12 @@ + - - - - - \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs index e9a3f331..4c368fe8 100644 --- a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs +++ b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Dynamic.Core; +using System.Linq.Expressions; using System.Reflection; using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; @@ -88,7 +88,10 @@ private static bool TryAsQueryable(this IEnumerable source, out IQueryable } PropertyInfo idProperty = typeof(T).GetProperty(nameof(Identified.Id), BindingFlags.Instance | BindingFlags.Public); - if (idProperty != null) sourceQueryable = sourceQueryable.OrderBy(nameof(Identified.Id)); + if (idProperty != null) + { + sourceQueryable = sourceQueryable.OrderBy(GetPropertyExpression(idProperty.Name)); + } outQueryable = sourceQueryable; return true; @@ -96,5 +99,12 @@ private static bool TryAsQueryable(this IEnumerable source, out IQueryable return false; } + + private static Expression> GetPropertyExpression(string propertyName) + { + var param = Expression.Parameter(typeof(T), "t"); + Expression conversion = Expression.Convert(Expression.Property(param, propertyName), typeof(object)); + return Expression.Lambda>(conversion, param); + } } } \ No newline at end of file From 4c07653206d46d595256082c5c57fcd5faba1fbe Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Thu, 22 Sep 2022 11:47:02 -0300 Subject: [PATCH 30/51] checkpoint --- Backend.Fx.sln | 10 + src/abstractions/Backend.Fx/Backend.Fx.csproj | 11 + .../BackendFxApplication.cs | 27 +- .../Backend.Fx/BuildingBlocks/Entity.cs | 63 - .../BuildingBlocks/IAsyncRepository.cs | 24 - .../Backend.Fx/BuildingBlocks/IRepository.cs | 25 - .../Backend.Fx/BuildingBlocks/Repository.cs | 157 - .../ISettingSerializer.cs | 86 - .../ConfigurationSettings/Setting.cs | 31 - .../ConfigurationSettings/SettingsService.cs | 49 - .../CompositionRoot.cs} | 29 +- .../DependencyInjection/IModule.cs | 5 +- .../ServiceDescriptorEx.cs | 15 +- .../AggregateRoot.cs | 4 +- src/abstractions/Backend.Fx/Domain/Entity.cs | 18 + .../Domain/IAsyncAggregateQueryable.cs | 8 + .../Backend.Fx/Domain/IEntityIdGenerator.cs | 6 + .../Persistence => Domain}/IIdGenerator.cs | 2 +- .../Backend.Fx/Domain/IRepository.cs | 83 + .../{BuildingBlocks => Domain}/IView.cs | 2 +- .../{BuildingBlocks => Domain}/Identified.cs | 2 +- .../{BuildingBlocks => Domain}/ValueObject.cs | 2 +- .../DateAndTime/AdjustableClock.cs | 47 - .../Environment/DateAndTime/FrozenClock.cs | 23 - .../Environment/DateAndTime/IClock.cs | 13 - .../Environment/DateAndTime/WallClock.cs | 12 - .../AllTenantBackendFxApplicationInvoker.cs | 34 - .../MultiTenancy/ITenantIdProvider.cs | 15 - .../MultiTenancy/ITenantRepository.cs | 13 - .../MultiTenancy/SingleTenantApplication.cs | 43 - .../Environment/MultiTenancy/TenantService.cs | 186 - .../Exceptions/ForbiddenException.cs | 2 +- .../AnonymousIdentity.cs | 6 +- .../BackendFxApplicationInvoker.cs | 78 + .../Correlation.cs | 7 +- .../CurrentCorrelationHolder.cs | 9 +- .../CurrentIdentityHolder.cs | 8 +- .../ExceptionLoggingAndHandlingInvoker.cs | 10 +- .../ExceptionLoggingInvoker.cs | 10 +- .../ExecutionPipelineModule.cs | 40 + .../ExecutionPipeline/FrozenClock.cs | 27 + .../Operation.cs | 10 +- .../SystemIdentity.cs | 6 +- .../BackendFxApplicationExtension.cs} | 17 +- .../DataGenerationApplication.cs | 59 + .../DataGeneration/DataGenerationModule.cs | 6 +- .../DataGeneration/DataGenerator.cs | 17 +- .../DataGeneration/IDemoDataGenerator.cs | 2 +- .../IProductiveDataGenerator.cs | 2 +- .../MessageBus/IIntegrationEventHandler.cs | 9 + .../MessageBus/InProc/InProcMessageBus.cs | 42 + .../InProc/InProcMessageBusChannel.cs} | 10 +- .../Extensions/MessageBus/IntegrationEvent.cs | 33 + .../IntegrationEventHandlingInvoker.cs | 45 + .../MessageBus/IntegrationEventSerializer.cs | 37 + .../Extensions/MessageBus/MessageBus.cs | 77 + .../MessageBus/MessageBusExtension.cs | 41 + .../Extensions/MessageBus/MessageBusModule.cs | 94 + .../MessageBus/MessageBusScope.cs | 28 +- .../MultiTenantIntegrationEventSerializer.cs | 48 + ...ntegrationEventsWhenOperationCompleted.cs} | 17 +- .../MessageBus/SerializedMessage.cs | 14 + .../DbConnectionOperationDecorator.cs | 10 +- .../DbTransactionOperationDecorator.cs | 10 +- .../FlushDomainEventAggregatorDecorator.cs | 3 +- .../Persistence/FlushOperationDecorator.cs | 10 +- .../Persistence/HiLoIdGenerator.cs | 4 +- .../Persistence/ICanFlush.cs | 2 +- .../IDatabaseAvailabilityAwaiter.cs | 2 +- .../Persistence/IDatabaseBootstrapper.cs | 2 +- .../Persistence/ISequence.cs | 2 +- .../Persistence/PersistentApplication.cs | 33 +- .../Persistence/SequenceHiLoIdGenerator.cs | 2 +- .../Persistence/SequenceIdGenerator.cs | 4 +- .../Features/Authorization/AllowAll.cs | 4 +- .../Authorization/AuthorizationFeature.cs | 23 + ...gApplication.cs => AuthorizationModule.cs} | 32 +- ...uthorization.cs => AuthorizationPolicy.cs} | 16 +- .../Authorization/AuthorizingQueryable.cs | 36 + .../Authorization/AuthorizingRepository.cs | 84 + .../Features/Authorization/DenyAll.cs | 4 +- ...thorization.cs => IAuthorizationPolicy.cs} | 15 +- .../BooleanSerializer.cs | 18 + .../ConfigurationSettingsFeature.cs | 28 + .../ConfigurationSettingsModule.cs | 25 + .../DateTimeSerializer.cs | 20 + .../ConfigurationSettings/DoubleSerializer.cs | 19 + .../ISettingRepository.cs | 25 + .../ISettingSerializer.cs | 12 + .../IntegerSerializer.cs | 19 + .../SettingSerializerFactory.cs | 3 +- .../ConfigurationSettings/SettingsCategory.cs | 51 + .../ConfigurationSettings/StringSerializer.cs | 18 + .../DataGeneratingApplication.cs | 65 - .../DataGeneration/DataGenerationContext.cs | 78 - .../DomainEvents/DomainEventAggregator.cs | 1 - .../DomainEvents/DomainEventsApplication.cs | 12 - .../DomainEvents/DomainEventsFeature.cs | 24 + .../DomainEvents/DomainEventsModule.cs | 24 +- .../RaiseDomainEventsOperationDecorator.cs | 7 +- .../DomainServices/DomainServicesFeature.cs | 18 + .../DomainServices/DomainServicesModule.cs | 53 + .../DomainServices}/IApplicationService.cs | 2 +- .../DomainServices}/IDomainService.cs | 2 +- .../Features/Jobs/ApplicationWithJobs.cs | 35 +- .../Backend.Fx/Features/Jobs/IJob.cs | 6 +- .../Backend.Fx/Features/Jobs/JobModule.cs | 6 +- .../Features/Jobs/WithTenantWideMutex.cs | 49 - .../DelegateIntegrationMessageHandler.cs | 20 - .../MessageBus/DynamicSubscription.cs | 36 - .../MessageBus/EventProcessingContext.cs | 14 - .../MessageBus/IIntegrationMessageHandler.cs | 16 - .../Features/MessageBus/IMessageBus.cs | 63 - .../MessageBus/IMessageNameProvider.cs | 16 - .../Features/MessageBus/ISubscription.cs | 10 - .../Features/MessageBus/InMemoryMessageBus.cs | 76 - .../Features/MessageBus/IntegrationEvent.cs | 50 - .../Features/MessageBus/MessageBus.cs | 201 - .../MessageBus/MessageBusApplication.cs | 36 - .../Features/MessageBus/MessageBusModule.cs | 53 - ...uentializingBackendFxApplicationInvoker.cs | 30 - .../MessageBus/SingletonSubscription.cs | 30 - .../Features/MessageBus/TypedSubscription.cs | 44 - .../MultiTenancy/CurrentTenantIdHolder.cs | 5 +- .../MultiTenancy/ICurrentTenantIdSelector.cs | 7 + .../ITenantFilterExpressionFactory.cs | 10 + .../MultiTenancy/ITenantWideMutex.cs | 2 +- .../MultiTenancy/ITenantWideMutexManager.cs | 7 + .../InMemoryTenantWideMutexManager.cs} | 15 +- .../MultiTenancy/MultiTenancyFeature.cs | 26 + .../MultiTenancy/MultiTenancyModule.cs | 41 + .../MultiTenancy/TenantFilteredQueryable.cs | 37 + .../MultiTenancy/TenantId.cs | 18 +- .../MultiTenancy/TenantOperationDecorator.cs | 36 + .../Persistence/IEntityIdGenerator.cs | 7 - ...ReadonlyDbTransactionOperationDecorator.cs | 34 - .../Backend.Fx/Hacking/AdjustableClock.cs | 35 + .../Backend.Fx/Logging/DurationLogger.cs | 12 +- .../Backend.Fx/Logging/ExceptionLoggers.cs | 2 +- .../Backend.Fx/Logging/IExceptionLogger.cs | 4 +- src/abstractions/Backend.Fx/Logging/Log.cs | 24 +- .../BackendFxToMicrosoftLoggingLogger.cs | 166 - ...ackendFxToMicrosoftLoggingLoggerFactory.cs | 41 - .../Logging/Obsolete/DebugLogger.cs | 175 - .../Logging/Obsolete/DebugLoggerFactory.cs | 42 - .../Backend.Fx/Logging/Obsolete/ILogger.cs | 99 - .../Logging/Obsolete/ILoggerFactory.cs | 17 - .../Logging/Obsolete/LegacyExceptionLogger.cs | 29 - .../Backend.Fx/Logging/Obsolete/LogManager.cs | 45 - .../Logging/Obsolete/LoggerExtensions.cs | 159 - .../BackendFxApplicationInvoker.cs | 119 - .../DependencyInjection/DomainModule.cs | 95 - .../ExceptionLoggingAsyncInvoker.cs | 33 - .../Backend.Fx/RandomData/Generator.cs | 43 - .../RandomData/LandLineGenerator.cs | 21 - .../Backend.Fx/RandomData/Letters.cs | 57 - .../Backend.Fx/RandomData/LinqExtensions.cs | 110 - .../Backend.Fx/RandomData/LoremIpsum.cs | 37 - .../RandomData/MobileLineGenerator.cs | 23 - .../Backend.Fx/RandomData/Names.cs | 5761 ----------------- .../Backend.Fx/RandomData/Numbers.cs | 5298 --------------- .../Backend.Fx/RandomData/TestAddress.cs | 36 - .../RandomData/TestAddressGenerator.cs | 25 - .../Backend.Fx/RandomData/TestChemical.cs | 202 - .../Backend.Fx/RandomData/TestPerson.cs | 144 - .../RandomData/TestPersonGenerator.cs | 50 - .../Backend.Fx/RandomData/TestRandom.cs | 86 - .../{Extensions => Util}/AsyncHelper.cs | 2 +- .../CurrentTHolder.cs | 4 +- .../{Extensions => Util}/DateTimeEx.cs | 6 +- .../DelegateDisposable.cs | 2 +- .../{Extensions => Util}/EnumerableEx.cs | 8 +- .../MultipleDisposable.cs | 2 +- .../ReaderWriterLockSlimExtensions.cs | 2 +- .../{Extensions => Util}/ReflectionEx.cs | 2 +- .../{Extensions => Util}/StringEnumUtil.cs | 2 +- .../{Extensions => Util}/StringEx.cs | 2 +- .../TolerantDateTimeComparer.cs | 28 +- .../BackendFxApplicationHostedService.cs | 1 - .../BackendFxApplicationStartup.cs | 1 - .../Bootstrapping/AspNetCoreApplication.cs | 4 +- .../Bootstrapping/AspNetCoreModule.cs | 4 +- .../Bootstrapping/WaitForBootMiddleware.cs | 1 - .../BackendFxApplicationHubActivator.cs | 1 - ...kend.Fx.Features.MultiTenancy.Admin.csproj | 12 + .../ITenantRepository.cs | 13 + .../Tenant.cs | 17 +- .../TenantService.cs | 144 + .../TenantsAdminApplication.cs | 84 + .../AggregateMapping.cs | 1 - .../DbContextTransactionOperationDecorator.cs | 4 +- .../Bootstrapping/EfCorePersistenceModule.cs | 11 +- .../DbContextExtensions.cs | 3 +- .../Backend.Fx.EfCore5Persistence/EfFlush.cs | 23 +- .../EfRepository.cs | 14 +- .../EntityQueryable.cs | 1 - .../IAggregateMapping.cs | 1 - .../Mssql/MsSqlSequence.cs | 2 +- .../Oracle/OracleSequence.cs | 2 +- .../PlainAggregateMapping.cs | 1 - .../Postgres/PostgresSequence.cs | 2 +- .../AggregateMapping.cs | 1 - .../DbContextTransactionOperationDecorator.cs | 4 +- .../Bootstrapping/EfCorePersistenceModule.cs | 11 +- .../DbContextExtensions.cs | 3 +- .../Backend.Fx.EfCore6Persistence/EfFlush.cs | 23 +- .../EfRepository.cs | 14 +- .../EntityQueryable.cs | 1 - .../IAggregateMapping.cs | 1 - .../Mssql/MsSqlSequence.cs | 2 +- .../Oracle/OracleSequence.cs | 2 +- .../PlainAggregateMapping.cs | 1 - .../Postgres/PostgresSequence.cs | 2 +- .../InMemoryEntityIdGenerator.cs | 3 +- .../InMemoryFlush.cs | 2 +- .../InMemoryPersistenceModule.cs | 10 +- .../InMemoryQueryable.cs | 1 - .../InMemoryRepository.cs | 9 +- .../InMemorySequence.cs | 2 +- .../InMemoryStore.cs | 1 - .../InMemoryView.cs | 1 - .../MicrosoftCompositionRoot.cs | 17 +- .../MicrosoftServiceProviderModule.cs | 2 +- .../Backend.Fx.RabbitMq.csproj | 1 - ...{RabbitMQChannel.cs => RabbitMqChannel.cs} | 56 +- .../Backend.Fx.RabbitMq/RabbitMqMessageBus.cs | 83 +- .../SimpleInjectorCompositionRoot.cs | 25 +- ...xtensions.cs => SimpleInjectorMappings.cs} | 4 +- .../SimpleInjectorModule.cs | 2 +- .../SampleApp/Domain/ICalculationService.cs | 18 +- .../SampleApplicationHostedService.cs | 1 - .../Fixtures/DatabaseFixture.cs | 8 +- .../Fixtures/SqlServerDatabaseFixture.cs | 4 +- .../Fixtures/SqliteDatabaseFixture.cs | 4 +- .../Fixtures/TestDbSession.cs | 7 +- .../Persistence/SampleAppDbBootstrapper.cs | 2 +- .../Persistence/SampleAppIdGenerator.cs | 3 +- .../SampleApp/Runtime/SampleAppBuilder.cs | 3 +- .../TheDbApplicationWithEfCore.cs | 6 +- .../TheRepositoryOfComposedAggregate.cs | 24 +- .../TheRepositoryOfPlainAggregate.cs | 15 +- ...Backend.Fx.EfCore6Persistence.Tests.csproj | 1 + .../Fixtures/DatabaseFixture.cs | 8 +- .../Fixtures/SqlServerDatabaseFixture.cs | 4 +- .../Fixtures/SqliteDatabaseFixture.cs | 8 +- .../Fixtures/TestDbSession.cs | 7 +- .../Persistence/SampleAppDbBootstrapper.cs | 4 +- .../Persistence/SampleAppDbContextFactory.cs | 2 +- .../Persistence/SampleAppIdGenerator.cs | 3 +- .../SampleApp/Runtime/SampleAppBuilder.cs | 8 +- .../TheDbApplicationWithEfCore.cs | 6 +- .../TheRepositoryOfComposedAggregate.cs | 30 +- .../TheRepositoryOfPlainAggregate.cs | 15 +- .../TheRabbitMqMessageBus.cs | 8 +- .../CompositionRootType.cs | 2 +- .../BuildingBlocks/TheAggregateRoot.cs | 22 +- .../BuildingBlocks/TheRepository.cs | 40 +- .../BuildingBlocks/TheValueObject.cs | 1 - .../ConfigurationSettings/TheSetting.cs | 9 +- .../TheSettingSerializerFactory.cs | 2 +- .../TheSettingsService.cs | 19 +- .../Authentication/TheAnonymousIdentity.cs | 2 +- .../TheCurrentIdentityHolder.cs | 2 +- .../Authentication/TheSystemIdentity.cs | 2 +- .../DateAndTime/TheAdjustableClock.cs | 11 +- .../Environment/DateAndTime/TheFrozenClock.cs | 14 +- .../Environment/DateAndTime/TheWallClock.cs | 32 - ...TheAllTenantBackendFxApplicationInvoker.cs | 2 +- .../TheSingleTenantApplication.cs | 1 - .../MultiTenancy/TheTenantService.cs | 2 +- .../Persistence/ThePersistentApplication.cs | 4 +- .../Extensions/TheDateTimeEx.cs | 2 +- .../Extensions/TheEnumerableEx.cs | 2 +- .../Extensions/TheStringEnumUtil.cs | 2 +- .../TheAllowAllImplementation.cs | 2 +- .../TheAuthorizingApplication.cs | 5 +- .../Authorization/TheDenyAllImplementation.cs | 2 +- .../TheDataGenerationContext.cs | 3 +- .../TheGenerateDataOnBootDecorator.cs | 6 +- .../DataGeneration/TheInitialDataGenerator.cs | 2 +- .../DependencyInjection/DITestFakes.cs | 5 +- .../TheBackendFxApplication.cs | 9 +- .../TheBackendFxApplicationAsyncInvoker.cs | 26 +- .../TheBackendFxApplicationInvoker.cs | 3 +- .../DependencyInjection/TheCorrelation.cs | 3 +- .../DependencyInjection/TheCurrentTHolder.cs | 3 +- .../DependencyInjection/TheDomainModule.cs | 12 +- .../DependencyInjection/TheOperation.cs | 2 +- ...uentializingBackendFxApplicationInvoker.cs | 7 +- .../Domain/TestIntegrationEvent.cs | 2 +- ...sageHandler.cs => BlockingEventHandler.cs} | 6 +- .../Integration/DynamicEventHandler.cs | 19 + .../Integration/DynamicMessageHandler.cs | 19 - .../Integration/LongRunningEventHandler.cs | 21 + .../Integration/LongRunningMessageHandler.cs | 21 - .../Integration/SerializingMessageBus.cs | 2 +- .../Integration/TestIntegrationEvent.cs | 2 +- .../TheInMemoryMessageBusChannel.cs | 37 +- .../Integration/TheMessageBus.cs | 69 +- .../Integration/TheMessageBusApplication.cs | 12 +- .../Integration/TheMessageBusScope.cs | 17 +- .../ThrowingDynamicEventHandler.cs | 8 +- ...andler.cs => ThrowingTypedEventHandler.cs} | 8 +- .../Integration/TypedEventHandler.cs | 20 + .../Integration/TypedMessageHandler.cs | 20 - .../IdGeneration/TheHiLoIdGenerator.cs | 3 +- .../TheSequenceHiLoIdGenerator.cs | 2 +- .../IdGeneration/TheSequenceIdGenerator.cs | 3 +- .../Patterns/Jobs/TheApplicationWithJobs.cs | 3 +- .../TheDbTransactionOperationDecorator.cs | 4 +- .../Transactions/TheReadonlyDecorator.cs | 3 +- tests/SampleApp.Domain/Blog.cs | 3 +- tests/SampleApp.Domain/BlogCreatedHandler.cs | 1 - tests/SampleApp.Domain/Blogger.cs | 3 +- tests/SampleApp.Domain/Post.cs | 1 - 315 files changed, 2607 insertions(+), 15559 deletions(-) rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => }/BackendFxApplication.cs (84%) delete mode 100644 src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs delete mode 100644 src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs delete mode 100644 src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs delete mode 100644 src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs delete mode 100644 src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs delete mode 100644 src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs delete mode 100644 src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection/ICompositionRoot.cs => DependencyInjection/CompositionRoot.cs} (66%) rename src/abstractions/Backend.Fx/{Patterns => }/DependencyInjection/IModule.cs (67%) rename src/abstractions/Backend.Fx/{Patterns => }/DependencyInjection/ServiceDescriptorEx.cs (61%) rename src/abstractions/Backend.Fx/{BuildingBlocks => Domain}/AggregateRoot.cs (82%) create mode 100644 src/abstractions/Backend.Fx/Domain/Entity.cs create mode 100644 src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs create mode 100644 src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs rename src/abstractions/Backend.Fx/{Features/Persistence => Domain}/IIdGenerator.cs (61%) create mode 100644 src/abstractions/Backend.Fx/Domain/IRepository.cs rename src/abstractions/Backend.Fx/{BuildingBlocks => Domain}/IView.cs (96%) rename src/abstractions/Backend.Fx/{BuildingBlocks => Domain}/Identified.cs (97%) rename src/abstractions/Backend.Fx/{BuildingBlocks => Domain}/ValueObject.cs (98%) delete mode 100644 src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs delete mode 100644 src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs rename src/abstractions/Backend.Fx/{Environment/Authentication => ExecutionPipeline}/AnonymousIdentity.cs (59%) create mode 100644 src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => ExecutionPipeline}/Correlation.cs (83%) rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => ExecutionPipeline}/CurrentCorrelationHolder.cs (55%) rename src/abstractions/Backend.Fx/{Environment/Authentication => ExecutionPipeline}/CurrentIdentityHolder.cs (84%) rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => ExecutionPipeline}/ExceptionLoggingAndHandlingInvoker.cs (61%) rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => ExecutionPipeline}/ExceptionLoggingInvoker.cs (62%) create mode 100644 src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs create mode 100644 src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => ExecutionPipeline}/Operation.cs (89%) rename src/abstractions/Backend.Fx/{Environment/Authentication => ExecutionPipeline}/SystemIdentity.cs (59%) rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection/BackendFxApplicationDecorator.cs => Extensions/BackendFxApplicationExtension.cs} (82%) create mode 100644 src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs rename src/abstractions/Backend.Fx/{Features => Extensions}/DataGeneration/DataGenerationModule.cs (84%) rename src/abstractions/Backend.Fx/{Features => Extensions}/DataGeneration/DataGenerator.cs (83%) rename src/abstractions/Backend.Fx/{Features => Extensions}/DataGeneration/IDemoDataGenerator.cs (79%) rename src/abstractions/Backend.Fx/{Features => Extensions}/DataGeneration/IProductiveDataGenerator.cs (79%) create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs rename src/abstractions/Backend.Fx/{Features/MessageBus/InMemoryMessageBusChannel.cs => Extensions/MessageBus/InProc/InProcMessageBusChannel.cs} (69%) create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs rename src/abstractions/Backend.Fx/{Features => Extensions}/MessageBus/MessageBusScope.cs (55%) create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs rename src/abstractions/Backend.Fx/{Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs => Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs} (60%) create mode 100644 src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/DbConnectionOperationDecorator.cs (87%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/DbTransactionOperationDecorator.cs (94%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/FlushDomainEventAggregatorDecorator.cs (92%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/FlushOperationDecorator.cs (78%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/HiLoIdGenerator.cs (93%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/ICanFlush.cs (59%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/IDatabaseAvailabilityAwaiter.cs (80%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/IDatabaseBootstrapper.cs (87%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/ISequence.cs (80%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/PersistentApplication.cs (50%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/SequenceHiLoIdGenerator.cs (89%) rename src/abstractions/Backend.Fx/{Features => Extensions}/Persistence/SequenceIdGenerator.cs (81%) create mode 100644 src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs rename src/abstractions/Backend.Fx/Features/Authorization/{AuthorizingApplication.cs => AuthorizationModule.cs} (72%) rename src/abstractions/Backend.Fx/Features/Authorization/{AggregateAuthorization.cs => AuthorizationPolicy.cs} (67%) create mode 100644 src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs create mode 100644 src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs rename src/abstractions/Backend.Fx/Features/Authorization/{IAggregateAuthorization.cs => IAuthorizationPolicy.cs} (70%) create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/BooleanSerializer.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/DateTimeSerializer.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/DoubleSerializer.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingRepository.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingSerializer.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/IntegerSerializer.cs rename src/abstractions/Backend.Fx/{ => Features}/ConfigurationSettings/SettingSerializerFactory.cs (96%) create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingsCategory.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/StringSerializer.cs delete mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs delete mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs delete mode 100644 src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs create mode 100644 src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesModule.cs rename src/abstractions/Backend.Fx/{BuildingBlocks => Features/DomainServices}/IApplicationService.cs (80%) rename src/abstractions/Backend.Fx/{BuildingBlocks => Features/DomainServices}/IDomainService.cs (80%) delete mode 100644 src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs rename src/abstractions/Backend.Fx/{Environment => Features}/MultiTenancy/CurrentTenantIdHolder.cs (90%) create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/ICurrentTenantIdSelector.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs rename src/abstractions/Backend.Fx/{Environment => Features}/MultiTenancy/ITenantWideMutex.cs (62%) create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutexManager.cs rename src/abstractions/Backend.Fx/{Environment/MultiTenancy/ITenantWideMutexManager.cs => Features/MultiTenancy/InMemoryTenantWideMutexManager.cs} (85%) create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs rename src/abstractions/Backend.Fx/{Environment => Features}/MultiTenancy/TenantId.cs (71%) create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/TenantOperationDecorator.cs delete mode 100644 src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs delete mode 100644 src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs create mode 100644 src/abstractions/Backend.Fx/Hacking/AdjustableClock.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs delete mode 100644 src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs delete mode 100644 src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/Generator.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/Letters.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/Names.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/Numbers.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/TestAddress.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/TestChemical.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/TestPerson.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs delete mode 100644 src/abstractions/Backend.Fx/RandomData/TestRandom.cs rename src/abstractions/Backend.Fx/{Extensions => Util}/AsyncHelper.cs (99%) rename src/abstractions/Backend.Fx/{Patterns/DependencyInjection => Util}/CurrentTHolder.cs (94%) rename src/abstractions/Backend.Fx/{Extensions => Util}/DateTimeEx.cs (95%) rename src/abstractions/Backend.Fx/{Extensions => Util}/DelegateDisposable.cs (92%) rename src/abstractions/Backend.Fx/{Extensions => Util}/EnumerableEx.cs (74%) rename src/abstractions/Backend.Fx/{Extensions => Util}/MultipleDisposable.cs (93%) rename src/abstractions/Backend.Fx/{Extensions => Util}/ReaderWriterLockSlimExtensions.cs (97%) rename src/abstractions/Backend.Fx/{Extensions => Util}/ReflectionEx.cs (98%) rename src/abstractions/Backend.Fx/{Extensions => Util}/StringEnumUtil.cs (95%) rename src/abstractions/Backend.Fx/{Extensions => Util}/StringEx.cs (95%) rename src/abstractions/Backend.Fx/{Extensions => Util}/TolerantDateTimeComparer.cs (67%) create mode 100644 src/features/Backend.Fx.Features.MultiTenancy.Admin/Backend.Fx.Features.MultiTenancy.Admin.csproj create mode 100644 src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs rename src/{abstractions/Backend.Fx/Environment/MultiTenancy => features/Backend.Fx.Features.MultiTenancy.Admin}/Tenant.cs (77%) create mode 100644 src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs create mode 100644 src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantsAdminApplication.cs rename src/implementations/Backend.Fx.RabbitMq/{RabbitMQChannel.cs => RabbitMqChannel.cs} (78%) rename src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/{MicrosoftDependencyInjectionExtensions.cs => SimpleInjectorMappings.cs} (79%) delete mode 100644 tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs rename tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/{BlockingMessageHandler.cs => BlockingEventHandler.cs} (72%) create mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs create mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs rename tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/{ThrowingTypedMessageHandler.cs => ThrowingTypedEventHandler.cs} (56%) create mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs diff --git a/Backend.Fx.sln b/Backend.Fx.sln index 07533205..6ca1ad95 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -62,6 +62,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.TestUtil", "test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Domain", "tests\SampleApp.Domain\SampleApp.Domain.csproj", "{ADCBD99B-0C75-484C-9C7F-6E174455B3AE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "features", "features", "{060355A0-F0E1-41EF-9F71-CDE5021CDC3A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.Features.MultiTenancy.Admin", "src\features\Backend.Fx.Features.MultiTenancy.Admin\Backend.Fx.Features.MultiTenancy.Admin.csproj", "{FF5F8A10-A439-4859-BE78-C2764EDF6462}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +135,10 @@ Global {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Release|Any CPU.Build.0 = Release|Any CPU + {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -159,6 +167,8 @@ Global {B4791DB0-F8DD-4248-86CB-407E46F55B13} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {ADCBD99B-0C75-484C-9C7F-6E174455B3AE} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} + {060355A0-F0E1-41EF-9F71-CDE5021CDC3A} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092} + {FF5F8A10-A439-4859-BE78-C2764EDF6462} = {060355A0-F0E1-41EF-9F71-CDE5021CDC3A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969} diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index 7d1d838e..f6939a4e 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -8,6 +8,7 @@ false false false + 8 @@ -37,9 +38,19 @@ + + + + ..\..\..\..\..\.nuget\packages\system.memory\4.5.4\lib\netstandard2.0\System.Memory.dll + + + ..\..\..\..\..\.nuget\packages\system.text.json\4.7.2\lib\netstandard2.0\System.Text.Json.dll + + + \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs b/src/abstractions/Backend.Fx/BackendFxApplication.cs similarity index 84% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs rename to src/abstractions/Backend.Fx/BackendFxApplication.cs index b2ba4a69..123ca65b 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/BackendFxApplication.cs @@ -3,12 +3,14 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Extensions; using Backend.Fx.Logging; +using Backend.Fx.Util; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx { /// /// The root object of the whole backend fx application framework @@ -16,20 +18,15 @@ namespace Backend.Fx.Patterns.DependencyInjection public interface IBackendFxApplication : IDisposable { /// - /// The async invoker runs a given action asynchronously in an application scope with injection facilities + /// The invoker runs a given action asynchronously in an application scope with injection facilities /// - IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } + IBackendFxApplicationInvoker Invoker { get; } /// /// The composition root of the dependency injection framework /// ICompositionRoot CompositionRoot { get; } - /// - /// The invoker runs a given action in an application scope with injection facilities - /// - IBackendFxApplicationInvoker Invoker { get; } - /// /// The global exception logger of this application /// @@ -48,7 +45,7 @@ public interface IBackendFxApplication : IDisposable /// Task BootAsync(CancellationToken cancellationToken = default); - TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationDecorator; + TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationExtension; } @@ -73,24 +70,24 @@ public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger e string.Join(", ", assemblies.Select(ass => ass.GetName().Name))); var invoker = new BackendFxApplicationInvoker(compositionRoot); - AsyncInvoker = new ExceptionLoggingAsyncInvoker(exceptionLogger, invoker); + Invoker = new ExceptionLoggingInvoker(exceptionLogger, invoker); CompositionRoot = compositionRoot; ExceptionLogger = exceptionLogger; Assemblies = assemblies; - CompositionRoot.RegisterModules(new DomainModule(Assemblies)); + CompositionRoot.RegisterModules(new ExecutionPipelineModule(withFrozenClockDuringExecution: true)); } public Assembly[] Assemblies { get; } - public IBackendFxApplicationAsyncInvoker AsyncInvoker { get; } + public IBackendFxApplicationInvoker Invoker { get; } public ICompositionRoot CompositionRoot { get; } public IExceptionLogger ExceptionLogger { get; } - public IBackendFxApplicationInvoker Invoker { get; } + public bool IsMultiTenancyApplication { get; set; } public Task BootAsync(CancellationToken cancellationToken = default) { @@ -101,7 +98,7 @@ public Task BootAsync(CancellationToken cancellationToken = default) } public virtual TBackendFxApplicationDecorator As() - where TBackendFxApplicationDecorator : BackendFxApplicationDecorator + where TBackendFxApplicationDecorator : BackendFxApplicationExtension { return null; } diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs deleted file mode 100644 index 5ddc4d22..00000000 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Entity.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; -using Backend.Fx.Extensions; -using JetBrains.Annotations; - -namespace Backend.Fx.BuildingBlocks -{ - /// - /// An object that is not defined by its attributes, but rather by a thread of continuity and its identity. - /// https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks - /// - public abstract class Entity : Identified - { - protected Entity() - { - } - - protected Entity(int id) - { - Id = id; - } - - public DateTime CreatedOn { get; protected set; } - - [StringLength(100)] public string CreatedBy { get; protected set; } - - public DateTime? ChangedOn { get; protected set; } - - [StringLength(100)] public string ChangedBy { get; protected set; } - - public void SetCreatedProperties([NotNull] string createdBy, DateTime createdOn) - { - if (createdBy == null) - { - throw new ArgumentNullException(nameof(createdBy)); - } - - if (createdBy == string.Empty) - { - throw new ArgumentException(nameof(createdBy)); - } - - CreatedBy = createdBy.Cut(100); - CreatedOn = createdOn; - } - - public void SetModifiedProperties([NotNull] string changedBy, DateTime changedOn) - { - if (changedBy == null) - { - throw new ArgumentNullException(nameof(changedBy)); - } - - if (changedBy == string.Empty) - { - throw new ArgumentException(nameof(changedBy)); - } - - ChangedBy = changedBy.Cut(100); - ChangedOn = changedOn; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs deleted file mode 100644 index 5af37ee6..00000000 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IAsyncRepository.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; - -namespace Backend.Fx.BuildingBlocks -{ - /// - /// Encapsulates methods for retrieving domain objects - /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks - /// - /// - [PublicAPI] - public interface IAsyncRepository where TAggregateRoot : AggregateRoot - { - Task SingleAsync(int id, CancellationToken cancellationToken = default); - Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default); - Task GetAllAsync(CancellationToken cancellationToken = default); - Task AnyAsync(CancellationToken cancellationToken = default); - Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default); - IQueryable AggregateQueryable { get; } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs deleted file mode 100644 index 675022cc..00000000 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; - -namespace Backend.Fx.BuildingBlocks -{ - /// - /// Encapsulates methods for retrieving domain objects - /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks - /// - /// - [PublicAPI] - public interface IRepository where TAggregateRoot : AggregateRoot - { - TAggregateRoot Single(int id); - TAggregateRoot SingleOrDefault(int id); - TAggregateRoot[] GetAll(); - void Delete(TAggregateRoot aggregateRoot); - void Add(TAggregateRoot aggregateRoot); - void AddRange(TAggregateRoot[] aggregateRoots); - bool Any(); - TAggregateRoot[] Resolve(IEnumerable ids); - IQueryable AggregateQueryable { get; } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs b/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs deleted file mode 100644 index fc607fe8..00000000 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Repository.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Exceptions; -using Backend.Fx.Extensions; -using Backend.Fx.Features.Authorization; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.BuildingBlocks -{ - public abstract class Repository : IRepository where TAggregateRoot : AggregateRoot - { - private static readonly ILogger Logger = Log.Create>(); - private readonly IAggregateAuthorization _aggregateAuthorization; - private readonly ICurrentTHolder _tenantIdHolder; - - protected Repository(ICurrentTHolder tenantIdHolder, IAggregateAuthorization aggregateAuthorization) - { - Logger.LogTrace( - "Instantiating a new Repository<{AggregateTypeName}> for tenant [{TenantId}]", - AggregateTypeName, - tenantIdHolder.Current.HasValue ? tenantIdHolder.Current.Value.ToString() : "null"); - _tenantIdHolder = tenantIdHolder; - _aggregateAuthorization = aggregateAuthorization; - } - - protected static string AggregateTypeName => typeof(TAggregateRoot).Name; - - protected abstract IQueryable RawAggregateQueryable { get; } - - public IQueryable AggregateQueryable - { - get - { - if (_tenantIdHolder.Current.HasValue) - { - return _aggregateAuthorization.Filter(RawAggregateQueryable - .Where(agg => agg.TenantId == _tenantIdHolder.Current.Value)); - } - - return RawAggregateQueryable.Where(agg => false); - } - } - - public TAggregateRoot Single(int id) - { - Logger.LogDebug("Getting single {AggregateTypeName}[{Id}]", AggregateTypeName, id); - TAggregateRoot aggregateRoot = AggregateQueryable.FirstOrDefault(agg => agg.Id.Equals(id)); - if (aggregateRoot == null) - { - throw new NotFoundException(id); - } - - return aggregateRoot; - } - - public TAggregateRoot SingleOrDefault(int id) - { - Logger.LogDebug("Getting single or default {AggregateTypeName}[{Id}]", AggregateTypeName, id); - return AggregateQueryable.FirstOrDefault(agg => agg.Id.Equals(id)); - } - - public TAggregateRoot[] GetAll() - { - return AggregateQueryable.ToArray(); - } - - public void Delete([NotNull] TAggregateRoot aggregateRoot) - { - if (aggregateRoot == null) - { - throw new ArgumentNullException(nameof(aggregateRoot)); - } - - Logger.LogDebug("Deleting {AggregateTypeName}[{Id}]", AggregateTypeName, aggregateRoot.Id); - if (aggregateRoot.TenantId != _tenantIdHolder.Current.Value || !_aggregateAuthorization.CanDelete(aggregateRoot)) - { - throw new ForbiddenException($"You are not allowed to delete {typeof(TAggregateRoot).Name}[{aggregateRoot.Id}]"); - } - - DeletePersistent(aggregateRoot); - } - - public void Add([NotNull] TAggregateRoot aggregateRoot) - { - if (aggregateRoot == null) - { - throw new ArgumentNullException(nameof(aggregateRoot)); - } - - if (_aggregateAuthorization.CanCreate(aggregateRoot)) - { - Logger.LogDebug("Adding {AggregateTypeName}[{Id}]", AggregateTypeName, aggregateRoot.Id); - aggregateRoot.TenantId = _tenantIdHolder.Current.Value; - AddPersistent(aggregateRoot); - } - else - { - throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}"); - } - } - - public void AddRange([NotNull] TAggregateRoot[] aggregateRoots) - { - if (aggregateRoots == null) - { - throw new ArgumentNullException(nameof(aggregateRoots)); - } - - aggregateRoots.ForAll(agg => - { - if (!_aggregateAuthorization.CanCreate(agg)) - { - throw new ForbiddenException($"You are not allowed to create records of type {typeof(TAggregateRoot).Name}"); - } - }); - - Logger.LogDebug("Adding {Count} items of type {AggregateTypeName}", aggregateRoots.Length, AggregateTypeName); - - aggregateRoots.ForAll(agg => agg.TenantId = _tenantIdHolder.Current.Value); - - AddRangePersistent(aggregateRoots); - } - - public bool Any() - { - return AggregateQueryable.Any(); - } - - public TAggregateRoot[] Resolve(IEnumerable ids) - { - if (ids == null) - { - return Array.Empty(); - } - - int[] idsToResolve = ids as int[] ?? ids.ToArray(); - TAggregateRoot[] resolved = AggregateQueryable.Where(agg => idsToResolve.Contains(agg.Id)).ToArray(); - if (resolved.Length != idsToResolve.Length) - { - throw new ArgumentException($"The following {AggregateTypeName} ids could not be resolved: {string.Join(", ", idsToResolve.Except(resolved.Select(agg => agg.Id)))}"); - } - return resolved; - } - - protected abstract void AddPersistent(TAggregateRoot aggregateRoot); - - protected abstract void AddRangePersistent(TAggregateRoot[] aggregateRoots); - - protected abstract void DeletePersistent(TAggregateRoot aggregateRoot); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs deleted file mode 100644 index 4fc9868d..00000000 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/ISettingSerializer.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Globalization; -using JetBrains.Annotations; - -namespace Backend.Fx.ConfigurationSettings -{ - public interface ISettingSerializer - { - } - - public interface ISettingSerializer : ISettingSerializer - { - string Serialize(T setting); - T Deserialize(string value); - } - - [UsedImplicitly] - public class StringSerializer : ISettingSerializer - { - public string Serialize(string setting) - { - return setting; - } - - public string Deserialize(string value) - { - return value; - } - } - - [UsedImplicitly] - public class IntegerSerializer : ISettingSerializer - { - public string Serialize(int? setting) - { - return setting?.ToString(CultureInfo.InvariantCulture); - } - - public int? Deserialize(string value) - { - return string.IsNullOrWhiteSpace(value) ? (int?) null : int.Parse(value, CultureInfo.InvariantCulture); - } - } - - [UsedImplicitly] - public class DoubleSerializer : ISettingSerializer - { - public string Serialize(double? setting) - { - return setting?.ToString("r", CultureInfo.InvariantCulture); - } - - public double? Deserialize(string value) - { - return string.IsNullOrWhiteSpace(value) ? (double?) null : double.Parse(value, CultureInfo.InvariantCulture); - } - } - - [UsedImplicitly] - public class BooleanSerializer : ISettingSerializer - { - public string Serialize(bool? setting) - { - return setting?.ToString(); - } - - public bool? Deserialize(string value) - { - return string.IsNullOrWhiteSpace(value) ? (bool?) null : bool.Parse(value); - } - } - - [UsedImplicitly] - public class DateTimeSerializer : ISettingSerializer - { - public string Serialize(DateTime? setting) - { - return setting?.ToString("O", CultureInfo.InvariantCulture); - } - - public DateTime? Deserialize(string value) - { - return string.IsNullOrWhiteSpace(value) ? (DateTime?) null : DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs deleted file mode 100644 index c1e55a92..00000000 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/Setting.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Backend.Fx.BuildingBlocks; -using JetBrains.Annotations; - -namespace Backend.Fx.ConfigurationSettings -{ - public class Setting : AggregateRoot - { - [UsedImplicitly] - private Setting() - { - } - - public Setting(int id, string key) : base(id) - { - Key = key; - } - - public string Key { get; [UsedImplicitly] private set; } - public string SerializedValue { get; private set; } - - public T GetValue(ISettingSerializer serializer) - { - return serializer.Deserialize(SerializedValue); - } - - public void SetValue(ISettingSerializer serializer, T value) - { - SerializedValue = serializer.Serialize(value); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs b/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs deleted file mode 100644 index 98649a6d..00000000 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingsService.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Linq; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Features.Persistence; - -namespace Backend.Fx.ConfigurationSettings -{ - public abstract class SettingsService - { - private readonly string _category; - private readonly IEntityIdGenerator _idGenerator; - private readonly IRepository _settingRepository; - private readonly ISettingSerializerFactory _settingSerializerFactory; - - protected SettingsService(string category, IEntityIdGenerator idGenerator, IRepository settingRepository, ISettingSerializerFactory settingSerializerFactory) - { - _category = category; - _idGenerator = idGenerator; - _settingRepository = settingRepository; - _settingSerializerFactory = settingSerializerFactory; - } - - protected T ReadSetting(string key) - { - var categoryKey = _category + "." + key; - var setting = _settingRepository.AggregateQueryable.SingleOrDefault(s => s.Key == categoryKey); - if (setting == null) - { - return default(T); - } - - var serializer = _settingSerializerFactory.GetSerializer(); - return setting.GetValue(serializer); - } - - protected void WriteSetting(string key, T value) - { - var categoryKey = _category + "." + key; - var setting = _settingRepository.AggregateQueryable.SingleOrDefault(s => s.Key == categoryKey); - if (setting == null) - { - setting = new Setting(_idGenerator.NextId(), categoryKey); - _settingRepository.Add(setting); - } - - var serializer = _settingSerializerFactory.GetSerializer(); - setting.SetValue(serializer, value); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs b/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs similarity index 66% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs rename to src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs index 915109fc..556f9040 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ICompositionRoot.cs +++ b/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using Backend.Fx.Logging; +using Backend.Fx.Util; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.DependencyInjection { /// /// Encapsulates the injection framework of choice. The implementation follows the Register/Resolve/Release pattern. @@ -13,6 +14,7 @@ namespace Backend.Fx.Patterns.DependencyInjection /// the domain or application logic, this would result in the Service Locator anti pattern, described here: /// http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ /// + [PublicAPI] public interface ICompositionRoot : IDisposable { void Verify(); @@ -33,13 +35,15 @@ public interface ICompositionRoot : IDisposable IServiceProvider ServiceProvider { get; } } - + [PublicAPI] public abstract class CompositionRoot : ICompositionRoot { private static readonly ILogger Logger = Log.Create(); public abstract IServiceProvider ServiceProvider { get; } + public abstract bool HasRegistration(); + public abstract void Verify(); public virtual void RegisterModules(params IModule[] modules) @@ -52,6 +56,7 @@ public virtual void RegisterModules(params IModule[] modules) } public abstract void Register(ServiceDescriptor serviceDescriptor); + public abstract void RegisterDecorator(ServiceDescriptor serviceDescriptor); public abstract void RegisterCollection(IEnumerable serviceDescriptors); @@ -60,6 +65,24 @@ public virtual void RegisterModules(params IModule[] modules) protected abstract void Dispose(bool disposing); + protected static void LogAddRegistration(ServiceDescriptor serviceDescriptor) + => LogDetails("Adding", serviceDescriptor); + + protected static void LogReplaceRegistration(ServiceDescriptor serviceDescriptor) + => LogDetails("Replacing", serviceDescriptor); + + protected static void LogAddDecoratorRegistration(ServiceDescriptor serviceDescriptor) + => LogDetails("Adding decorator", serviceDescriptor); + + private static void LogDetails(string prefix, ServiceDescriptor serviceDescriptor) + { + Logger.LogDebug("{Prefix} {Lifetime} registration for {ServiceType}: {ImplementationType}", + prefix, + serviceDescriptor.Lifetime.ToString(), + serviceDescriptor.ServiceType.GetDetailedTypeName(), + serviceDescriptor.GetImplementationTypeDescription()); + } + public void Dispose() { Dispose(true); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs b/src/abstractions/Backend.Fx/DependencyInjection/IModule.cs similarity index 67% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs rename to src/abstractions/Backend.Fx/DependencyInjection/IModule.cs index 762f2042..97647018 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/IModule.cs +++ b/src/abstractions/Backend.Fx/DependencyInjection/IModule.cs @@ -1,8 +1,11 @@ -namespace Backend.Fx.Patterns.DependencyInjection +using JetBrains.Annotations; + +namespace Backend.Fx.DependencyInjection { /// /// A logically cohesive bunch of services /// + [PublicAPI] public interface IModule { void Register(ICompositionRoot compositionRoot); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs b/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs similarity index 61% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs rename to src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs index dcf78123..87f6a4c0 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ServiceDescriptorEx.cs +++ b/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs @@ -1,20 +1,13 @@ -using Backend.Fx.Extensions; +using Backend.Fx.Util; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.DependencyInjection { + [PublicAPI] public static class ServiceDescriptorEx { - public static void LogDetails(this ServiceDescriptor serviceDescriptor, ILogger logger, string prefix = null) - { - logger.LogDebug("{Prefix} {Lifetime} registration for {ServiceType}: {ImplementationType}", - prefix, - serviceDescriptor.Lifetime.ToString(), - serviceDescriptor.ServiceType.GetDetailedTypeName(), - serviceDescriptor.GetImplementationTypeDescription()); - } - public static string GetImplementationTypeDescription(this ServiceDescriptor serviceDescriptor) { if (serviceDescriptor.ImplementationFactory != null) diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs b/src/abstractions/Backend.Fx/Domain/AggregateRoot.cs similarity index 82% rename from src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs rename to src/abstractions/Backend.Fx/Domain/AggregateRoot.cs index e22101f1..17006bbf 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/AggregateRoot.cs +++ b/src/abstractions/Backend.Fx/Domain/AggregateRoot.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.BuildingBlocks +namespace Backend.Fx.Domain { /// /// A collection of objects that are bound together by a root entity @@ -13,7 +13,5 @@ protected AggregateRoot() protected AggregateRoot(int id) : base(id) { } - - public int TenantId { get; set; } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/Entity.cs b/src/abstractions/Backend.Fx/Domain/Entity.cs new file mode 100644 index 00000000..ceeadb4a --- /dev/null +++ b/src/abstractions/Backend.Fx/Domain/Entity.cs @@ -0,0 +1,18 @@ +namespace Backend.Fx.Domain +{ + /// + /// An object that is not defined by its attributes, but rather by a thread of continuity and its identity. + /// https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks + /// + public abstract class Entity : Identified + { + protected Entity() + { + } + + protected Entity(int id) + { + Id = id; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs b/src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs new file mode 100644 index 00000000..31551810 --- /dev/null +++ b/src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs @@ -0,0 +1,8 @@ +using System.Linq; + +namespace Backend.Fx.Domain +{ + public interface IAsyncAggregateQueryable : IAsyncQueryable + where TAggregateRoot: AggregateRoot + { } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs b/src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs new file mode 100644 index 00000000..302da473 --- /dev/null +++ b/src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs @@ -0,0 +1,6 @@ +namespace Backend.Fx.Domain +{ + public interface IEntityIdGenerator : IIdGenerator + { + } +} diff --git a/src/abstractions/Backend.Fx/Features/Persistence/IIdGenerator.cs b/src/abstractions/Backend.Fx/Domain/IIdGenerator.cs similarity index 61% rename from src/abstractions/Backend.Fx/Features/Persistence/IIdGenerator.cs rename to src/abstractions/Backend.Fx/Domain/IIdGenerator.cs index fddebf5e..b2282f71 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/IIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Domain/IIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Domain { public interface IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Domain/IRepository.cs b/src/abstractions/Backend.Fx/Domain/IRepository.cs new file mode 100644 index 00000000..a62d4e13 --- /dev/null +++ b/src/abstractions/Backend.Fx/Domain/IRepository.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Backend.Fx.Domain +{ + /// + /// Encapsulates methods for retrieving domain objects + /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks + /// + /// + [PublicAPI] + public interface IRepository where TAggregateRoot : AggregateRoot + { + Task SingleAsync(int id, CancellationToken cancellationToken = default); + + Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default); + + Task GetAllAsync(CancellationToken cancellationToken = default); + + Task AnyAsync(CancellationToken cancellationToken = default); + + Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default); + + Task DeleteAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default); + + Task AddAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default); + + Task AddRangeAsync(TAggregateRoot[] aggregateRoots, CancellationToken cancellationToken = default); + } + + public abstract class Repository : IRepository where TAggregateRoot : AggregateRoot + { + private readonly IAsyncAggregateQueryable _queryable; + + protected Repository(IAsyncAggregateQueryable queryable) + { + _queryable = queryable; + } + + public async Task SingleAsync(int id, CancellationToken cancellationToken = default) + { + return await _queryable.SingleAsync(ar => ar.Id == id, cancellationToken).ConfigureAwait(false); + } + + public async Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default) + { + return await _queryable.SingleOrDefaultAsync(ar => ar.Id == id, cancellationToken).ConfigureAwait(false); + } + + public async Task GetAllAsync(CancellationToken cancellationToken = default) + { + return await _queryable.ToArrayAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task AnyAsync(CancellationToken cancellationToken = default) + { + return await _queryable.AnyAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task ResolveAsync( + IEnumerable ids, + CancellationToken cancellationToken = default) + { + var idArray = ids as int[] ?? ids.ToArray(); + var resolved = new TAggregateRoot[idArray.Length]; + for (var i = 0; i < idArray.Length; i++) + { + resolved[i] = await SingleAsync(idArray[i], cancellationToken).ConfigureAwait(false); + } + + return resolved; + } + + public abstract Task DeleteAsync(TAggregateRoot aggregateRoot,CancellationToken cancellationToken = default); + + public abstract Task AddAsync(TAggregateRoot aggregateRoot,CancellationToken cancellationToken = default); + + public abstract Task AddRangeAsync(TAggregateRoot[] aggregateRoots, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs b/src/abstractions/Backend.Fx/Domain/IView.cs similarity index 96% rename from src/abstractions/Backend.Fx/BuildingBlocks/IView.cs rename to src/abstractions/Backend.Fx/Domain/IView.cs index 71df6627..3b1766c6 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IView.cs +++ b/src/abstractions/Backend.Fx/Domain/IView.cs @@ -5,7 +5,7 @@ using System.Linq.Expressions; using JetBrains.Annotations; -namespace Backend.Fx.BuildingBlocks +namespace Backend.Fx.Domain { [PublicAPI] public interface IView : IQueryable diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs b/src/abstractions/Backend.Fx/Domain/Identified.cs similarity index 97% rename from src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs rename to src/abstractions/Backend.Fx/Domain/Identified.cs index f37a3bde..b30c1d9f 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/Identified.cs +++ b/src/abstractions/Backend.Fx/Domain/Identified.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using JetBrains.Annotations; -namespace Backend.Fx.BuildingBlocks +namespace Backend.Fx.Domain { [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] public abstract class Identified : IEquatable diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs b/src/abstractions/Backend.Fx/Domain/ValueObject.cs similarity index 98% rename from src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs rename to src/abstractions/Backend.Fx/Domain/ValueObject.cs index f4d5a5f3..ed714c29 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/ValueObject.cs +++ b/src/abstractions/Backend.Fx/Domain/ValueObject.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace Backend.Fx.BuildingBlocks +namespace Backend.Fx.Domain { /// /// An object that contains attributes but has no conceptual identity. diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs deleted file mode 100644 index cb7694f7..00000000 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/AdjustableClock.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using Backend.Fx.Logging; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Environment.DateAndTime -{ - public class AdjustableClock : IClock - { - private static readonly ILogger Logger = Log.Create(); - - private readonly IClock _clockImplementation; - private DateTime? _overriddenUtcNow; - - public AdjustableClock(IClock clockImplementation) - { - _clockImplementation = clockImplementation; - } - - public DateTime UtcNow => _overriddenUtcNow ?? _clockImplementation.UtcNow; - - public void OverrideUtcNow(DateTime utcNow) - { - Logger.LogTrace("Adjusting clock to {UtcNow}", utcNow); - if (utcNow.Kind == DateTimeKind.Unspecified) - { - Logger.LogWarning("Overriding UtcNow with a date time value of unspecified kind. Assuming kind:Utc"); - utcNow = new DateTime(utcNow.Ticks, DateTimeKind.Utc); - } - - if (utcNow.Kind == DateTimeKind.Local) - { - throw new ArgumentException("When overriding the UtcNow value you have to provide a DateTime value of kind Utc"); - } - - _overriddenUtcNow = utcNow; - } - - public DateTime Advance(TimeSpan timespan) - { - _overriddenUtcNow = _overriddenUtcNow ?? _clockImplementation.UtcNow; - Logger.LogTrace("Advancing clock by {TimeSpan}", timespan); - _overriddenUtcNow = _overriddenUtcNow.Value.Add(timespan); - return _overriddenUtcNow.Value; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs deleted file mode 100644 index b67d9fb8..00000000 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/FrozenClock.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Backend.Fx.Logging; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Environment.DateAndTime -{ - /// - /// Best practice for web (service) applications: time does not advance during a single request - /// - public class FrozenClock : IClock - { - private static readonly ILogger Logger = Log.Create(); - - public FrozenClock(IClock clock) - { - UtcNow = clock.UtcNow; - Logger.LogTrace("Freezing clock at {UtcNow}", UtcNow); - } - - public DateTime UtcNow { get; } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs deleted file mode 100644 index dcd4a886..00000000 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/IClock.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Backend.Fx.Environment.DateAndTime -{ - /// - /// Wraps access to DateTime.UtcNow. By means of this interface the current time can be mocked. - /// the database should only store universal date and time values, that could be translated into user's time by applying a UtcOffset - /// - public interface IClock - { - DateTime UtcNow { get; } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs b/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs deleted file mode 100644 index 5ca43a75..00000000 --- a/src/abstractions/Backend.Fx/Environment/DateAndTime/WallClock.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Backend.Fx.Environment.DateAndTime -{ - /// - /// The real system clock - /// - public class WallClock : IClock - { - public DateTime UtcNow => DateTime.UtcNow; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs deleted file mode 100644 index dda74153..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/AllTenantBackendFxApplicationInvoker.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Environment.MultiTenancy -{ - public class AllTenantBackendFxApplicationInvoker - { - private static readonly ILogger Logger = Log.Create(); - private readonly ITenantIdProvider _tenantIdProvider; - private readonly IBackendFxApplicationInvoker _invoker; - - public AllTenantBackendFxApplicationInvoker(ITenantIdProvider tenantIdProvider, IBackendFxApplicationInvoker invoker) - { - _tenantIdProvider = tenantIdProvider; - _invoker = invoker; - } - - public void Invoke(Action action) - { - var correlationId = Guid.NewGuid(); - TenantId[] tenantIds = _tenantIdProvider.GetActiveTenantIds(); - Logger.LogDebug("Action will be called in tenants: {TenantIds}", string.Join(",", tenantIds.Select(t => t.ToString()))); - foreach (TenantId tenantId in tenantIds) - { - _invoker.Invoke(action, new SystemIdentity(), tenantId, correlationId); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs deleted file mode 100644 index 44873e50..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantIdProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - /// - /// By means of this instance, the IBackendFxApplication gains insight about all active tenants. This is required, when for example a job - /// should be executed for all tenants or data should be generated for all tenants during startup. - /// The can provide such implementation, but this can only be done in process. When the tenant service is - /// running in another process, the implementation must be done using a suitable remoting technology. - /// - public interface ITenantIdProvider - { - TenantId[] GetActiveTenantIds(); - TenantId[] GetActiveDemonstrationTenantIds(); - TenantId[] GetActiveProductionTenantIds(); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs deleted file mode 100644 index 2f8292f5..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Backend.Fx.Environment.MultiTenancy -{ - public interface ITenantRepository - { - void SaveTenant(Tenant tenant); - - Tenant[] GetTenants(); - - Tenant GetTenant(TenantId tenantId); - - void DeleteTenant(TenantId tenantId); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs deleted file mode 100644 index 26de3bf9..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/SingleTenantApplication.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Linq; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using JetBrains.Annotations; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Environment.MultiTenancy -{ - [PublicAPI] - public class SingleTenantApplication : BackendFxApplicationDecorator - { - private static readonly ILogger Logger = Log.Create(); - private readonly ITenantService _tenantService; - private readonly bool _singleTenantIsDemoTenant; - private readonly object _padlock = new object(); - - public SingleTenantApplication( - ITenantRepository tenantRepository, - bool singleTenantIsDemoTenant, - IBackendFxApplication application) - : base(application) - { - _tenantService = new TenantService(tenantRepository); - _singleTenantIsDemoTenant = singleTenantIsDemoTenant; - } - - public TenantId TenantId { get; private set; } - - public ITenantIdProvider TenantProvider => _tenantService.TenantIdProvider; - - public void Boot() - { - lock (_padlock) - { - Logger.Info("Ensuring existence of single tenant"); - TenantId = _tenantService.GetActiveTenants().SingleOrDefault()?.GetTenantId() - ?? _tenantService.CreateTenant("Single Tenant", - "This application runs in single tenant mode", - _singleTenantIsDemoTenant); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs b/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs deleted file mode 100644 index 09fee0f1..00000000 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantService.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Linq; -using Backend.Fx.Exceptions; -using Backend.Fx.Logging; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Environment.MultiTenancy -{ - /// - /// Encapsulates the management of tenants - /// Note that this should not use repositories and other building blocks, but access the persistence layer directly - /// - [PublicAPI] - public interface ITenantService - { - /// - /// The tenant service can also provide an . Keep in mind that this instance uses a direct - /// database connection. When multiple microservices do not share the same database, this instance cannot be used, but must - /// be implemented by a client to the master tenant service, probably using a remoting technology like RESTful Service, HTTP, - /// gRPC or SOAP web service - /// - ITenantIdProvider TenantIdProvider { get; } - - TenantId CreateTenant(string name, string description, bool isDemonstrationTenant, string configuration = null); - void ActivateTenant(TenantId tenantId); - void DeactivateTenant(TenantId tenantId); - void DeleteTenant(TenantId tenantId); - Tenant UpdateTenant(TenantId tenantId, string name, string description, string configuration); - - Tenant[] GetTenants(); - Tenant[] GetActiveTenants(); - Tenant[] GetActiveDemonstrationTenants(); - Tenant[] GetActiveProductionTenants(); - Tenant GetTenant(TenantId tenantId); - } - - public class TenantService : ITenantService - { - private static readonly ILogger Logger = Log.Create(); - private readonly ITenantRepository _tenantRepository; - - public ITenantIdProvider TenantIdProvider { get; } - - public TenantService(ITenantRepository tenantRepository) - { - _tenantRepository = tenantRepository; - TenantIdProvider = new TenantServiceTenantIdProvider(this); - } - - public TenantId CreateTenant(string name, string description, bool isDemonstrationTenant, string configuration = null) - { - Logger.LogInformation("Creating tenant: {Name}", name); - - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); - } - - if (_tenantRepository.GetTenants().Any(t => t.Name != null && t.Name.ToLowerInvariant() == name.ToLowerInvariant())) - { - throw new ArgumentException($"There is already a tenant named {name}"); - } - - var tenant = new Tenant(name, description, isDemonstrationTenant) { Configuration = configuration }; - _tenantRepository.SaveTenant(tenant); - var tenantId = new TenantId(tenant.Id); - return tenantId; - } - - public void ActivateTenant(TenantId tenantId) - { - Logger.LogInformation("Activating tenant: {TenantId}", tenantId); - Tenant tenant = _tenantRepository.GetTenant(tenantId); - tenant.State = TenantState.Active; - _tenantRepository.SaveTenant(tenant); - } - - public void DeactivateTenant(TenantId tenantId) - { - Logger.LogInformation("Deactivating tenant: {TenantId}", tenantId); - Tenant tenant = _tenantRepository.GetTenant(tenantId); - tenant.State = TenantState.Inactive; - _tenantRepository.SaveTenant(tenant); - } - - public void DeleteTenant(TenantId tenantId) - { - Logger.LogInformation("Deleting tenant: {TenantId}", tenantId); - Tenant tenant = _tenantRepository.GetTenant(tenantId); - if (tenant.State != TenantState.Inactive) - { - throw new UnprocessableException($"Attempt to delete active tenant[{tenantId.Value}]") - .AddError("You cannot delete an active tenant. Please make sure to deactivate it first."); - } - - _tenantRepository.DeleteTenant(tenantId); - } - - public Tenant GetTenant(TenantId tenantId) - { - return _tenantRepository.GetTenant(tenantId); - } - - public Tenant UpdateTenant(TenantId tenantId, string name, string description, string configuration) - { - var tenant = _tenantRepository.GetTenant(tenantId); - tenant.Name = name; - tenant.Description = description; - tenant.Configuration = configuration; - _tenantRepository.SaveTenant(tenant); - return tenant; - } - - public Tenant[] GetTenants() - { - var tenants = _tenantRepository.GetTenants(); - Logger.LogTrace("TenantIds: {TenantIds}", string.Join(",", tenants.Select(t => t.ToString()))); - return tenants; - } - - public Tenant[] GetActiveTenants() - { - var activeTenants = _tenantRepository - .GetTenants() - .Where(t => t.State == TenantState.Active) - .ToArray(); - Logger.LogTrace("Active TenantIds: {TenantIds}", string.Join(",", activeTenants.Select(t => t.ToString()))); - return activeTenants; - } - - public Tenant[] GetActiveDemonstrationTenants() - { - var activeDemonstrationTenants = _tenantRepository - .GetTenants() - .Where(t => t.State == TenantState.Active && t.IsDemoTenant) - .ToArray(); - Logger.LogTrace("Active Demonstration TenantIds: {TenantIds}", - string.Join(",", activeDemonstrationTenants.Select(t => t.ToString()))); - return activeDemonstrationTenants; - } - - public Tenant[] GetActiveProductionTenants() - { - var activeProductionTenants = _tenantRepository - .GetTenants() - .Where(t => t.State == TenantState.Active && !t.IsDemoTenant) - .ToArray(); - Logger.LogTrace("Active Production TenantIds: {TenantIds}", - string.Join(",", activeProductionTenants.Select(t => t.ToString()))); - return activeProductionTenants; - } - - private class TenantServiceTenantIdProvider : ITenantIdProvider - { - private readonly ITenantService _tenantService; - - public TenantServiceTenantIdProvider(ITenantService tenantService) - { - _tenantService = tenantService; - } - - public TenantId[] GetActiveDemonstrationTenantIds() - { - return _tenantService.GetActiveDemonstrationTenants() - .Select(t => new TenantId(t.Id)) - .ToArray(); - } - - public TenantId[] GetActiveProductionTenantIds() - { - return _tenantService.GetActiveProductionTenants() - .Select(t => new TenantId(t.Id)) - .ToArray(); - } - - public TenantId[] GetActiveTenantIds() - { - return _tenantService.GetActiveTenants() - .Select(t => new TenantId(t.Id)) - .ToArray(); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs b/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs index d174ba47..2bc6f195 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ForbiddenException.cs @@ -7,7 +7,7 @@ namespace Backend.Fx.Exceptions public class ForbiddenException : ClientException { public ForbiddenException() - : base("Unauthorized") + : base("Forbidden") { } diff --git a/src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs similarity index 59% rename from src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs index 3ef29a26..29cc5d26 100644 --- a/src/abstractions/Backend.Fx/Environment/Authentication/AnonymousIdentity.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs @@ -1,8 +1,10 @@ using System.Security.Principal; +using JetBrains.Annotations; -namespace Backend.Fx.Environment.Authentication +namespace Backend.Fx.ExecutionPipeline { - public class AnonymousIdentity : IIdentity + [PublicAPI] + public sealed class AnonymousIdentity : IIdentity { public string Name => "ANONYMOUS"; diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs new file mode 100644 index 00000000..10c4c4ee --- /dev/null +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs @@ -0,0 +1,78 @@ +using System; +using System.Security.Principal; +using System.Threading.Tasks; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Logging; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.ExecutionPipeline +{ + public interface IBackendFxApplicationInvoker + { + /// The async action to be invoked by the application + /// The acting identity + Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity); + } + + + internal class BackendFxApplicationInvoker : IBackendFxApplicationInvoker + { + private readonly ICompositionRoot _compositionRoot; + private static readonly ILogger Logger = Log.Create(); + + public BackendFxApplicationInvoker(ICompositionRoot compositionRoot) + { + _compositionRoot = compositionRoot; + } + + + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity) + { + Logger.LogInformation("Invoking asynchronous action as {@Identity}", identity); + using (IServiceScope serviceScope = BeginScope(identity)) + { + using (UseDurationLogger(serviceScope)) + { + var operation = serviceScope.ServiceProvider.GetRequiredService(); + try + { + operation.Begin(serviceScope); + await awaitableAsyncAction.Invoke(serviceScope.ServiceProvider).ConfigureAwait(false); + operation.Complete(); + } + catch + { + operation.Cancel(); + throw; + } + } + } + } + + + private IServiceScope BeginScope(IIdentity identity) + { + IServiceScope serviceScope = _compositionRoot.BeginScope(); + + identity = identity ?? new AnonymousIdentity(); + serviceScope.ServiceProvider.GetRequiredService>().ReplaceCurrent(identity); + + + + return serviceScope; + } + + + private static IDisposable UseDurationLogger(IServiceScope serviceScope) + { + IIdentity identity = serviceScope.ServiceProvider.GetRequiredService>().Current; + Correlation correlation = serviceScope.ServiceProvider.GetRequiredService>().Current; + return Logger.LogInformationDuration( + $"Starting scope (correlation [{correlation.Id}]) for {identity.Name}", + $"Ended scope (correlation [{correlation.Id}]) for {identity.Name}"); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/Correlation.cs similarity index 83% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/Correlation.cs index ec5f6d23..7a913583 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Correlation.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/Correlation.cs @@ -1,15 +1,16 @@ using System; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.ExecutionPipeline { /// /// A guid that is unique for an invocation. In case of an invocation as result of handling an integration event, the correlation /// is stable, that is, the correlation can be used to track a logical action over different systems. /// - public class Correlation + [PublicAPI] + public sealed class Correlation { private static readonly ILogger Logger = Log.Create(); diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs similarity index 55% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs index 70a9f67e..6540ef16 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentCorrelationHolder.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs @@ -1,6 +1,11 @@ -namespace Backend.Fx.Patterns.DependencyInjection +using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Util; +using JetBrains.Annotations; + +namespace Backend.Fx.ExecutionPipeline { - public class CurrentCorrelationHolder : CurrentTHolder + [PublicAPI] + public sealed class CurrentCorrelationHolder : CurrentTHolder { public override Correlation ProvideInstance() { diff --git a/src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs similarity index 84% rename from src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs index 9799769d..48247b00 100644 --- a/src/abstractions/Backend.Fx/Environment/Authentication/CurrentIdentityHolder.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs @@ -1,9 +1,11 @@ using System.Security.Principal; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; +using JetBrains.Annotations; -namespace Backend.Fx.Environment.Authentication +namespace Backend.Fx.ExecutionPipeline { - public class CurrentIdentityHolder : CurrentTHolder + [PublicAPI] + public sealed class CurrentIdentityHolder : CurrentTHolder { public CurrentIdentityHolder() { } diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs similarity index 61% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs index 1e3539f9..3e76dfa4 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAndHandlingInvoker.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs @@ -1,11 +1,11 @@ using System; using System.Security.Principal; -using Backend.Fx.Environment.MultiTenancy; +using System.Threading.Tasks; using Backend.Fx.Logging; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.ExecutionPipeline { - public class ExceptionLoggingAndHandlingInvoker : IBackendFxApplicationInvoker + internal class ExceptionLoggingAndHandlingInvoker : IBackendFxApplicationInvoker { private readonly IExceptionLogger _exceptionLogger; private readonly IBackendFxApplicationInvoker _invoker; @@ -16,11 +16,11 @@ public ExceptionLoggingAndHandlingInvoker(IExceptionLogger exceptionLogger, IBac _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity) { try { - _invoker.Invoke(action, identity, tenantId, correlationId); + await _invoker.InvokeAsync(awaitableAsyncAction, identity).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingInvoker.cs similarity index 62% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingInvoker.cs index fb91a301..94e3c23f 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingInvoker.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingInvoker.cs @@ -1,11 +1,11 @@ using System; using System.Security.Principal; -using Backend.Fx.Environment.MultiTenancy; +using System.Threading.Tasks; using Backend.Fx.Logging; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.ExecutionPipeline { - public class ExceptionLoggingInvoker : IBackendFxApplicationInvoker + internal class ExceptionLoggingInvoker : IBackendFxApplicationInvoker { private readonly IExceptionLogger _exceptionLogger; private readonly IBackendFxApplicationInvoker _invoker; @@ -16,11 +16,11 @@ public ExceptionLoggingInvoker(IExceptionLogger exceptionLogger, IBackendFxAppli _invoker = invoker; } - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity) { try { - _invoker.Invoke(action, identity, tenantId, correlationId); + await _invoker.InvokeAsync(awaitableAsyncAction, identity).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs new file mode 100644 index 00000000..9dee156b --- /dev/null +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs @@ -0,0 +1,40 @@ +using System.Security.Principal; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; +using NodaTime; + +namespace Backend.Fx.ExecutionPipeline +{ + internal class ExecutionPipelineModule : IModule + { + private readonly bool _withFrozenClockDuringExecution; + + public ExecutionPipelineModule(bool withFrozenClockDuringExecution = true) + { + _withFrozenClockDuringExecution = withFrozenClockDuringExecution; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.Register( + ServiceDescriptor.Singleton(_ => SystemClock.Instance)); + + if (_withFrozenClockDuringExecution) + { + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); + } + + compositionRoot.Register( + ServiceDescriptor.Scoped()); + + compositionRoot.Register( + ServiceDescriptor.Scoped, CurrentIdentityHolder>()); + + compositionRoot.Register( + ServiceDescriptor.Scoped, CurrentCorrelationHolder>()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs new file mode 100644 index 00000000..01af6841 --- /dev/null +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs @@ -0,0 +1,27 @@ +using Backend.Fx.Logging; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Backend.Fx.ExecutionPipeline +{ + /// + /// Best practice for web (service) applications: time does not advance during an invocation + /// + internal class FrozenClock : IClock + { + private static readonly ILogger Logger = Log.Create(); + private readonly Instant _frozenInstant; + + public FrozenClock(IClock clock) + { + _frozenInstant = clock.GetCurrentInstant(); + Logger.LogTrace("Freezing clock at {Instant}", _frozenInstant); + } + + + public Instant GetCurrentInstant() + { + return _frozenInstant; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs similarity index 89% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs index 62883929..781c7802 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/Operation.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs @@ -1,10 +1,10 @@ using System; using Backend.Fx.Logging; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.ExecutionPipeline { /// /// The basic interface of an operation invoked by the (or its async counterpart). @@ -14,14 +14,14 @@ namespace Backend.Fx.Patterns.DependencyInjection [PublicAPI] public interface IOperation { - void Begin(); + void Begin(IServiceScope serviceScope); void Complete(); void Cancel(); } - public class Operation : IOperation + internal sealed class Operation : IOperation { private static readonly ILogger Logger = Log.Create(); private static int _index; @@ -29,7 +29,7 @@ public class Operation : IOperation private bool? _isActive; private IDisposable _lifetimeLogger; - public void Begin() + public void Begin(IServiceScope serviceScope) { if (_isActive != null) { diff --git a/src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs similarity index 59% rename from src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs rename to src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs index cb388295..80c8f598 100644 --- a/src/abstractions/Backend.Fx/Environment/Authentication/SystemIdentity.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs @@ -1,8 +1,10 @@ using System.Security.Principal; +using JetBrains.Annotations; -namespace Backend.Fx.Environment.Authentication +namespace Backend.Fx.ExecutionPipeline { - public class SystemIdentity : IIdentity + [PublicAPI] + public sealed class SystemIdentity : IIdentity { public string Name => "SYSTEM"; diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs b/src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs similarity index 82% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs rename to src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs index 09088fa9..763a663e 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationDecorator.cs +++ b/src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs @@ -2,19 +2,20 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Extensions; +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; +using Backend.Fx.Util; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.Extensions { - public abstract class BackendFxApplicationDecorator : IBackendFxApplication + public abstract class BackendFxApplicationExtension : IBackendFxApplication { - private static readonly ILogger Logger = Log.Create(); + private static readonly ILogger Logger = Log.Create(); private readonly IBackendFxApplication _application; - protected BackendFxApplicationDecorator(IBackendFxApplication application) + protected BackendFxApplicationExtension(IBackendFxApplication application) { Logger.LogInformation("Decorating the application with {Decorator}", GetType().GetDetailedTypeName()); _application = application; @@ -22,8 +23,6 @@ protected BackendFxApplicationDecorator(IBackendFxApplication application) public Assembly[] Assemblies => _application.Assemblies; - public virtual IBackendFxApplicationAsyncInvoker AsyncInvoker => _application.AsyncInvoker; - public virtual ICompositionRoot CompositionRoot => _application.CompositionRoot; public virtual IExceptionLogger ExceptionLogger => _application.ExceptionLogger; @@ -42,7 +41,7 @@ public virtual Task BootAsync(CancellationToken cancellationToken = default) return _application.BootAsync(cancellationToken); } - public TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationDecorator + public TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationExtension { if (this is TBackendFxApplicationDecorator matchingDecorator) { diff --git a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs new file mode 100644 index 00000000..d06fb8fb --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Extensions.DataGeneration +{ + /// + /// The feature "Data Generation" makes sure that all implemented data generators are executed on application boot + /// + [PublicAPI] + public class DataGenerationApplication : BackendFxApplicationExtension + { + private readonly bool _runDemoDataGenerators; + + public DataGenerationApplication(IBackendFxApplication application, bool runDemoDataGenerators) : base( + application) + { + _runDemoDataGenerators = runDemoDataGenerators; + application.CompositionRoot.RegisterModules(new DataGenerationModule(application.Assemblies)); + } + + public override async Task BootAsync(CancellationToken cancellationToken = default) + { + await base.BootAsync(cancellationToken).ConfigureAwait(false); + await RunDataGenerators(cancellationToken).ConfigureAwait(false); + } + + private async Task RunDataGenerators(CancellationToken cancellationToken) + { + var dataGeneratorTypes = Type.EmptyTypes; + await Invoker.InvokeAsync(sp => + { + dataGeneratorTypes = sp + .GetServices() + .OrderBy(dg => dg.Priority) + .Select(dg => dg.GetType()) + .ToArray(); + return Task.CompletedTask; + }, new SystemIdentity()).ConfigureAwait(false); + + foreach (var dataGeneratorType in dataGeneratorTypes) + { + if (typeof(IProductiveDataGenerator).IsAssignableFrom(dataGeneratorType) + || typeof(IDemoDataGenerator).IsAssignableFrom(dataGeneratorType) && _runDemoDataGenerators) + { + await Invoker.InvokeAsync(async sp => + { + var dataGenerator = (IDataGenerator)sp.GetRequiredService(dataGeneratorType); + await dataGenerator.GenerateAsync().ConfigureAwait(false); + }, new SystemIdentity()).ConfigureAwait(false); + } + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs similarity index 84% rename from src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs rename to src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs index f9c44b0e..2f35613b 100644 --- a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs +++ b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs @@ -1,10 +1,10 @@ using System.Linq; using System.Reflection; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; -namespace Backend.Fx.Features.DataGeneration +namespace Backend.Fx.Extensions.DataGeneration { public class DataGenerationModule : IModule { diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerator.cs similarity index 83% rename from src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs rename to src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerator.cs index 75efa4ce..d5dbb4fe 100644 --- a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs +++ b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerator.cs @@ -1,7 +1,9 @@ -using Backend.Fx.Logging; +using System.Threading.Tasks; +using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Features.DataGeneration +namespace Backend.Fx.Extensions.DataGeneration { public interface IDataGenerator { @@ -10,7 +12,7 @@ public interface IDataGenerator /// int Priority { get; } - void Generate(); + Task GenerateAsync(); } /// @@ -20,22 +22,23 @@ public interface IDataGenerator /// Any implementation is automatically picked up by the injection container, so no extra plumbing is required. /// You can require any application or domain service including repositories via constructor parameter. /// + [PublicAPI] public abstract class DataGenerator : IDataGenerator { - private static readonly Microsoft.Extensions.Logging.ILogger Logger = Log.Create(); + private static readonly ILogger Logger = Log.Create(); /// /// simple way of ordering the execution of DataGenerators. Priority 0 will be executed first. /// public abstract int Priority { get; } - public void Generate() + public async Task GenerateAsync() { if (ShouldRun()) { Initialize(); Logger.LogInformation("{DataGeneratorTypeName} is now generating initial data", GetType().FullName); - GenerateCore(); + await GenerateCoreAsync(); } else { @@ -46,7 +49,7 @@ public void Generate() /// /// Implement your generate Logic here /// - protected abstract void GenerateCore(); + protected abstract Task GenerateCoreAsync(); /// /// Implement your initial logic here (e.g. loading from external source) diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/IDemoDataGenerator.cs similarity index 79% rename from src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs rename to src/abstractions/Backend.Fx/Extensions/DataGeneration/IDemoDataGenerator.cs index 63f28de0..6ba2209f 100644 --- a/src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Extensions/DataGeneration/IDemoDataGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Features.DataGeneration +namespace Backend.Fx.Extensions.DataGeneration { /// /// Marks an as active in development environments only diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/IProductiveDataGenerator.cs similarity index 79% rename from src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs rename to src/abstractions/Backend.Fx/Extensions/DataGeneration/IProductiveDataGenerator.cs index 255fd759..ac43e473 100644 --- a/src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Extensions/DataGeneration/IProductiveDataGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Features.DataGeneration +namespace Backend.Fx.Extensions.DataGeneration { /// /// Marks an as active in all environments diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs new file mode 100644 index 00000000..1395b350 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Backend.Fx.Extensions.MessageBus +{ + internal interface IIntegrationEventHandler + { + Task HandleAsync(T integrationEvent); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs new file mode 100644 index 00000000..52a185b6 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Backend.Fx.Extensions.MessageBus.InProc +{ + public class InProcMessageBus : MessageBus + { + private readonly InProcMessageBusChannel _channel; + private readonly HashSet _subscribedEventTypeNames = new HashSet(); + + public InProcMessageBus() + { + _channel = new InProcMessageBusChannel(); + } + + public override void Connect() + { + _channel.MessageReceived += ChannelOnMessageReceived; + } + + protected override void SubscribeToEventMessage(string eventTypeName) + { + _subscribedEventTypeNames.Add(eventTypeName); + } + + protected override Task PublishMessageAsync(SerializedMessage serializedMessage) + { + _channel.Publish(serializedMessage); + return Task.CompletedTask; + } + + private async void ChannelOnMessageReceived( + object sender, + InProcMessageBusChannel.MessageReceivedEventArgs eventArgs) + { + if (_subscribedEventTypeNames.Contains(eventArgs.SerializedMessage.EventTypeName)) + { + await ProcessAsync(eventArgs.SerializedMessage).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBusChannel.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBusChannel.cs similarity index 69% rename from src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBusChannel.cs rename to src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBusChannel.cs index 60084eb5..00258ebc 100644 --- a/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBusChannel.cs +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBusChannel.cs @@ -2,17 +2,17 @@ using System.Collections.Concurrent; using System.Threading.Tasks; -namespace Backend.Fx.Features.MessageBus +namespace Backend.Fx.Extensions.MessageBus.InProc { - public class InMemoryMessageBusChannel + public class InProcMessageBusChannel { private readonly ConcurrentBag _messageHandlingTasks = new ConcurrentBag(); internal event EventHandler MessageReceived; - internal void Publish(IIntegrationEvent integrationEvent) + internal void Publish(SerializedMessage serializedMessage) { - var eventArgs = new MessageReceivedEventArgs { IntegrationEvent = integrationEvent }; + var eventArgs = new MessageReceivedEventArgs { SerializedMessage = serializedMessage }; _messageHandlingTasks.Add(Task.Run(() => MessageReceived?.Invoke(this, eventArgs))); } @@ -26,7 +26,7 @@ public async Task FinishHandlingAllMessagesAsync() internal class MessageReceivedEventArgs { - public IIntegrationEvent IntegrationEvent { get; set; } + public SerializedMessage SerializedMessage { get; set; } } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs new file mode 100644 index 00000000..a12c5b86 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using NodaTime; + +namespace Backend.Fx.Extensions.MessageBus +{ + [PublicAPI] + public interface IIntegrationEvent + { + Guid Id { get; } + Instant CreationDate { get; } + Guid CorrelationId { get; } + + Dictionary Properties { get; } + } + + /// + /// Events that should be handled in a separate context. Might be persisted as well using an external message bus. + /// See https://blogs.msdn.microsoft.com/cesardelatorre/2017/02/07/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/ + /// + public abstract class IntegrationEvent : IIntegrationEvent + { + public Guid Id { get; } = Guid.NewGuid(); + + + public Instant CreationDate { get; } = SystemClock.Instance.GetCurrentInstant(); + + public Guid CorrelationId { get; internal set; } + + public Dictionary Properties { get; } = new Dictionary(); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs new file mode 100644 index 00000000..6fb40984 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs @@ -0,0 +1,45 @@ +using System; +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Logging; + +namespace Backend.Fx.Extensions.MessageBus +{ + /// + /// Ensures events to be handled sequentially and catches all exceptions. + /// + public class IntegrationEventHandlingInvoker : IBackendFxApplicationInvoker + { + private readonly object _syncLock = new object(); + private readonly IExceptionLogger _exceptionLogger; + private readonly IBackendFxApplicationInvoker _invoker; + + public IntegrationEventHandlingInvoker(IExceptionLogger exceptionLogger, IBackendFxApplicationInvoker invoker) + { + _exceptionLogger = exceptionLogger; + _invoker = invoker; + } + + + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity) + { + Monitor.Enter(_syncLock); + try + { + await _invoker.InvokeAsync( + awaitableAsyncAction, + identity).ConfigureAwait(false); + } + catch (Exception ex) + { + _exceptionLogger.LogException(ex); + } + finally + { + Monitor.Exit(_syncLock); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs new file mode 100644 index 00000000..6adb6edc --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Backend.Fx.Extensions.MessageBus +{ + public interface IIntegrationEventSerializer + { + Task SerializeAsync(T integrationEvent) where T : IIntegrationEvent; + Task DeserializeAsync(SerializedMessage serializedMessage); + } + + public class IntegrationEventSerializer : IIntegrationEventSerializer + { + public async Task SerializeAsync(T integrationEvent) + where T : IIntegrationEvent + { + using var memoryStream = new MemoryStream(4096); + await JsonSerializer.SerializeAsync(memoryStream, integrationEvent).ConfigureAwait(false); + memoryStream.Seek(0, SeekOrigin.Begin); + return new SerializedMessage(typeof(T).Name, memoryStream.ToArray()); + } + + public async Task DeserializeAsync(SerializedMessage serializedMessage) + { + using var memoryStream = new MemoryStream(serializedMessage.MessagePayload, false); + var integrationEvent = (IIntegrationEvent)await JsonSerializer + .DeserializeAsync( + memoryStream, + Type.GetType(serializedMessage.EventTypeName)) + .ConfigureAwait(false); + + return integrationEvent; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs new file mode 100644 index 00000000..2192c702 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs @@ -0,0 +1,77 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Logging; +using Backend.Fx.Util; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Extensions.MessageBus +{ + [PublicAPI] + public abstract class MessageBus + { + private static readonly ILogger Logger = Log.Create(); + + public IBackendFxApplicationInvoker Invoker { get; internal set; } + + public ICompositionRoot CompositionRoot { get; internal set; } + + public abstract void Connect(); + + public async Task PublishAsync(IIntegrationEvent integrationEvent) + { + var serializer = CompositionRoot.ServiceProvider.GetRequiredService(); + SerializedMessage serializedMessage = await serializer.SerializeAsync(integrationEvent).ConfigureAwait(false); + await PublishMessageAsync(serializedMessage).ConfigureAwait(false); + } + + public void Subscribe() where TEvent : IIntegrationEvent + { + SubscribeToEventMessage(typeof(TEvent).Name); + } + + public void Subscribe(Type eventType) + { + SubscribeToEventMessage(eventType.Name); + } + + protected async Task ProcessAsync(SerializedMessage serializedMessage) + { + Logger.LogInformation("Processing a {EventTypeName} message", serializedMessage.EventTypeName); + + await Invoker.InvokeAsync(async sp => + { + var serializer = sp.GetRequiredService(); + var integrationEvent = await serializer.DeserializeAsync(serializedMessage).ConfigureAwait(false); + var handlerType = typeof(IIntegrationEventHandler<>).MakeGenericType(integrationEvent.GetType()); + var handler = sp.GetRequiredService(handlerType); + const string methodName = nameof(IIntegrationEventHandler.HandleAsync); + var handleAsyncMethod = handlerType.GetMethod(methodName, new[] { integrationEvent.GetType() }); + Debug.Assert(handleAsyncMethod != null, nameof(handleAsyncMethod) + " != null"); + sp.GetRequiredService>().Current.Resume(integrationEvent.CorrelationId); + await ((Task)handleAsyncMethod + .Invoke(handler, new object[] { integrationEvent })) + .ConfigureAwait(false); + }, new SystemIdentity()).ConfigureAwait(false); + } + + + + protected abstract void SubscribeToEventMessage(string eventTypeName); + + protected abstract Task PublishMessageAsync(SerializedMessage serializedMessage); + + protected virtual void Dispose(bool disposing) + { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs new file mode 100644 index 00000000..4375499e --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs @@ -0,0 +1,41 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Backend.Fx.Extensions.MessageBus +{ + /// + /// The extension "Message Bus" adds integration message sending and handling of received integration messages to the + /// application. If the feature "Multi Tenancy" has been activated, this feature takes care of adding a tenant id + /// to all outgoing messages and handling incoming messages in the respective tenant. + /// + public class MessageBusExtension : BackendFxApplicationExtension + { + private readonly MessageBus _messageBus; + private readonly MessageBusModule _messageBusModule; + + public MessageBusExtension(MessageBus messageBus, IBackendFxApplication application) + : base(application) + { + _messageBusModule = new MessageBusModule(messageBus, application); + application.CompositionRoot.RegisterModules(_messageBusModule); + _messageBus = messageBus; + + } + + public override async Task BootAsync(CancellationToken cancellationToken = default) + { + await base.BootAsync(cancellationToken).ConfigureAwait(false); + _messageBus.Connect(); + _messageBusModule.SubscribeToAllEvents(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _messageBus.Dispose(); + } + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs new file mode 100644 index 00000000..fdd0aedc --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Logging; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Extensions.MessageBus +{ + internal class MessageBusModule : IModule + { + private static readonly ILogger Logger = Log.Create(); + private readonly MessageBus _messageBus; + private readonly IBackendFxApplication _application; + private readonly List _eventTypesToSubscribe = new List(); + + public MessageBusModule(MessageBus messageBus, IBackendFxApplication application) + { + _messageBus = messageBus; + _application = application; + _messageBus.Invoker = new IntegrationEventHandlingInvoker(application.ExceptionLogger, application.Invoker); + _messageBus.CompositionRoot = application.CompositionRoot; + + } + + public void Register(ICompositionRoot compositionRoot) + { + // note tht there should be no reason to access the singleton message bus instance from the service provider + + // register the message bus scope + compositionRoot.Register( + ServiceDescriptor.Scoped( + sp => new MessageBusScope( + _messageBus, + sp.GetRequiredService>()))); + + compositionRoot.Register(ServiceDescriptor.Scoped()); + + // make sure all integration events are raised after completing an operation, but before ending the scope + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); + + compositionRoot.Register( + ServiceDescriptor.Scoped, CurrentCorrelationHolder>()); + + RegisterIntegrationEventHandlers(compositionRoot); + + // support multi tenancy, when available + if ((_application as BackendFxApplication)?.IsMultiTenancyApplication == true) + { + // enrich the integration event with a TenantId property + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); + } + } + + public void SubscribeToAllEvents() + { + foreach (var eventType in _eventTypesToSubscribe) + { + _messageBus.Subscribe(eventType); + } + } + + private void RegisterIntegrationEventHandlers(ICompositionRoot compositionRoot) + { + foreach (var integrationEventType in _application.Assemblies.GetImplementingTypes(typeof(IIntegrationEvent))) + { + var handlerTypeForThisIntegrationEventType = + typeof(IIntegrationEventHandler<>).MakeGenericType(integrationEventType); + + var serviceDescriptors = _application.Assemblies + .GetImplementingTypes(handlerTypeForThisIntegrationEventType) + .Select(t => + new ServiceDescriptor(handlerTypeForThisIntegrationEventType, t, ServiceLifetime.Scoped)) + .ToArray(); + + if (serviceDescriptors.Any()) + { + compositionRoot.RegisterCollection(serviceDescriptors); + } + else + { + Logger.LogInformation("No handlers for {IntegrationEventType} found", integrationEventType); + } + + _eventTypesToSubscribe.Add(integrationEventType); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusScope.cs similarity index 55% rename from src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs rename to src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusScope.cs index 24e40bf9..15435c92 100644 --- a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusScope.cs @@ -1,9 +1,9 @@ using System.Collections.Concurrent; using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; -namespace Backend.Fx.Features.MessageBus +namespace Backend.Fx.Extensions.MessageBus { public interface IMessageBusScope { @@ -14,38 +14,34 @@ public interface IMessageBusScope /// void Publish(IIntegrationEvent integrationEvent); - Task RaiseEvents(); + Task RaiseEventsAsync(); } - public class MessageBusScope : IMessageBusScope + internal class MessageBusScope : IMessageBusScope { private readonly ConcurrentQueue _integrationEvents = new ConcurrentQueue(); - private readonly IMessageBus _messageBus; + private readonly MessageBus _messageBus; private readonly ICurrentTHolder _correlationHolder; - private readonly ICurrentTHolder _tenantIdHolder; - + public MessageBusScope( - IMessageBus messageBus, - ICurrentTHolder correlationHolder, - ICurrentTHolder tenantIdHolder) + MessageBus messageBus, + ICurrentTHolder correlationHolder) { _messageBus = messageBus; _correlationHolder = correlationHolder; - _tenantIdHolder = tenantIdHolder; } void IMessageBusScope.Publish(IIntegrationEvent integrationEvent) { - ((IntegrationEvent) integrationEvent).SetCorrelationId(_correlationHolder.Current.Id); - ((IntegrationEvent) integrationEvent).SetTenantId(_tenantIdHolder.Current); + ((IntegrationEvent) integrationEvent).CorrelationId = _correlationHolder.Current.Id; _integrationEvents.Enqueue(integrationEvent); } - public async Task RaiseEvents() + public async Task RaiseEventsAsync() { while (_integrationEvents.TryDequeue(out IIntegrationEvent integrationEvent)) { - await _messageBus.Publish(integrationEvent).ConfigureAwait(false); + await _messageBus.PublishAsync(integrationEvent).ConfigureAwait(false); } } } diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs new file mode 100644 index 00000000..d7409645 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs @@ -0,0 +1,48 @@ +using System.Globalization; +using System.Threading.Tasks; +using Backend.Fx.Exceptions; +using Backend.Fx.Features.MultiTenancy; +using Backend.Fx.Util; + +namespace Backend.Fx.Extensions.MessageBus +{ + public class MultiTenantIntegrationEventSerializer : IIntegrationEventSerializer + { + private const string TenantIdPropertyKey = nameof(TenantId); + private readonly ICurrentTHolder _tenantIdHolder; + private readonly IIntegrationEventSerializer _serializer; + + public MultiTenantIntegrationEventSerializer(IIntegrationEventSerializer serializer, ICurrentTHolder tenantIdHolder) + { + _serializer = serializer; + _tenantIdHolder = tenantIdHolder; + } + + public Task SerializeAsync(T integrationEvent) where T : IIntegrationEvent + { + integrationEvent.Properties[TenantIdPropertyKey] = + _tenantIdHolder.Current.Value.ToString(CultureInfo.InvariantCulture); + return _serializer.SerializeAsync(integrationEvent); + } + + public async Task DeserializeAsync(SerializedMessage serializedMessage) + { + var integrationEvent = await _serializer.DeserializeAsync(serializedMessage).ConfigureAwait(false); + + if (!integrationEvent.Properties.TryGetValue(TenantIdPropertyKey, out var tenantIdString)) + { + throw new UnprocessableException("Received an integration event message without TenantId property"); + } + + if (!int.TryParse(tenantIdString, out int tenantId)) + { + throw new UnprocessableException( + $"Received an integration event message with an invalid TenantId property value: {tenantIdString}"); + } + + _tenantIdHolder.ReplaceCurrent(new TenantId(tenantId)); + + return integrationEvent; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs similarity index 60% rename from src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs rename to src/abstractions/Backend.Fx/Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs index 03ac2799..dd992fbe 100644 --- a/src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs @@ -1,16 +1,17 @@ -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; -namespace Backend.Fx.Features.MessageBus +namespace Backend.Fx.Extensions.MessageBus { [UsedImplicitly] - public class RaiseIntegrationEventsOperationDecorator : IOperation + internal class RaiseIntegrationEventsWhenOperationCompleted : IOperation { private readonly IMessageBusScope _messageBusScope; private readonly IOperation _operation; - public RaiseIntegrationEventsOperationDecorator( + public RaiseIntegrationEventsWhenOperationCompleted( IMessageBusScope messageBusScope, IOperation operation) { @@ -18,15 +19,15 @@ public RaiseIntegrationEventsOperationDecorator( _operation = operation; } - public void Begin() + public void Begin(IServiceScope serviceScope) { - _operation.Begin(); + _operation.Begin(serviceScope); } public void Complete() { _operation.Complete(); - AsyncHelper.RunSync(() => _messageBusScope.RaiseEvents()); + AsyncHelper.RunSync(() => _messageBusScope.RaiseEventsAsync()); } public void Cancel() diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs b/src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs new file mode 100644 index 00000000..2dbae042 --- /dev/null +++ b/src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs @@ -0,0 +1,14 @@ +namespace Backend.Fx.Extensions.MessageBus +{ + public struct SerializedMessage + { + public SerializedMessage(string eventTypeName, byte[] messagePayload) + { + EventTypeName = eventTypeName; + MessagePayload = messagePayload; + } + + public string EventTypeName { get; } + public byte[] MessagePayload { get; } + } +} diff --git a/src/abstractions/Backend.Fx/Features/Persistence/DbConnectionOperationDecorator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/DbConnectionOperationDecorator.cs similarity index 87% rename from src/abstractions/Backend.Fx/Features/Persistence/DbConnectionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/DbConnectionOperationDecorator.cs index daa1f31e..53536708 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/DbConnectionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/DbConnectionOperationDecorator.cs @@ -1,11 +1,11 @@ using System; using System.Data; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public class DbConnectionOperationDecorator : IOperation { @@ -21,12 +21,12 @@ public DbConnectionOperationDecorator(IDbConnection dbConnection, IOperation ope public IDbConnection DbConnection { get; } - public void Begin() + public void Begin(IServiceScope serviceScope) { Logger.LogDebug("Opening database connection"); DbConnection.Open(); _connectionLifetimeLogger = Logger.LogDebugDuration("Database connection open", "Database connection closed"); - Operation.Begin(); + Operation.Begin(serviceScope); } public void Complete() diff --git a/src/abstractions/Backend.Fx/Features/Persistence/DbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/DbTransactionOperationDecorator.cs similarity index 94% rename from src/abstractions/Backend.Fx/Features/Persistence/DbTransactionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/DbTransactionOperationDecorator.cs index 528db2b9..fcf32ee1 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/DbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/DbTransactionOperationDecorator.cs @@ -1,11 +1,11 @@ using System; using System.Data; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { /// /// Enriches the operation to use a database transaction during lifetime. The transaction gets started, before IOperation.Begin() @@ -28,7 +28,7 @@ public DbTransactionOperationDecorator(IDbConnection dbConnection, IOperation op } - public virtual void Begin() + public virtual void Begin(IServiceScope serviceScope) { if (_state != TxState.NotStarted) { @@ -46,7 +46,7 @@ public virtual void Begin() CurrentTransaction = _dbConnection.BeginTransaction(_isolationLevel); _transactionLifetimeLogger = Logger.LogDebugDuration("Transaction open", "Transaction terminated"); _state = TxState.Active; - _operation.Begin(); + _operation.Begin(serviceScope); } public IDbTransaction CurrentTransaction { get; private set; } diff --git a/src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/FlushDomainEventAggregatorDecorator.cs similarity index 92% rename from src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/FlushDomainEventAggregatorDecorator.cs index 9207935d..1f4484fd 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/FlushDomainEventAggregatorDecorator.cs @@ -1,9 +1,8 @@ using Backend.Fx.Features.DomainEvents; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public class FlushDomainEventAggregatorDecorator : IDomainEventAggregator { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/FlushOperationDecorator.cs similarity index 78% rename from src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/FlushOperationDecorator.cs index 2e96e8b6..6740b52d 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/FlushOperationDecorator.cs @@ -1,9 +1,9 @@ +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public class FlushOperationDecorator : IOperation { @@ -17,9 +17,9 @@ public FlushOperationDecorator(ICanFlush canFlush, IOperation operationImplement _canFlush = canFlush; } - public void Begin() + public void Begin(IServiceScope serviceScope) { - _operationImplementation.Begin(); + _operationImplementation.Begin(serviceScope); } public void Complete() diff --git a/src/abstractions/Backend.Fx/Features/Persistence/HiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/HiLoIdGenerator.cs similarity index 93% rename from src/abstractions/Backend.Fx/Features/Persistence/HiLoIdGenerator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/HiLoIdGenerator.cs index 438f3312..d098f26a 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/HiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/HiLoIdGenerator.cs @@ -1,9 +1,9 @@ using System.Threading; +using Backend.Fx.Domain; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public abstract class HiLoIdGenerator : IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs similarity index 59% rename from src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs index 8e13936f..7cdc2027 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public interface ICanFlush { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseAvailabilityAwaiter.cs similarity index 80% rename from src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseAvailabilityAwaiter.cs index 40e9b2ce..ae3680f1 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseAvailabilityAwaiter.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public interface IDatabaseAvailabilityAwaiter { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseBootstrapper.cs similarity index 87% rename from src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseBootstrapper.cs index 62b6172e..ddf825b5 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseBootstrapper.cs @@ -1,6 +1,6 @@ using System; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { /// /// Encapsulates database bootstrapping. This interface hides the implementation details for creating/migrating the database diff --git a/src/abstractions/Backend.Fx/Features/Persistence/ISequence.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/ISequence.cs similarity index 80% rename from src/abstractions/Backend.Fx/Features/Persistence/ISequence.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/ISequence.cs index fedbd1c4..c612abb5 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/ISequence.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/ISequence.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { [PublicAPI] public interface ISequence diff --git a/src/abstractions/Backend.Fx/Features/Persistence/PersistentApplication.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs similarity index 50% rename from src/abstractions/Backend.Fx/Features/Persistence/PersistentApplication.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs index a527e4f9..a7de7aa4 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/PersistentApplication.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs @@ -1,24 +1,39 @@ using System.Threading; using System.Threading.Tasks; +using Backend.Fx.DependencyInjection; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { - public class PersistentApplication : BackendFxApplicationDecorator + [PublicAPI] + public class PersistentApplication : BackendFxApplicationExtension { private static readonly ILogger Logger = Log.Create(); private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; private readonly IDatabaseBootstrapper _databaseBootstrapper; + public PersistentApplication(IModule persistenceModule, IBackendFxApplication application) + : this(null, null, persistenceModule, application) + { + } + + public PersistentApplication( + IDatabaseBootstrapper databaseBootstrapper, + IModule persistenceModule, + IBackendFxApplication application) + : this(databaseBootstrapper, null, persistenceModule, application) + { + } + public PersistentApplication( IDatabaseBootstrapper databaseBootstrapper, IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, IModule persistenceModule, - IBackendFxApplication application) : base(application) + IBackendFxApplication application) + : base(application) { _databaseBootstrapper = databaseBootstrapper; _databaseAvailabilityAwaiter = databaseAvailabilityAwaiter; @@ -29,8 +44,12 @@ public PersistentApplication( public override async Task BootAsync(CancellationToken cancellationToken = default) { Logger.LogTrace("Booting..."); - await _databaseAvailabilityAwaiter.WaitForDatabase(cancellationToken).ConfigureAwait(false); - _databaseBootstrapper.EnsureDatabaseExistence(); + if (_databaseAvailabilityAwaiter != null) + { + await _databaseAvailabilityAwaiter.WaitForDatabase(cancellationToken).ConfigureAwait(false); + } + + _databaseBootstrapper?.EnsureDatabaseExistence(); await base.BootAsync(cancellationToken).ConfigureAwait(false); } } diff --git a/src/abstractions/Backend.Fx/Features/Persistence/SequenceHiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceHiLoIdGenerator.cs similarity index 89% rename from src/abstractions/Backend.Fx/Features/Persistence/SequenceHiLoIdGenerator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/SequenceHiLoIdGenerator.cs index f01a711e..3ce90dde 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/SequenceHiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceHiLoIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Features.Persistence +namespace Backend.Fx.Extensions.Persistence { public class SequenceHiLoIdGenerator : HiLoIdGenerator { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/SequenceIdGenerator.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceIdGenerator.cs similarity index 81% rename from src/abstractions/Backend.Fx/Features/Persistence/SequenceIdGenerator.cs rename to src/abstractions/Backend.Fx/Extensions/Persistence/SequenceIdGenerator.cs index e371dcfa..c861a4ef 100644 --- a/src/abstractions/Backend.Fx/Features/Persistence/SequenceIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceIdGenerator.cs @@ -1,4 +1,6 @@ -namespace Backend.Fx.Features.Persistence +using Backend.Fx.Domain; + +namespace Backend.Fx.Extensions.Persistence { public class SequenceIdGenerator : IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs b/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs index 43d1c99b..87113553 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs @@ -1,10 +1,10 @@ using System; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; +using Backend.Fx.Domain; namespace Backend.Fx.Features.Authorization { - public class AllowAll : AggregateAuthorization where TAggregateRoot : AggregateRoot + public class AllowAll : AuthorizationPolicy where TAggregateRoot : AggregateRoot { public override Expression> HasAccessExpression { diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs new file mode 100644 index 00000000..ba54b606 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using Backend.Fx.Domain; +using Backend.Fx.Exceptions; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Authorization +{ + public static class AuthorizationFeature + { + /// + /// The feature "Authorization" obligates you the implementation of an + /// for every . Instances of these policy classes are applied to the repositories, so + /// that on every read or write operation on it, the policy is automatically enforced. Denied reads won't fail but + /// just appear invisible, while a denied write throws a . + /// While implementing policies, you can start by deriving from or + /// . + /// + /// + [PublicAPI] + public static void EnableAuthorization(this IBackendFxApplication application) + => application.CompositionRoot.RegisterModules(new AuthorizationModule(application.Assemblies)); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingApplication.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs similarity index 72% rename from src/abstractions/Backend.Fx/Features/Authorization/AuthorizingApplication.cs rename to src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs index 18807d74..beb30bca 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingApplication.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs @@ -1,25 +1,16 @@ using System; using System.Linq; using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Extensions; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Domain; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Features.Authorization { - public class AuthorizingApplication : BackendFxApplicationDecorator - { - public AuthorizingApplication(IBackendFxApplication application) : base(application) - { - application.CompositionRoot.RegisterModules(new AuthorizationModule(Assemblies)); - } - } - - public class AuthorizationModule : IModule + internal class AuthorizationModule : IModule { private static readonly ILogger Logger = Log.Create(); private readonly Assembly[] _assemblies; @@ -32,6 +23,19 @@ public AuthorizationModule(Assembly[] assemblies) } public void Register(ICompositionRoot compositionRoot) + { + RegisterAuthorizingDecorators(compositionRoot); + RegisterAuthorizationPolicies(compositionRoot); + } + + private static void RegisterAuthorizingDecorators(ICompositionRoot compositionRoot) + { + Logger.LogDebug("Registering authorization decorators"); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped(typeof(IAsyncAggregateQueryable<>), typeof(AuthorizingQueryable<>))); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped(typeof(IRepository<>), typeof(AuthorizingRepository<>))); + } + + private void RegisterAuthorizationPolicies(ICompositionRoot compositionRoot) { Logger.LogDebug("Registering authorization services from {Assemblies}", _assembliesForLogging); @@ -39,7 +43,7 @@ public void Register(ICompositionRoot compositionRoot) foreach (var aggregateRootType in aggregateRootTypes) { var aggregateAuthorizationTypes = _assemblies - .GetImplementingTypes(typeof(IAggregateAuthorization<>).MakeGenericType(aggregateRootType)) + .GetImplementingTypes(typeof(IAuthorizationPolicy<>).MakeGenericType(aggregateRootType)) .ToArray(); foreach (Type aggregateAuthorizationType in aggregateAuthorizationTypes) diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AggregateAuthorization.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs similarity index 67% rename from src/abstractions/Backend.Fx/Features/Authorization/AggregateAuthorization.cs rename to src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs index c156e053..68d3ed8c 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs @@ -1,33 +1,25 @@ using System; -using System.Linq; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; +using Backend.Fx.Domain; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Features.Authorization { - public abstract class AggregateAuthorization : IAggregateAuthorization where TAggregateRoot : AggregateRoot + public abstract class AuthorizationPolicy : IAuthorizationPolicy where TAggregateRoot : AggregateRoot { - private static readonly ILogger Logger = Log.Create>(); + private static readonly ILogger Logger = Log.Create>(); /// > public abstract Expression> HasAccessExpression { get; } - /// > - public virtual IQueryable Filter(IQueryable queryable) - { - return queryable.Where(HasAccessExpression); - } - /// > public abstract bool CanCreate(TAggregateRoot t); /// /// Implement a guard that might disallow modifying an existing aggregate. /// This overload is called directly before saving modification of an instance, so that you can use the instance's state for deciding. - /// This default implementation forwards to + /// This default implementation forwards to /// public virtual bool CanModify(TAggregateRoot t) { diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs new file mode 100644 index 00000000..87003189 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using Backend.Fx.Domain; + +namespace Backend.Fx.Features.Authorization +{ + /// + /// Applies the authorization policy expression to the queryable via decoration + /// + /// + internal class AuthorizingQueryable : IAsyncAggregateQueryable where TAggregateRoot : AggregateRoot + { + private readonly AuthorizationPolicy _authorizationPolicy; + private readonly IAsyncAggregateQueryable _aggregateQueryable; + + public AuthorizingQueryable(AuthorizationPolicy authorizationPolicy, IAsyncAggregateQueryable aggregateQueryable) + { + _authorizationPolicy = authorizationPolicy; + _aggregateQueryable = aggregateQueryable; + } + + public Type ElementType => _aggregateQueryable.ElementType; + + public Expression Expression => Expression.And(_aggregateQueryable.Expression, _authorizationPolicy.HasAccessExpression); + + IAsyncQueryProvider IAsyncQueryable.Provider => _aggregateQueryable.Provider; + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) + { + return _aggregateQueryable.GetAsyncEnumerator(cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs new file mode 100644 index 00000000..87951037 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Domain; +using Backend.Fx.Exceptions; + +namespace Backend.Fx.Features.Authorization +{ + /// + /// Checks the authorization policy for write operations + /// + /// + internal class AuthorizingRepository : IRepository where TAggregateRoot : AggregateRoot + { + private readonly IAuthorizationPolicy _authorizationPolicy; + private readonly IRepository _repository; + + public AuthorizingRepository(IAuthorizationPolicy authorizationPolicy, IRepository repository) + { + _authorizationPolicy = authorizationPolicy; + _repository = repository; + } + + public Task SingleAsync(int id, CancellationToken cancellationToken = default) + { + return _repository.SingleAsync(id, cancellationToken); + } + + public Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default) + { + return _repository.SingleOrDefaultAsync(id, cancellationToken); + } + + public Task GetAllAsync(CancellationToken cancellationToken = default) + { + return _repository.GetAllAsync(cancellationToken); + } + + public Task AnyAsync(CancellationToken cancellationToken = default) + { + return _repository.AnyAsync(cancellationToken); + } + + public Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default) + { + return _repository.ResolveAsync(ids, cancellationToken); + } + + public async Task DeleteAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default) + { + if (_authorizationPolicy.CanDelete(aggregateRoot)) + { + await _repository.DeleteAsync(aggregateRoot, cancellationToken).ConfigureAwait(false); + } + else + { + throw new ForbiddenException($"You are not allowed to delete this record"); + } + } + + public async Task AddAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default) + { + if (_authorizationPolicy.CanCreate(aggregateRoot)) + { + await _repository.AddAsync(aggregateRoot, cancellationToken).ConfigureAwait(false); + } + else + { + throw new ForbiddenException($"You are not allowed to create such a record"); + } + } + + public async Task AddRangeAsync(TAggregateRoot[] aggregateRoots, CancellationToken cancellationToken = default) + { + if (aggregateRoots.Any(ar => !_authorizationPolicy.CanCreate(ar))) + { + throw new ForbiddenException($"You are not allowed to create such a record"); + } + + await _repository.AddRangeAsync(aggregateRoots, cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs b/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs index b8828e9c..5d1cfe47 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs @@ -1,10 +1,10 @@ using System; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; +using Backend.Fx.Domain; namespace Backend.Fx.Features.Authorization { - public class DenyAll : AggregateAuthorization where TAggregateRoot : AggregateRoot + public class DenyAll : AuthorizationPolicy where TAggregateRoot : AggregateRoot { public override Expression> HasAccessExpression { diff --git a/src/abstractions/Backend.Fx/Features/Authorization/IAggregateAuthorization.cs b/src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs similarity index 70% rename from src/abstractions/Backend.Fx/Features/Authorization/IAggregateAuthorization.cs rename to src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs index c33ddab0..19ba2662 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/IAggregateAuthorization.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs @@ -1,27 +1,22 @@ -using System.Linq; +using System; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; +using Backend.Fx.Domain; using JetBrains.Annotations; namespace Backend.Fx.Features.Authorization { /// - /// Implements permissions on aggregate level. The respective instance is applied when creating an , + /// Implements permissions on aggregate level. The respective instance is applied when creating an , /// so that the repository never allows reading or writing of an aggregate without permissions. /// /// [PublicAPI] - public interface IAggregateAuthorization where TAggregateRoot : AggregateRoot + public interface IAuthorizationPolicy where TAggregateRoot : AggregateRoot { /// /// Express a filter for repository queryable /// - Expression> HasAccessExpression { get; } - - /// - /// Only if the filter expression is not sufficient, you can override this method to apply the filtering to the queryable directly. - /// - IQueryable Filter(IQueryable queryable); + Expression> HasAccessExpression { get; } /// /// Implement a guard that might disallow adding to the repository. diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/BooleanSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/BooleanSerializer.cs new file mode 100644 index 00000000..2cdf3d7d --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/BooleanSerializer.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [UsedImplicitly] + public class BooleanSerializer : ISettingSerializer + { + public string Serialize(bool? setting) + { + return setting?.ToString(); + } + + public bool? Deserialize(string value) + { + return string.IsNullOrWhiteSpace(value) ? (bool?) null : bool.Parse(value); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs new file mode 100644 index 00000000..172a9c6c --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + public static class ConfigurationSettingsFeature + { + /// + /// The feature "Configuration Settings" provides a simple abstraction over an arbitrary key/value configuration + /// setting store. The default already provides serialization to and from + /// string for various configuration setting types, but you can provide your own implementation to extend the + /// functionality. + /// + /// + /// The factory that provides serializers. A singleton instance is being held. + /// The abstraction over your key/value store. Instances of this type will + /// be injected with a scoped lifetime. + [PublicAPI] + public static void AddConfigurationSettings( + this IBackendFxApplication application, + SettingSerializerFactory settingSerializerFactory = null) + where TSettingRepository : class, ISettingRepository + { + settingSerializerFactory = settingSerializerFactory ?? new SettingSerializerFactory(); + application.CompositionRoot.RegisterModules( + new ConfigurationSettingsModule(settingSerializerFactory)); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs new file mode 100644 index 00000000..e2e8f7cf --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs @@ -0,0 +1,25 @@ +using Backend.Fx.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + internal class ConfigurationSettingsModule : IModule + where TSettingRepository : class, ISettingRepository + { + private readonly SettingSerializerFactory _settingSerializerFactory; + + public ConfigurationSettingsModule(SettingSerializerFactory settingSerializerFactory) + { + _settingSerializerFactory = settingSerializerFactory; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.Register( + ServiceDescriptor.Singleton(_settingSerializerFactory)); + + compositionRoot.Register( + ServiceDescriptor.Scoped()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/DateTimeSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/DateTimeSerializer.cs new file mode 100644 index 00000000..1c455e82 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/DateTimeSerializer.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [UsedImplicitly] + public class DateTimeSerializer : ISettingSerializer + { + public string Serialize(DateTime? setting) + { + return setting?.ToString("O", CultureInfo.InvariantCulture); + } + + public DateTime? Deserialize(string value) + { + return string.IsNullOrWhiteSpace(value) ? (DateTime?) null : DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/DoubleSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/DoubleSerializer.cs new file mode 100644 index 00000000..120c6414 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/DoubleSerializer.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [UsedImplicitly] + public class DoubleSerializer : ISettingSerializer + { + public string Serialize(double? setting) + { + return setting?.ToString("r", CultureInfo.InvariantCulture); + } + + public double? Deserialize(string value) + { + return string.IsNullOrWhiteSpace(value) ? (double?) null : double.Parse(value, CultureInfo.InvariantCulture); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingRepository.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingRepository.cs new file mode 100644 index 00000000..14db94f5 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingRepository.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [PublicAPI] + public interface ISettingRepository + { + /// + /// Gets the serialized string value for a specific setting key in a specific category + /// + /// The category of the setting + /// The key of the setting + /// The serialized value of the configuration setting, or null when not configured. + [CanBeNull] + string GetSerializedValue([NotNull] string category, [NotNull] string key); + + /// + /// Writes the serialized string value for a specific setting key in a specific category + /// + /// The category of the setting + /// The key of the setting + /// The serialized value of the configuration setting + void WriteSerializedValue([NotNull] string category, [NotNull] string key, [CanBeNull] string serializedValue); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingSerializer.cs new file mode 100644 index 00000000..ad3ec434 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ISettingSerializer.cs @@ -0,0 +1,12 @@ +namespace Backend.Fx.Features.ConfigurationSettings +{ + public interface ISettingSerializer + { + } + + public interface ISettingSerializer : ISettingSerializer + { + string Serialize(T setting); + T Deserialize(string value); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/IntegerSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/IntegerSerializer.cs new file mode 100644 index 00000000..875358e4 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/IntegerSerializer.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [UsedImplicitly] + public class IntegerSerializer : ISettingSerializer + { + public string Serialize(int? setting) + { + return setting?.ToString(CultureInfo.InvariantCulture); + } + + public int? Deserialize(string value) + { + return string.IsNullOrWhiteSpace(value) ? (int?) null : int.Parse(value, CultureInfo.InvariantCulture); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingSerializerFactory.cs similarity index 96% rename from src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs rename to src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingSerializerFactory.cs index a270989c..8e2fdf6e 100644 --- a/src/abstractions/Backend.Fx/ConfigurationSettings/SettingSerializerFactory.cs +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingSerializerFactory.cs @@ -4,13 +4,14 @@ using System.Reflection; using JetBrains.Annotations; -namespace Backend.Fx.ConfigurationSettings +namespace Backend.Fx.Features.ConfigurationSettings { public interface ISettingSerializerFactory { ISettingSerializer GetSerializer(); } + [PublicAPI] public class SettingSerializerFactory : ISettingSerializerFactory { protected Dictionary Serializers { get; } diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingsCategory.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingsCategory.cs new file mode 100644 index 00000000..dafa9bcb --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/SettingsCategory.cs @@ -0,0 +1,51 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + /// + /// Base class to implement a category of settings. + /// A typical setting would be implemented as a read/write property. + /// + /// + /// public int MyIntegerSetting + /// { + /// get => ReadSetting<int?>(nameof(MyIntegerSetting)) ?? 0; + /// set => WriteSetting<int>>(nameof(MyIntegerSetting), value); + /// } + /// + /// + /// + [PublicAPI] + public abstract class SettingsCategory + { + private readonly string _category; + private readonly ISettingRepository _settingRepository; + private readonly ISettingSerializerFactory _settingSerializerFactory; + + protected SettingsCategory(string category, ISettingRepository settingRepository, ISettingSerializerFactory settingSerializerFactory) + { + _category = category; + _settingRepository = settingRepository; + _settingSerializerFactory = settingSerializerFactory; + } + + protected T ReadSetting(string key) + { + var serializedValue = _settingRepository.GetSerializedValue(_category, key); + if (serializedValue == null) + { + return default; + } + + var serializer = _settingSerializerFactory.GetSerializer(); + return serializer.Deserialize(serializedValue); + } + + protected void WriteSetting(string key, T value) + { + var serializer = _settingSerializerFactory.GetSerializer(); + var serializedValue = serializer.Serialize(value); + _settingRepository.WriteSerializedValue(_category, key, serializedValue); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/StringSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/StringSerializer.cs new file mode 100644 index 00000000..b9a9a88f --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/StringSerializer.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [UsedImplicitly] + public class StringSerializer : ISettingSerializer + { + public string Serialize(string setting) + { + return setting; + } + + public string Deserialize(string value) + { + return value; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs deleted file mode 100644 index 7162d2a8..00000000 --- a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGeneratingApplication.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using JetBrains.Annotations; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.DataGeneration -{ - /// - /// Enriches the by calling all data generators for all tenants - /// on application start - /// - public class DataGeneratingApplication : BackendFxApplicationDecorator - { - private static readonly ILogger Logger = Log.Create(); - - private readonly ITenantIdProvider _tenantIdProvider; - private readonly IBackendFxApplication _application; - - public IDataGenerationContext DataGenerationContext { get; [UsedImplicitly] private set; } - - /// To be able to query all active demo/production tenants - /// to make sure data generation will never run in parallel for the same tenant - /// the decorated instance - public DataGeneratingApplication( - ITenantIdProvider tenantIdProvider, - ITenantWideMutexManager tenantWideMutexManager, - IBackendFxApplication application) : base(application) - { - _tenantIdProvider = tenantIdProvider; - _application = application; - DataGenerationContext = new DataGenerationContext( - _application.CompositionRoot, - _application.Invoker, - tenantWideMutexManager); - } - - public override async Task BootAsync(CancellationToken cancellationToken = default) - { - _application.CompositionRoot.RegisterModules(new DataGenerationModule(_application.Assemblies)); - await base.BootAsync(cancellationToken).ConfigureAwait(false); - SeedDataForAllActiveTenants(); - } - - private void SeedDataForAllActiveTenants() - { - using (Logger.LogInformationDuration("Seeding data")) - { - var prodTenantIds = _tenantIdProvider.GetActiveProductionTenantIds(); - foreach (TenantId prodTenantId in prodTenantIds) - { - DataGenerationContext.SeedDataForTenant(prodTenantId, false); - } - - var demoTenantIds = _tenantIdProvider.GetActiveDemonstrationTenantIds(); - foreach (TenantId demoTenantId in demoTenantIds) - { - DataGenerationContext.SeedDataForTenant(demoTenantId, true); - } - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs deleted file mode 100644 index a30bc31c..00000000 --- a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationContext.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Linq; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.DataGeneration -{ - public interface IDataGenerationContext - { - void SeedDataForTenant(TenantId tenantId, bool isDemoTenant); - } - - public class DataGenerationContext : IDataGenerationContext - { - private static readonly ILogger Logger = Log.Create(); - - private readonly ICompositionRoot _compositionRoot; - private readonly IBackendFxApplicationInvoker _invoker; - private readonly ITenantWideMutexManager _mutexManager; - - public DataGenerationContext( - ICompositionRoot compositionRoot, - IBackendFxApplicationInvoker invoker, - ITenantWideMutexManager mutexManager) - { - _compositionRoot = compositionRoot; - _invoker = invoker; - _mutexManager = mutexManager; - } - - public void SeedDataForTenant(TenantId tenantId, bool isDemoTenant) - { - if (!_mutexManager.TryAcquire(tenantId, GetType().Name, out var mutex)) return; - - using (mutex) - { - using (Logger.LogInformationDuration($"Seeding data for tenant {tenantId.Value}")) - { - Type[] dataGeneratorTypesToRun = GetDataGeneratorTypes(_compositionRoot.ServiceProvider, isDemoTenant); - foreach (Type dataGeneratorTypeToRun in dataGeneratorTypesToRun) - { - _invoker.Invoke(serviceProvider => - { - IDataGenerator dataGenerator = serviceProvider - .GetServices() - .Single(dg => dg.GetType() == dataGeneratorTypeToRun); - dataGenerator.Generate(); - }, new SystemIdentity(), tenantId); - } - } - } - } - - private static Type[] GetDataGeneratorTypes(IServiceProvider serviceProvider, bool includeDemoDataGenerators) - { - using (IServiceScope scope = serviceProvider.CreateScope()) - { - var dataGenerators = scope - .ServiceProvider - .GetServices() - .OrderBy(dg => dg.Priority) - .Select(dg => dg.GetType()) - .ToArray(); - - if (!includeDemoDataGenerators) - { - dataGenerators = dataGenerators.Where(dg => !typeof(IDemoDataGenerator).IsAssignableFrom(dg)).ToArray(); - } - - return dataGenerators; - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs index 93dcf290..1fd8cabc 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs @@ -3,7 +3,6 @@ using Backend.Fx.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.Features.DomainEvents { diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs deleted file mode 100644 index 6216485a..00000000 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsApplication.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.Features.DomainEvents -{ - public class DomainEventsApplication : BackendFxApplicationDecorator - { - public DomainEventsApplication(IBackendFxApplication application) : base(application) - { - application.CompositionRoot.RegisterModules(new DomainEventsModule(application.Assemblies)); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs new file mode 100644 index 00000000..b383c0b1 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs @@ -0,0 +1,24 @@ +using Backend.Fx.ExecutionPipeline; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.DomainEvents +{ + + public static class DomainEventsFeature + { + /// + /// The feature "Domain Events" provides you with a domain event aggregator, that will be injected as a scoped + /// instance and generic domain event handlers that will also be injected as scoped instances. You can publish + /// arbitrary domain events using the instance, but domain events won't be + /// raised until the is completing. + /// Failures when handling domain events will result in canceling the whole operation, thus in rolling back a + /// possible transaction. + /// + /// + [PublicAPI] + public static void EnableDomainEvents(this IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new DomainEventsModule(application.Assemblies)); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs index c0283ff4..63e3e8f1 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs @@ -1,14 +1,17 @@ -using System; using System.Linq; using System.Reflection; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Logging; +using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Backend.Fx.Features.DomainEvents { - public class DomainEventsModule : IModule + internal class DomainEventsModule : IModule { + private static readonly ILogger Logger = Log.Create(); private readonly Assembly[] _assemblies; public DomainEventsModule(params Assembly[] assemblies) @@ -25,16 +28,23 @@ public void Register(ICompositionRoot compositionRoot) private void RegisterDomainEventHandlers(ICompositionRoot compositionRoot) { - foreach (Type domainEventType in _assemblies.GetImplementingTypes(typeof(IDomainEvent))) + foreach (var domainEventType in _assemblies.GetImplementingTypes(typeof(IDomainEvent))) { - Type handlerTypeForThisDomainEventType = typeof(IDomainEventHandler<>).MakeGenericType(domainEventType); + var handlerTypeForThisDomainEventType = typeof(IDomainEventHandler<>).MakeGenericType(domainEventType); var serviceDescriptors = _assemblies .GetImplementingTypes(handlerTypeForThisDomainEventType) .Select(t => new ServiceDescriptor(handlerTypeForThisDomainEventType, t, ServiceLifetime.Scoped)) .ToArray(); - compositionRoot.RegisterCollection(serviceDescriptors); + if (serviceDescriptors.Any()) + { + compositionRoot.RegisterCollection(serviceDescriptors); + } + else + { + Logger.LogInformation("No handlers for {DomainEventType} found", domainEventType); + } } } } diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs index b174c45f..85fcfb64 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/RaiseDomainEventsOperationDecorator.cs @@ -1,5 +1,6 @@ -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.Features.DomainEvents { @@ -17,9 +18,9 @@ public RaiseDomainEventsOperationDecorator( _operation = operation; } - public void Begin() + public void Begin(IServiceScope serviceScope) { - _operation.Begin(); + _operation.Begin(serviceScope); } public void Complete() diff --git a/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs b/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs new file mode 100644 index 00000000..00d1f138 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.DomainServices +{ + public static class DomainServicesFeature + { + /// + /// The feature "Domain Services" makes sure that all implementations of and + /// are injected as scoped instances. + /// + /// + /// + [PublicAPI] + public static void AddDomainServices(this IBackendFxApplication application) + => application.CompositionRoot.RegisterModules(new DomainServicesModule(application.Assemblies)); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesModule.cs b/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesModule.cs new file mode 100644 index 00000000..a1fc6999 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesModule.cs @@ -0,0 +1,53 @@ +using System.Linq; +using System.Reflection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Logging; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Features.DomainServices +{ + internal class DomainServicesModule : IModule + { + private static readonly ILogger Logger = Log.Create(); + private readonly Assembly[] _assemblies; + private readonly string _assembliesForLogging; + + public DomainServicesModule(params Assembly[] assemblies) + { + _assemblies = assemblies; + _assembliesForLogging = string.Join(",", _assemblies.Select(ass => ass.GetName().Name)); + } + + public void Register(ICompositionRoot compositionRoot) + { + RegisterDomainAndApplicationServices(compositionRoot); + } + + private void RegisterDomainAndApplicationServices(ICompositionRoot container) + { + Logger.LogDebug("Registering domain and application services from {Assemblies}", _assembliesForLogging); + + var serviceDescriptors = _assemblies.GetImplementingTypes(typeof(IDomainService)) + .Concat(_assemblies.GetImplementingTypes(typeof(IApplicationService))) + .SelectMany(type => + type.GetTypeInfo() + .ImplementedInterfaces + .Where(i => typeof(IDomainService) != i + && typeof(IApplicationService) != i + && (i.Namespace != null && i.Namespace.StartsWith("Backend") + || _assemblies.Contains(i.GetTypeInfo().Assembly))) + .Select(service => new ServiceDescriptor(service, type, ServiceLifetime.Scoped))); + + + foreach (var serviceDescriptor in serviceDescriptors) + { + Logger.LogDebug("Registering scoped service {ServiceType} with implementation {ImplementationType}", + serviceDescriptor.ServiceType.Name, + serviceDescriptor.ImplementationType.Name); + container.Register(serviceDescriptor); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs b/src/abstractions/Backend.Fx/Features/DomainServices/IApplicationService.cs similarity index 80% rename from src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs rename to src/abstractions/Backend.Fx/Features/DomainServices/IApplicationService.cs index 832d8777..cdd65a8b 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IApplicationService.cs +++ b/src/abstractions/Backend.Fx/Features/DomainServices/IApplicationService.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.BuildingBlocks +namespace Backend.Fx.Features.DomainServices { /// /// A marker interface to identify application services to be auto registered in the container on boot diff --git a/src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs b/src/abstractions/Backend.Fx/Features/DomainServices/IDomainService.cs similarity index 80% rename from src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs rename to src/abstractions/Backend.Fx/Features/DomainServices/IDomainService.cs index ddcc120d..633399b5 100644 --- a/src/abstractions/Backend.Fx/BuildingBlocks/IDomainService.cs +++ b/src/abstractions/Backend.Fx/Features/DomainServices/IDomainService.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.BuildingBlocks +namespace Backend.Fx.Features.DomainServices { /// /// A marker interface to domain application services to be auto registered in the container on boot diff --git a/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs b/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs index 9971deee..9e2670d4 100644 --- a/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs @@ -1,13 +1,40 @@ -using Backend.Fx.Patterns.DependencyInjection; +using System.Security.Principal; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.Features.Jobs { - public abstract class ApplicationWithJobs : BackendFxApplicationDecorator + public static class JobsFeature { - protected ApplicationWithJobs(IBackendFxApplication application) - : base(application) + /// + /// The feature "Jobs" makes sure, that all implementations of are injected as scoped instances. + /// You can use to execute any job later on, using a scheduler or other triggers. + /// + /// + [PublicAPI] + public static void EnableJobs(IBackendFxApplication application) { application.CompositionRoot.RegisterModules(new JobModule(application.Assemblies)); } + + /// + /// Runs the given job. + /// + /// + /// The identity who should run the job. Defaults to when omitted. + /// + [PublicAPI] + public static async Task RunJob(this IBackendFxApplication application, IIdentity identity = null) + where TJob : IJob + { + identity = identity ?? new SystemIdentity(); + await application + .Invoker + .InvokeAsync(async sp => + await sp.GetRequiredService().RunAsync().ConfigureAwait(false), identity) + .ConfigureAwait(false); + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs b/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs index 63225f2c..3ee01409 100644 --- a/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs @@ -1,10 +1,12 @@ -namespace Backend.Fx.Features.Jobs +using System.Threading.Tasks; + +namespace Backend.Fx.Features.Jobs { /// /// This interface describes a job that can be executed directly or by a scheduler. /// public interface IJob { - void Run(); + Task RunAsync(); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs b/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs index d556cdea..4cb9811a 100644 --- a/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs @@ -1,12 +1,12 @@ using System; using System.Reflection; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.Features.Jobs { - public class JobModule : IModule + internal class JobModule : IModule { private readonly Assembly[] _assemblies; diff --git a/src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs b/src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs deleted file mode 100644 index 3a3f5c25..00000000 --- a/src/abstractions/Backend.Fx/Features/Jobs/WithTenantWideMutex.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.Jobs -{ - [PublicAPI] - public class WithTenantWideMutex : IJob where TJob : IJob - { - private static readonly ILogger Logger = Log.Create>(); - private readonly TJob _job; - private readonly ICurrentTHolder _tenantIdHolder; - private readonly ITenantWideMutexManager _tenantWideMutexManager; - - public WithTenantWideMutex( - ITenantWideMutexManager tenantWideMutexManager, - TJob job, - ICurrentTHolder tenantIdHolder) - { - _tenantWideMutexManager = tenantWideMutexManager; - _job = job; - _tenantIdHolder = tenantIdHolder; - } - - public void Run() - { - if (_tenantWideMutexManager.TryAcquire(_tenantIdHolder.Current, typeof(TJob).Name, out var mutex)) - { - try - { - _job.Run(); - } - finally - { - mutex.Dispose(); - } - } - else - { - var tenantIdString - = _tenantIdHolder.Current.HasValue ? _tenantIdHolder.Current.Value.ToString() : "null"; - Logger.LogInformation("{Job} is already running in tenant {TenantId}", typeof(TJob).Name, tenantIdString); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs deleted file mode 100644 index ff454688..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/DelegateIntegrationMessageHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Backend.Fx.Features.MessageBus -{ - public class DelegateIntegrationMessageHandler - : IIntegrationMessageHandler where TIntegrationEvent : IIntegrationEvent - { - private readonly Action _handleAction; - - public DelegateIntegrationMessageHandler(Action handleAction) - { - _handleAction = handleAction; - } - - public void Handle(TIntegrationEvent eventData) - { - _handleAction.Invoke(eventData); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs deleted file mode 100644 index ca4a0dd3..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/DynamicSubscription.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using Backend.Fx.Extensions; -using Backend.Fx.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - - -namespace Backend.Fx.Features.MessageBus -{ - public class DynamicSubscription : ISubscription - { - private static readonly ILogger Logger = Log.Create(); - private readonly Type _handlerType; - - public DynamicSubscription(Type handlerType) - { - _handlerType = handlerType; - } - - public void Process(IServiceProvider serviceProvider, EventProcessingContext context) - { - Logger.LogInformation("Getting subscribed handler instance of type {HandlerTypeName}", _handlerType.Name); - object handlerInstance = serviceProvider.GetRequiredService(_handlerType); - using (Logger.LogInformationDuration($"Invoking subscribed handler {_handlerType.GetDetailedTypeName()}")) - { - ((IIntegrationMessageHandler) handlerInstance).Handle(context.DynamicEvent); - } - } - - public bool Matches(object handler) - { - return (Type) handler == _handlerType; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs b/src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs deleted file mode 100644 index 47c06329..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/EventProcessingContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Backend.Fx.Environment.MultiTenancy; - -namespace Backend.Fx.Features.MessageBus -{ - public abstract class EventProcessingContext - { - public abstract TenantId TenantId { get; } - public abstract dynamic DynamicEvent { get; } - public abstract Guid CorrelationId { get; } - - public abstract IIntegrationEvent GetTypedEvent(Type eventType); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs deleted file mode 100644 index d099f655..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationMessageHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JetBrains.Annotations; - -namespace Backend.Fx.Features.MessageBus -{ - [PublicAPI] - public interface IIntegrationMessageHandler - { - void Handle(dynamic eventData); - } - - [PublicAPI] - public interface IIntegrationMessageHandler where TEvent : IIntegrationEvent - { - void Handle(TEvent eventData); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs deleted file mode 100644 index 63e9cb8b..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/IMessageBus.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Threading.Tasks; -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.Features.MessageBus -{ - public interface IMessageBus : IDisposable - { - /// - /// This instance is used to determine a message name from an integration event. The default implementation just - /// returns event.GetType().Name - /// - IMessageNameProvider MessageNameProvider { get; } - - void Connect(); - - /// - /// Directly publishes an event on the message bus without delay. - /// In most cases you want to publish an event when the cause is considered as safely done, e.g. when the - /// wrapping transaction is committed. Use to let the framework raise all events - /// after completing the operation. - /// - /// - /// - Task Publish(IIntegrationEvent integrationEvent); - - /// - /// Subscribes to an integration event with a dynamic event handler - /// - /// The handler type - /// The event name to subscribe to. - void Subscribe(string eventName) - where THandler : IIntegrationMessageHandler; - - /// - /// Subscribes to an integration event with a generically typed event handler - /// - /// The handler type - /// The event type to subscribe to - void Subscribe() - where THandler : IIntegrationMessageHandler - where TEvent : IIntegrationEvent; - - /// - /// Subscribes to an integration event with a singleton instance event handler - /// - /// The event type to subscribe to - void Subscribe(IIntegrationMessageHandler handler) - where TEvent : IIntegrationEvent; - - void Unsubscribe(string eventName) - where THandler : IIntegrationMessageHandler; - - void Unsubscribe() - where THandler : IIntegrationMessageHandler - where TEvent : IIntegrationEvent; - - void Unsubscribe(IIntegrationMessageHandler handler) - where TEvent : IIntegrationEvent; - - void ProvideInvoker(IBackendFxApplicationInvoker invoker); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs deleted file mode 100644 index d3ec5a41..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/IMessageNameProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace Backend.Fx.Features.MessageBus -{ - [PublicAPI] - public interface IMessageNameProvider - { - [NotNull] - string GetMessageName(); - [NotNull] - string GetMessageName(Type t); - [NotNull] - string GetMessageName(IIntegrationEvent integrationEvent); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs deleted file mode 100644 index 77c934c0..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/ISubscription.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Backend.Fx.Features.MessageBus -{ - public interface ISubscription - { - void Process(IServiceProvider serviceProvider, EventProcessingContext context); - bool Matches(object handler); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs deleted file mode 100644 index ed6848c7..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/InMemoryMessageBus.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; - -namespace Backend.Fx.Features.MessageBus -{ - public class InMemoryMessageBus : MessageBus - { - private readonly InMemoryMessageBusChannel _channel; - - public InMemoryMessageBus() - { - _channel = new InMemoryMessageBusChannel(); - } - - public InMemoryMessageBus(InMemoryMessageBusChannel channel) - { - _channel = channel; - } - - public override void Connect() - { - _channel.MessageReceived += ChannelOnMessageReceived; - } - - protected override void Dispose(bool disposing) - { - _channel.MessageReceived -= ChannelOnMessageReceived; - } - - protected override Task PublishOnMessageBus(IIntegrationEvent integrationEvent) - { - _channel.Publish(integrationEvent); - - // the returning Task is about publishing the event, not processing! - return Task.CompletedTask; - } - - protected override void Subscribe(string eventName) - { - } - - protected override void Unsubscribe(string eventName) - { - } - - private void ChannelOnMessageReceived( - object sender, - InMemoryMessageBusChannel.MessageReceivedEventArgs eventArgs) - { - Process( - MessageNameProvider.GetMessageName(eventArgs.IntegrationEvent), - new InMemoryProcessingContext(eventArgs.IntegrationEvent)); - } - - private class InMemoryProcessingContext : EventProcessingContext - { - private readonly IIntegrationEvent _integrationEvent; - - public InMemoryProcessingContext(IIntegrationEvent integrationEvent) - { - _integrationEvent = integrationEvent; - } - - public override TenantId TenantId => new TenantId(_integrationEvent.TenantId); - - public override dynamic DynamicEvent => _integrationEvent; - public override Guid CorrelationId => _integrationEvent.CorrelationId; - - public override IIntegrationEvent GetTypedEvent(Type eventType) - { - return _integrationEvent; - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs deleted file mode 100644 index 58e8654c..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using Backend.Fx.Environment.MultiTenancy; -using JetBrains.Annotations; - -namespace Backend.Fx.Features.MessageBus -{ - [PublicAPI] - public interface IIntegrationEvent - { - Guid Id { get; } - DateTime CreationDate { get; } - int TenantId { get; } - Guid CorrelationId { get; } - } - - /// - /// Events that should be handled in a separate context. Might be persisted as well using an external message bus. - /// See https://blogs.msdn.microsoft.com/cesardelatorre/2017/02/07/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/ - /// - public abstract class IntegrationEvent : IIntegrationEvent - { - public Guid Id { get; } = Guid.NewGuid(); - - public DateTime CreationDate { get; } = DateTime.UtcNow; - - public int TenantId { get; private set; } - - public Guid CorrelationId { get; private set; } - - protected IntegrationEvent() - { - } - - [Obsolete("TenantId is maintained by the framework now")] - protected IntegrationEvent(int tenantId) - { - TenantId = tenantId; - } - - internal void SetCorrelationId(Guid correlationId) - { - CorrelationId = correlationId; - } - - public void SetTenantId(TenantId tenantId) - { - TenantId = (int) tenantId; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs deleted file mode 100644 index e8b2ed3f..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.MessageBus -{ - public abstract class MessageBus : IMessageBus - { - private static readonly ILogger Logger = Log.Create(); - - /// - /// Holds the registered handlers. - /// Each event type name (key) matches to various subscriptions - /// - private readonly ConcurrentDictionary> _subscriptions = new ConcurrentDictionary>(); - - private IBackendFxApplicationInvoker _invoker; - - public IMessageNameProvider MessageNameProvider { get; } = new DefaultMessageNameProvider(); - public abstract void Connect(); - - public void ProvideInvoker(IBackendFxApplicationInvoker invoker) - { - if (_invoker != null && !Equals(_invoker, invoker)) - { - throw new InvalidOperationException("This message bus instance has been linked to an application instance invoker before. " + - "You cannot share the same message bus instance between multiple applications."); - } - _invoker = invoker; - } - - public Task Publish(IIntegrationEvent integrationEvent) - { - return PublishOnMessageBus(integrationEvent); - } - - protected abstract Task PublishOnMessageBus(IIntegrationEvent integrationEvent); - - - /// - public void Subscribe(string eventName) where THandler : IIntegrationMessageHandler - { - Logger.LogInformation("Subscribing to {EventName}", eventName); - EnsureInvoker(); - var subscription = new DynamicSubscription(typeof(THandler)); - _subscriptions.AddOrUpdate(eventName, - s => new List {subscription}, - (s, list) => - { - list.Add(subscription); - return list; - }); - Subscribe(eventName); - } - - /// - public void Subscribe() where THandler : IIntegrationMessageHandler where TEvent : IIntegrationEvent - { - string eventName = MessageNameProvider.GetMessageName(); - Logger.LogInformation("Subscribing to {EventName}", eventName); - EnsureInvoker(); - var subscription = new TypedSubscription(typeof(THandler), typeof(TEvent)); - _subscriptions.AddOrUpdate(eventName, - s => new List {subscription}, - (s, list) => - { - list.Add(subscription); - return list; - }); - Subscribe(eventName); - } - - public void Subscribe(IIntegrationMessageHandler handler) - where TEvent : IIntegrationEvent - { - string eventName = MessageNameProvider.GetMessageName(); - Logger.LogInformation("Subscribing to {EventName}", eventName); - EnsureInvoker(); - var subscription = new SingletonSubscription(handler); - _subscriptions.AddOrUpdate(eventName, - s => new List {subscription}, - (s, list) => - { - list.Add(subscription); - return list; - }); - Subscribe(eventName); - } - - public void Unsubscribe(string eventName) where THandler : IIntegrationMessageHandler - { - Logger.LogInformation("Unsubscribing from {EventName}", eventName); - if (_subscriptions.TryGetValue(eventName, out var handlers)) - { - handlers.RemoveAll(t => t.Matches(typeof(THandler))); - } - - Unsubscribe(eventName); - } - - public void Unsubscribe() where THandler : IIntegrationMessageHandler where TEvent : IIntegrationEvent - { - string eventName = MessageNameProvider.GetMessageName(); - Logger.LogInformation("Unsubscribing from {EventName}", eventName); - if (_subscriptions.TryGetValue(eventName, out var handlers)) - { - handlers.RemoveAll(t => t.Matches(typeof(THandler))); - } - - Unsubscribe(eventName); - } - - public void Unsubscribe(IIntegrationMessageHandler handler) where TEvent : IIntegrationEvent - { - string eventName = MessageNameProvider.GetMessageName(); - Logger.LogInformation("Unsubscribing from {EventName}", eventName); - if (_subscriptions.TryGetValue(eventName, out var handlers)) - { - handlers.RemoveAll(t => t.Matches(handler)); - } - - Unsubscribe(eventName); - } - - protected abstract void Subscribe(string eventName); - protected abstract void Unsubscribe(string eventName); - - protected void Process(string eventName, EventProcessingContext context) - { - Logger.LogInformation("Processing a {EventName} message", eventName); - EnsureInvoker(); - - if (_subscriptions.TryGetValue(eventName, out List subscriptions)) - { - foreach (ISubscription subscription in subscriptions) - { - try - { - _invoker.Invoke( - serviceProvider => subscription.Process(serviceProvider, context), - new SystemIdentity(), - context.TenantId, - context.CorrelationId); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Processing a {EventName} message failed", eventName); - throw; - } - } - } - else - { - Logger.LogInformation("No handler registered. Ignoring {EventName} event", eventName); - } - } - - private void EnsureInvoker() - { - if (_invoker == null) - { - throw new InvalidOperationException("Before using the message bus you have to provide the application invoker by calling ProvideInvoker()"); - } - } - - protected virtual void Dispose(bool disposing) - { - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private class DefaultMessageNameProvider : IMessageNameProvider - { - public string GetMessageName() - { - return GetMessageName(typeof(T)); - } - - public string GetMessageName(Type t) - { - var messageName = t.Name; - return messageName; - } - - public string GetMessageName(IIntegrationEvent integrationEvent) - { - return GetMessageName(integrationEvent.GetType()); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs deleted file mode 100644 index 99634876..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusApplication.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.Features.MessageBus -{ - public class MessageBusApplication : BackendFxApplicationDecorator - { - private readonly IMessageBus _messageBus; - - public MessageBusApplication(IMessageBus messageBus, IBackendFxApplication application) - : base(application) - { - application.CompositionRoot.RegisterModules(new MessageBusModule(messageBus, application.Assemblies)); - _messageBus = messageBus; - _messageBus.ProvideInvoker( - new SequentializingBackendFxApplicationInvoker( - new ExceptionLoggingAndHandlingInvoker(application.ExceptionLogger, application.Invoker))); - } - - public override async Task BootAsync(CancellationToken cancellationToken = default) - { - await base.BootAsync(cancellationToken).ConfigureAwait(false); - _messageBus.Connect(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _messageBus.Dispose(); - } - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs deleted file mode 100644 index 62bd5d6f..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.Features.MessageBus -{ - public class MessageBusModule : IModule - { - private readonly IMessageBus _messageBus; - private readonly IEnumerable _assemblies; - - public MessageBusModule(IMessageBus messageBus, IEnumerable assemblies) - { - _messageBus = messageBus; - _assemblies = assemblies; - } - - public void Register(ICompositionRoot compositionRoot) - { - // note tht there should be no reason to access the singleton message bus instance from the service provider - - // register the message bus scope - compositionRoot.Register( - ServiceDescriptor.Scoped( - sp => new MessageBusScope( - _messageBus, - sp.GetRequiredService>(), - sp.GetRequiredService>()))); - - // register typed handlers - foreach (Type integrationEventType in _assemblies.GetImplementingTypes(typeof(IIntegrationEvent))) - { - Type handlerServiceType = typeof(IIntegrationMessageHandler<>).MakeGenericType(integrationEventType); - foreach (var handlerImplementingType in _assemblies.GetImplementingTypes(handlerServiceType)) - { - compositionRoot.Register( - new ServiceDescriptor( - handlerServiceType, - handlerImplementingType, - ServiceLifetime.Scoped)); - } - } - - // make sure all integration events are raised after completing an operation, but before ending the scope - compositionRoot.RegisterDecorator( - ServiceDescriptor.Scoped()); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs deleted file mode 100644 index 69191f8e..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/SequentializingBackendFxApplicationInvoker.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Security.Principal; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.Features.MessageBus -{ - /// - /// Decorates the to prevent parallel invocation. - /// - public class SequentializingBackendFxApplicationInvoker : IBackendFxApplicationInvoker - { - private readonly object _syncLock = new object(); - private readonly IBackendFxApplicationInvoker _backendFxApplicationInvokerImplementation; - - public SequentializingBackendFxApplicationInvoker(IBackendFxApplicationInvoker backendFxApplicationInvokerImplementation) - { - _backendFxApplicationInvokerImplementation = backendFxApplicationInvokerImplementation; - } - - - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) - { - lock (_syncLock) - { - _backendFxApplicationInvokerImplementation.Invoke(action, identity, tenantId, correlationId); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs deleted file mode 100644 index cabf653f..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/SingletonSubscription.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Backend.Fx.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.MessageBus -{ - public class SingletonSubscription : ISubscription where TEvent : IIntegrationEvent - { - private static readonly ILogger Logger = Log.Create>(); - private readonly IIntegrationMessageHandler _handler; - - public SingletonSubscription(IIntegrationMessageHandler handler) - { - _handler = handler; - } - - public void Process(IServiceProvider serviceProvider, EventProcessingContext context) - { - using (Logger.LogInformationDuration($"Invoking subscribed handler {_handler.GetType().Name}")) - { - _handler.Handle((TEvent) context.GetTypedEvent(typeof(TEvent))); - } - } - - public bool Matches(object handler) - { - return _handler == handler; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs b/src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs deleted file mode 100644 index 72fac18a..00000000 --- a/src/abstractions/Backend.Fx/Features/MessageBus/TypedSubscription.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Diagnostics; -using System.Reflection; -using Backend.Fx.Extensions; -using Backend.Fx.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.MessageBus -{ - public class TypedSubscription : ISubscription - { - private static readonly ILogger Logger = Log.Create(); - private readonly Type _handlerType; - private readonly Type _eventType; - - public TypedSubscription(Type handlerType, Type eventType) - { - _handlerType = handlerType; - _eventType = eventType; - } - - public void Process(IServiceProvider serviceProvider, EventProcessingContext context) - { - IIntegrationEvent integrationEvent = context.GetTypedEvent(_eventType); - MethodInfo handleMethod = _handlerType.GetRuntimeMethod("Handle", new[] {_eventType}); - Debug.Assert(handleMethod != null, $"No method with signature `Handle({_eventType.Name} event)` found on {_handlerType.Name}"); - - Logger.LogInformation("Getting subscribed handler instance of type {HandlerTypeName}", _handlerType.Name); - object handlerInstance = serviceProvider.GetRequiredService(_handlerType); - - using (Logger.LogInformationDuration($"Invoking subscribed handler {_handlerType.GetDetailedTypeName()}")) - { - handleMethod.Invoke(handlerInstance, new object[] {integrationEvent}); - } - } - - public bool Matches(object handler) - { - return (Type) handler == _handlerType; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs similarity index 90% rename from src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs index c5c4f1c2..7f767237 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/CurrentTenantIdHolder.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs @@ -1,6 +1,7 @@ -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; -namespace Backend.Fx.Environment.MultiTenancy +namespace Backend.Fx.Features.MultiTenancy { public class CurrentTenantIdHolder : CurrentTHolder { diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/ICurrentTenantIdSelector.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/ICurrentTenantIdSelector.cs new file mode 100644 index 00000000..794c97c8 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/ICurrentTenantIdSelector.cs @@ -0,0 +1,7 @@ +namespace Backend.Fx.Features.MultiTenancy +{ + public interface ICurrentTenantIdSelector + { + TenantId GetCurrentTenantId(); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs new file mode 100644 index 00000000..37295eb8 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs @@ -0,0 +1,10 @@ +using System.Linq.Expressions; +using Backend.Fx.Domain; + +namespace Backend.Fx.Features.MultiTenancy +{ + public interface ITenantFilterExpressionFactory where TAggregateRoot : AggregateRoot + { + Expression CreateTenantFilterExpression(); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantWideMutex.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutex.cs similarity index 62% rename from src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantWideMutex.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutex.cs index 1be237b7..015cc902 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantWideMutex.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutex.cs @@ -1,6 +1,6 @@ using System; -namespace Backend.Fx.Environment.MultiTenancy +namespace Backend.Fx.Features.MultiTenancy { public interface ITenantWideMutex : IDisposable { } diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutexManager.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutexManager.cs new file mode 100644 index 00000000..789aa063 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantWideMutexManager.cs @@ -0,0 +1,7 @@ +namespace Backend.Fx.Features.MultiTenancy +{ + public interface ITenantWideMutexManager + { + bool TryAcquire(TenantId tenantId, string key, out ITenantWideMutex mutex); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantWideMutexManager.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/InMemoryTenantWideMutexManager.cs similarity index 85% rename from src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantWideMutexManager.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancy/InMemoryTenantWideMutexManager.cs index e06b7ec8..3fdb12f5 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/ITenantWideMutexManager.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/InMemoryTenantWideMutexManager.cs @@ -3,13 +3,8 @@ using System.Threading; using JetBrains.Annotations; -namespace Backend.Fx.Environment.MultiTenancy +namespace Backend.Fx.Features.MultiTenancy { - public interface ITenantWideMutexManager - { - bool TryAcquire(TenantId tenantId, string key, out ITenantWideMutex mutex); - } - /// /// If an instance of this class is being hold as singleton it can be used to manage tenant wide /// mutexes in a single process. When multiple processes are running, another locking mechanism @@ -36,11 +31,9 @@ public bool TryAcquire(TenantId tenantId, string key, out ITenantWideMutex tenan tenantWideMutex = new InMemoryTenantWideMutex(() => mutex.ReleaseMutex()); return true; } - else - { - tenantWideMutex = null; - return false; - } + + tenantWideMutex = null; + return false; } } diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs new file mode 100644 index 00000000..e6d72216 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs @@ -0,0 +1,26 @@ +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; +using Backend.Fx.Util; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.MultiTenancy +{ + /// + /// The feature "Multi Tenancy" makes you provide an implementation of and + /// takes care of asking it for the current tenant id and setting it in the + /// on every operation. + /// + /// + [PublicAPI] + public static class MultiTenancyFeature + { + public static void EnableMultiTenancy (this IBackendFxApplication application) + where T : class, ICurrentTenantIdSelector + { + application.CompositionRoot.RegisterModules( + new MultiTenancyModule(application.As() != null)); + + ((BackendFxApplication)application).IsMultiTenancyApplication = true; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs new file mode 100644 index 00000000..12a8e2d4 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs @@ -0,0 +1,41 @@ +using System.Linq; +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.MultiTenancy +{ + public class MultiTenancyModule : IModule + where TCurrentTenantIdSelector : class, ICurrentTenantIdSelector + + { + private readonly bool _isPersistentApplication; + + public MultiTenancyModule(bool isPersistentApplication) + { + _isPersistentApplication = isPersistentApplication; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.Register( + ServiceDescriptor.Scoped()); + + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); + + compositionRoot.Register( + ServiceDescriptor.Scoped, CurrentTenantIdHolder>()); + + if (_isPersistentApplication) + { + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped(typeof(IQueryable<>), typeof(TenantFilteredQueryable<>))); + + // compositionRoot.Register( + // ServiceDescriptor.Scoped(typeof(ITenantFilterExpressionFactory<>),typeof(TTenantFilterExpressionFactory<>))); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs new file mode 100644 index 00000000..7d151b03 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Backend.Fx.Domain; + +namespace Backend.Fx.Features.MultiTenancy +{ + internal class TenantFilteredQueryable : IQueryable where TAggregateRoot : AggregateRoot + { + private readonly ITenantFilterExpressionFactory _tenantFilterExpressionFactory; + private readonly IQueryable _queryable; + + public TenantFilteredQueryable(ITenantFilterExpressionFactory tenantFilterExpressionFactory, IQueryable queryable) + { + _tenantFilterExpressionFactory = tenantFilterExpressionFactory; + _queryable = queryable; + } + + public IEnumerator GetEnumerator() + { + return _queryable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_queryable).GetEnumerator(); + } + + public Type ElementType => _queryable.ElementType; + + public Expression Expression => Expression.And(_queryable.Expression, _tenantFilterExpressionFactory.CreateTenantFilterExpression()); + + public IQueryProvider Provider => _queryable.Provider; + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs similarity index 71% rename from src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs index fbcd5ffd..1fc1056b 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/TenantId.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using Backend.Fx.BuildingBlocks; -namespace Backend.Fx.Environment.MultiTenancy +namespace Backend.Fx.Features.MultiTenancy { [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public class TenantId : ValueObject + public class TenantId { private readonly int? _id; @@ -51,9 +49,17 @@ public override string ToString() return _id?.ToString() ?? "NULL"; } - protected override IEnumerable GetEqualityComponents() + public override bool Equals(object obj) { - yield return _id; + if (ReferenceEquals(this, obj)) return true; + if (ReferenceEquals(null, obj)) return false; + if (GetType() != obj.GetType()) return false; + return Equals(Value, (obj as TenantId)?.Value); + } + + public override int GetHashCode() + { + return HasValue ? Value.GetHashCode() : 17.GetHashCode(); } public static explicit operator int(TenantId tid) => tid.Value; diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantOperationDecorator.cs new file mode 100644 index 00000000..7be133d0 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantOperationDecorator.cs @@ -0,0 +1,36 @@ +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.MultiTenancy +{ + [UsedImplicitly] + public class TenantOperationDecorator : IOperation + { + private readonly IOperation _operation; + + public TenantOperationDecorator(IOperation operation) + { + _operation = operation; + } + public void Begin(IServiceScope serviceScope) + { + var currentTenantIdSelector = serviceScope.ServiceProvider.GetRequiredService(); + var currentTenantId = currentTenantIdSelector.GetCurrentTenantId(); + var tenantIdHolder = serviceScope.ServiceProvider.GetRequiredService>(); + tenantIdHolder.ReplaceCurrent(currentTenantId); + _operation.Begin(serviceScope); + } + + public void Complete() + { + _operation.Complete(); + } + + public void Cancel() + { + _operation.Cancel(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs b/src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs deleted file mode 100644 index e9389a16..00000000 --- a/src/abstractions/Backend.Fx/Features/Persistence/IEntityIdGenerator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Backend.Fx.Features.Persistence -{ - public interface IEntityIdGenerator - { - int NextId(); - } -} diff --git a/src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs deleted file mode 100644 index 51d43333..00000000 --- a/src/abstractions/Backend.Fx/Features/Persistence/ReadonlyDbTransactionOperationDecorator.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Features.Persistence -{ - public class ReadonlyDbTransactionOperationDecorator : IOperation - { - private static readonly ILogger Logger = Log.Create(); - private readonly IOperation _operationImplementation; - - public ReadonlyDbTransactionOperationDecorator(IOperation operationImplementation) - { - _operationImplementation = operationImplementation; - } - - public void Begin() - { - _operationImplementation.Begin(); - } - - public void Complete() - { - Logger.LogDebug("Canceling operation instead of completing it due to classification as readonly operation"); - _operationImplementation.Cancel(); - } - - public void Cancel() - { - _operationImplementation.Cancel(); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Hacking/AdjustableClock.cs b/src/abstractions/Backend.Fx/Hacking/AdjustableClock.cs new file mode 100644 index 00000000..83ce3a22 --- /dev/null +++ b/src/abstractions/Backend.Fx/Hacking/AdjustableClock.cs @@ -0,0 +1,35 @@ +using Backend.Fx.Logging; +using Microsoft.Extensions.Logging; +using NodaTime; + +namespace Backend.Fx.Hacking +{ + public class AdjustableClock : IClock + { + private static readonly ILogger Logger = Log.Create(); + + private readonly IClock _clockImplementation; + private Instant? _overriddenUtcNow; + + public AdjustableClock(IClock clockImplementation) + { + _clockImplementation = clockImplementation; + } + + public Instant GetCurrentInstant() => _overriddenUtcNow ?? _clockImplementation.GetCurrentInstant(); + + public void OverrideUtcNow(Instant instant) + { + Logger.LogTrace("Adjusting clock to {Instant}", instant); + _overriddenUtcNow = instant; + } + + public Instant Advance(Duration duration) + { + _overriddenUtcNow = _overriddenUtcNow ?? _clockImplementation.GetCurrentInstant(); + Logger.LogTrace("Advancing clock by {TimeSpan}", duration); + _overriddenUtcNow = _overriddenUtcNow.Value.Plus(duration); + return _overriddenUtcNow.Value; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs index b4d9ac0d..ea8926d2 100644 --- a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs @@ -51,32 +51,32 @@ private static string FormatDuration(string activity, TimeSpan duration) public static class DurationLoggerEx { - public static IDisposable LogInformationDuration(this Microsoft.Extensions.Logging.ILogger logger, string activity) + public static IDisposable LogInformationDuration(this ILogger logger, string activity) { return new DurationLogger(s => logger.LogInformation(s), activity); } - public static IDisposable LogInformationDuration(this Microsoft.Extensions.Logging.ILogger logger, string beginMessage, string endMessage) + public static IDisposable LogInformationDuration(this ILogger logger, string beginMessage, string endMessage) { return new DurationLogger(s => logger.LogInformation(s), beginMessage, endMessage); } - public static IDisposable LogDebugDuration(this Microsoft.Extensions.Logging.ILogger logger, string activity) + public static IDisposable LogDebugDuration(this ILogger logger, string activity) { return new DurationLogger(s => logger.LogDebug(s), activity); } - public static IDisposable LogDebugDuration(this Microsoft.Extensions.Logging.ILogger logger, string beginMessage, string endMessage) + public static IDisposable LogDebugDuration(this ILogger logger, string beginMessage, string endMessage) { return new DurationLogger(s => logger.LogDebug(s), beginMessage, endMessage); } - public static IDisposable LogTraceDuration(this Microsoft.Extensions.Logging.ILogger logger, string activity) + public static IDisposable LogTraceDuration(this ILogger logger, string activity) { return new DurationLogger(s => logger.LogTrace(s), activity); } - public static IDisposable LogTraceDuration(this Microsoft.Extensions.Logging.ILogger logger, string beginMessage, string endMessage) + public static IDisposable LogTraceDuration(this ILogger logger, string beginMessage, string endMessage) { return new DurationLogger(s => logger.LogTrace(s), beginMessage, endMessage); } diff --git a/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs b/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs index 7a6dd561..47403453 100644 --- a/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs +++ b/src/abstractions/Backend.Fx/Logging/ExceptionLoggers.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.Logging [PublicAPI] public class ExceptionLoggers : ICollection, IExceptionLogger { - private static readonly Microsoft.Extensions.Logging.ILogger Logger = Log.Create(); + private static readonly ILogger Logger = Log.Create(); private readonly ICollection _collectionImplementation = new List(); public ExceptionLoggers() diff --git a/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs b/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs index 891eb7a5..f967fb46 100644 --- a/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/IExceptionLogger.cs @@ -11,9 +11,9 @@ public interface IExceptionLogger public class ExceptionLogger : IExceptionLogger { - private readonly Microsoft.Extensions.Logging.ILogger _logger; + private readonly ILogger _logger; - public ExceptionLogger(Microsoft.Extensions.Logging.ILogger logger) + public ExceptionLogger(ILogger logger) { _logger = logger; } diff --git a/src/abstractions/Backend.Fx/Logging/Log.cs b/src/abstractions/Backend.Fx/Logging/Log.cs index 57c5bb1c..2f961422 100644 --- a/src/abstractions/Backend.Fx/Logging/Log.cs +++ b/src/abstractions/Backend.Fx/Logging/Log.cs @@ -1,6 +1,6 @@ using System; using System.Threading; -using Backend.Fx.Extensions; +using Backend.Fx.Util; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -13,12 +13,12 @@ namespace Backend.Fx.Logging [PublicAPI] public static class Log { - private static Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory = new NullLoggerFactory(); + private static ILoggerFactory _loggerFactory = new NullLoggerFactory(); - private static readonly AsyncLocal AsyncLocalLoggerFactory = - new AsyncLocal(); + private static readonly AsyncLocal AsyncLocalLoggerFactory = + new AsyncLocal(); - public static void Initialize(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) + public static void Initialize(ILoggerFactory loggerFactory) { _loggerFactory = loggerFactory; } @@ -27,37 +27,37 @@ public static void Initialize(Microsoft.Extensions.Logging.ILoggerFactory logger /// Override the global, static ILoggerFactory in this async local scope. This can be done per web request or per test run /// /// - public static IDisposable InitAsyncLocal(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) + public static IDisposable InitAsyncLocal(ILoggerFactory loggerFactory) { AsyncLocalLoggerFactory.Value = loggerFactory; return new DelegateDisposable(() => AsyncLocalLoggerFactory.Value = null); } - public static Microsoft.Extensions.Logging.ILogger Create() + public static ILogger Create() { return LoggerFactory.CreateLogger(typeof(T).FullName); } - public static Microsoft.Extensions.Logging.ILogger Create(Type t) + public static ILogger Create(Type t) { return LoggerFactory.CreateLogger(t.FullName); } - public static Microsoft.Extensions.Logging.ILogger Create(string category) + public static ILogger Create(string category) { return LoggerFactory.CreateLogger(category); } - public static Microsoft.Extensions.Logging.ILoggerFactory LoggerFactory { get; } + public static ILoggerFactory LoggerFactory { get; } = new MaybeAsyncLocalLoggerFactory(); - private class MaybeAsyncLocalLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory + private class MaybeAsyncLocalLoggerFactory : ILoggerFactory { public void Dispose() { } - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) + public ILogger CreateLogger(string categoryName) { return (AsyncLocalLoggerFactory.Value ?? _loggerFactory) .CreateLogger(categoryName); diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs deleted file mode 100644 index 4ac07425..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLogger.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete] - public class BackendFxToMicrosoftLoggingLogger : ILogger - { - private readonly Microsoft.Extensions.Logging.ILogger _logger; - - public BackendFxToMicrosoftLoggingLogger(Microsoft.Extensions.Logging.ILogger logger) - { - _logger = logger; - } - - public Exception Fatal(Exception exception) - { - _logger.LogCritical(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public void Fatal([StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogCritical(format, args); - } - - public Exception Fatal(Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogCritical(exception, format, args); - return exception; - } - - - - public Exception Error(Exception exception) - { - _logger.LogError(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public void Error([StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogError(format, args); - } - - public Exception Error(Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogError(exception, format, args); - return exception; - } - - - public Exception Warn(Exception exception) - { - _logger.LogWarning(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public void Warn([StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogWarning(format, args); - } - - public Exception Warn(Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogWarning(exception, format, args); - return exception; - } - - - public Exception Info(Exception exception) - { - _logger.LogInformation(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public IDisposable InfoDuration(string activity) - { - return new DurationLogger(s => _logger.LogInformation(s), activity); - } - - public IDisposable InfoDuration(string beginMessage, string endMessage) - { - return new DurationLogger(s => _logger.LogInformation(s), beginMessage, endMessage); - } - - public void Info([StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogInformation(format, args); - } - - public Exception Info(Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogInformation(exception, format, args); - return exception; - } - - public bool IsDebugEnabled() - { - return _logger.IsEnabled(LogLevel.Debug); - } - - - public Exception Debug(Exception exception) - { - _logger.LogDebug(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public IDisposable DebugDuration(string activity) - { - return new DurationLogger(s => _logger.LogDebug(s), activity); - } - - public IDisposable DebugDuration(string beginMessage, string endMessage) - { - return new DurationLogger(s => _logger.LogDebug(s), beginMessage, endMessage); - } - - public void Debug([StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogDebug(format, args); - } - - public Exception Debug(Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogDebug(exception, format, args); - return exception; - } - - public bool IsTraceEnabled() - { - return _logger.IsEnabled(LogLevel.Trace); - } - - - public Exception Trace(Exception exception) - { - _logger.LogTrace(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public IDisposable TraceDuration(string activity) - { - return new DurationLogger(s => _logger.LogTrace(s), activity); - } - - public IDisposable TraceDuration(string beginMessage, string endMessage) - { - return new DurationLogger(s => _logger.LogTrace(s), beginMessage, endMessage); - } - - public void Trace([StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogTrace(format, args); - } - - public Exception Trace(Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - _logger.LogTrace(exception, format, args); - return exception; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs deleted file mode 100644 index 38c076b9..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/BackendFxToMicrosoftLoggingLoggerFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete] - public class BackendFxToMicrosoftLoggingLoggerFactory : ILoggerFactory - { - private readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory; - - public BackendFxToMicrosoftLoggingLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } - - public ILogger Create(string s) - { - return new BackendFxToMicrosoftLoggingLogger(_loggerFactory.CreateLogger(s)); - } - - public ILogger Create(Type t) - { - return new BackendFxToMicrosoftLoggingLogger(_loggerFactory.CreateLogger(t.FullName)); - } - - public ILogger Create() - { - return new BackendFxToMicrosoftLoggingLogger(_loggerFactory.CreateLogger(typeof(T).FullName)); - } - - public void BeginActivity(int activityIndex) - { - - } - - public void Shutdown() - { - - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs deleted file mode 100644 index 8a271411..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLogger.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Diagnostics; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete] - [DebuggerStepThrough] - public class DebugLogger : ILogger - { - private readonly string _type; - - public DebugLogger(string type) - { - _type = type; - } - - public Exception Fatal(Exception exception) - { - PrintToDebug(exception); - return exception; - } - - public void Fatal(string format, params object[] args) - { - PrintToDebug(format, args); - } - - public void Error(string format, params object[] args) - { - PrintToDebug(format, args); - } - - public void Warn(string format, params object[] args) - { - PrintToDebug(format, args); - } - - public IDisposable InfoDuration(string activity) - { - return new DurationLogger(s => System.Diagnostics.Debug.WriteLine(s), activity); - } - - public IDisposable InfoDuration(string beginMessage, string endMessage) - { - return new DurationLogger(s => System.Diagnostics.Debug.WriteLine(s), beginMessage, endMessage); - } - - public void Info(string format, params object[] args) - { - PrintToDebug(format, args); - } - - public bool IsDebugEnabled() - { - return true; - } - - public IDisposable DebugDuration(string activity) - { - return new DurationLogger(s => System.Diagnostics.Debug.WriteLine(s), activity); - } - - public IDisposable DebugDuration(string beginMessage, string endMessage) - { - return new DurationLogger(s => System.Diagnostics.Debug.WriteLine(s), beginMessage, endMessage); - } - - public void Debug(string format, params object[] args) - { - PrintToDebug(format, args); - } - - public bool IsTraceEnabled() - { - return true; - } - - public IDisposable TraceDuration(string activity) - { - return new DurationLogger(s => System.Diagnostics.Debug.WriteLine(s), activity); - } - - public IDisposable TraceDuration(string beginMessage, string endMessage) - { - return new DurationLogger(s => System.Diagnostics.Debug.WriteLine(s), beginMessage, endMessage); - } - - public void Trace(string format, params object[] args) - { - PrintToDebug(format, args); - } - - public Exception Trace(Exception exception, string format, params object[] args) - { - PrintToDebug(exception); - PrintToDebug(format, args); - return exception; - } - - public Exception Trace(Exception exception) - { - PrintToDebug(exception); - return exception; - } - - public Exception Debug(Exception exception, string format, params object[] args) - { - PrintToDebug(format, args); - return exception; - } - - public Exception Debug(Exception exception) - { - PrintToDebug(exception); - return exception; - } - - public Exception Info(Exception exception, string format, params object[] args) - { - PrintToDebug(exception); - PrintToDebug(format, args); - return exception; - } - - public Exception Info(Exception exception) - { - PrintToDebug(exception); - return exception; - } - - public Exception Warn(Exception exception, string format, params object[] args) - { - PrintToDebug(exception); - PrintToDebug(format, args); - return exception; - } - - public Exception Warn(Exception exception) - { - PrintToDebug(exception); - return exception; - } - - public Exception Error(Exception exception, string format, params object[] args) - { - PrintToDebug(exception); - PrintToDebug(format, args); - return exception; - } - - public Exception Error(Exception exception) - { - PrintToDebug(exception); - return exception; - } - - public Exception Fatal(Exception exception, string format, params object[] args) - { - PrintToDebug(exception); - PrintToDebug(format, args); - return exception; - } - - private void PrintToDebug(Exception ex) - { - System.Diagnostics.Debug.WriteLine($"{_type} {ex}"); - } - - private void PrintToDebug(string format, params object[] args) - { - System.Diagnostics.Debug.WriteLine(_type + format, args); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs deleted file mode 100644 index 1b29b0bd..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/DebugLoggerFactory.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Diagnostics; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [DebuggerStepThrough] - [Obsolete] - public class DebugLoggerFactory : ILoggerFactory - { - public ILogger Create(string s) - { - return new DebugLogger(s); - } - - public ILogger Create(Type t) - { - string s = t.FullName; - var indexOf = s?.IndexOf('[') ?? 0; - if (indexOf > 0) - { - s = s?.Substring(0, indexOf); - } - - return Create(s); - } - - public ILogger Create() - { - return Create(typeof(T)); - } - - public void BeginActivity(int activityIndex) - { - Debug.WriteLine($"Beginning activity {activityIndex}"); - } - - public void Shutdown() - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs deleted file mode 100644 index ffe0354f..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/ILogger.cs +++ /dev/null @@ -1,99 +0,0 @@ -// ReSharper disable UnusedMethodReturnValue.Global -using System; -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete] - public interface ILogger - { - #region fatal - - Exception Fatal(Exception exception); - - [StringFormatMethod("format")] - void Fatal(string format, params object[] args); - - [StringFormatMethod("format")] - Exception Fatal(Exception exception, string format, params object[] args); - - #endregion - - #region error - - Exception Error(Exception exception); - - [StringFormatMethod("format")] - void Error(string format, params object[] args); - - [StringFormatMethod("format")] - Exception Error(Exception exception, string format, params object[] args); - - #endregion - - #region warn - - Exception Warn(Exception exception); - - [StringFormatMethod("format")] - void Warn(string format, params object[] args); - - [StringFormatMethod("format")] - Exception Warn(Exception exception, string format, params object[] args); - - #endregion - - #region info - - Exception Info(Exception exception); - - IDisposable InfoDuration(string activity); - - IDisposable InfoDuration(string beginMessage, string endMessage); - - [StringFormatMethod("format")] - void Info(string format, params object[] args); - - [StringFormatMethod("format")] - Exception Info(Exception exception, string format, params object[] args); - - #endregion - - #region debug - - bool IsDebugEnabled(); - - Exception Debug(Exception exception); - - IDisposable DebugDuration(string activity); - - IDisposable DebugDuration(string beginMessage, string endMessage); - - [StringFormatMethod("format")] - void Debug(string format, params object[] args); - - [StringFormatMethod("format")] - Exception Debug(Exception exception, string format, params object[] args); - - #endregion - - #region Trace - - bool IsTraceEnabled(); - - Exception Trace(Exception exception); - - IDisposable TraceDuration(string activity); - - IDisposable TraceDuration(string beginMessage, string endMessage); - - [StringFormatMethod("format")] - void Trace(string format, params object[] args); - - [StringFormatMethod("format")] - Exception Trace(Exception exception, string format, params object[] args); - - #endregion - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs deleted file mode 100644 index 553734b3..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/ILoggerFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using JetBrains.Annotations; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete, PublicAPI] - public interface ILoggerFactory - { - ILogger Create(string s); - ILogger Create(Type t); - ILogger Create(); - - void BeginActivity(int activityIndex); - void Shutdown(); - } -} diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs deleted file mode 100644 index 6c2b4d8a..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/LegacyExceptionLogger.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using Backend.Fx.Exceptions; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete] - public class LegacyExceptionLogger : IExceptionLogger - { - private readonly ILogger _logger; - - public LegacyExceptionLogger(ILogger logger) - { - _logger = logger; - } - - public void LogException(Exception exception) - { - if (exception is ClientException cex) - { - _logger.Warn(cex, "Client Exception"); - } - else - { - _logger.Error(exception, "Server Exception"); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs deleted file mode 100644 index 6d22d982..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/LogManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Threading; -using Microsoft.Extensions.Logging.Abstractions; -// ReSharper disable CheckNamespace - -namespace Backend.Fx.Logging -{ - [Obsolete] - public static class LogManager - { - private static ILoggerFactory _loggerFactory = new BackendFxToMicrosoftLoggingLoggerFactory(new NullLoggerFactory()); - private static int _activityIndex; - - public static void Initialize(ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - } - - public static ILogger Create() - { - return _loggerFactory.Create(typeof(T).FullName); - } - - public static ILogger Create(Type t) - { - return _loggerFactory.Create(t.FullName); - } - - public static ILogger Create(string category) - { - return _loggerFactory.Create(category); - } - - public static void BeginActivity() - { - Interlocked.Increment(ref _activityIndex); - _loggerFactory.BeginActivity(_activityIndex); - } - - public static void Shutdown() - { - _loggerFactory.Shutdown(); - } - } -} diff --git a/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs b/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs deleted file mode 100644 index c1b1bbe4..00000000 --- a/src/abstractions/Backend.Fx/Logging/Obsolete/LoggerExtensions.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; - -// ReSharper disable CheckNamespace -namespace Backend.Fx.Logging -{ - [Obsolete] - public static class LoggerExtensions - { - public static Exception Fatal(this Microsoft.Extensions.Logging.ILogger logger, Exception exception) - { - logger.LogCritical(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public static void Fatal(this Microsoft.Extensions.Logging.ILogger logger, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogCritical(format, args); - } - - public static Exception Fatal(this Microsoft.Extensions.Logging.ILogger logger, Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogCritical(exception, format, args); - return exception; - } - - - - public static Exception Error(this Microsoft.Extensions.Logging.ILogger logger, Exception exception) - { - logger.LogError(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public static void Error(this Microsoft.Extensions.Logging.ILogger logger, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogError(format, args); - } - - public static Exception Error(this Microsoft.Extensions.Logging.ILogger logger, Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogError(exception, format, args); - return exception; - } - - - public static Exception Warn(this Microsoft.Extensions.Logging.ILogger logger, Exception exception) - { - logger.LogWarning(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public static void Warn(this Microsoft.Extensions.Logging.ILogger logger, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogWarning(format, args); - } - - public static Exception Warn(this Microsoft.Extensions.Logging.ILogger logger, Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogWarning(exception, format, args); - return exception; - } - - - public static Exception Info(this Microsoft.Extensions.Logging.ILogger logger, Exception exception) - { - logger.LogInformation(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public static IDisposable InfoDuration(this Microsoft.Extensions.Logging.ILogger logger, string activity) - { - return new DurationLogger(s => logger.LogInformation(s), activity); - } - - public static IDisposable InfoDuration(this Microsoft.Extensions.Logging.ILogger logger, string beginMessage, string endMessage) - { - return new DurationLogger(s => logger.LogInformation(s), beginMessage, endMessage); - } - - public static void Info(this Microsoft.Extensions.Logging.ILogger logger, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogInformation(format, args); - } - - public static Exception Info(this Microsoft.Extensions.Logging.ILogger logger, Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogInformation(exception, format, args); - return exception; - } - - public static bool IsDebugEnabled(this Microsoft.Extensions.Logging.ILogger logger) - { - return logger.IsEnabled(LogLevel.Debug); - } - - - public static Exception Debug(this Microsoft.Extensions.Logging.ILogger logger, Exception exception) - { - logger.LogDebug(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public static IDisposable DebugDuration(this Microsoft.Extensions.Logging.ILogger logger, string activity) - { - return new DurationLogger(s => logger.LogDebug(s), activity); - } - - public static IDisposable DebugDuration(this Microsoft.Extensions.Logging.ILogger logger, string beginMessage, string endMessage) - { - return new DurationLogger(s => logger.LogDebug(s), beginMessage, endMessage); - } - - public static void Debug(this Microsoft.Extensions.Logging.ILogger logger, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogDebug(format, args); - } - - public static Exception Debug(this Microsoft.Extensions.Logging.ILogger logger, Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogDebug(exception, format, args); - return exception; - } - - public static bool IsTraceEnabled(this Microsoft.Extensions.Logging.ILogger logger) - { - return logger.IsEnabled(LogLevel.Trace); - } - - - public static Exception Trace(this Microsoft.Extensions.Logging.ILogger logger, Exception exception) - { - logger.LogTrace(exception, "Exception: {Message}", exception.Message); - return exception; - } - - public static IDisposable TraceDuration(this Microsoft.Extensions.Logging.ILogger logger, string activity) - { - return new DurationLogger(s => logger.LogTrace(s), activity); - } - - public static IDisposable TraceDuration(this Microsoft.Extensions.Logging.ILogger logger, string beginMessage, string endMessage) - { - return new DurationLogger(s => logger.LogTrace(s), beginMessage, endMessage); - } - - public static void Trace(this Microsoft.Extensions.Logging.ILogger logger, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogTrace(format, args); - } - - public static Exception Trace(this Microsoft.Extensions.Logging.ILogger logger, Exception exception, [StructuredMessageTemplate] string format, params object[] args) - { - logger.LogTrace(exception, format, args); - return exception; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs deleted file mode 100644 index 3f0ba432..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplicationInvoker.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - public interface IBackendFxApplicationAsyncInvoker - { - /// The async action to be invoked by the application - /// The acting identity - /// The targeted tenant id - /// The correlation id, when it was continued - Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null); - } - - - public interface IBackendFxApplicationInvoker - { - /// The action to be invoked by the application - /// The acting identity - /// The targeted tenant id - /// The correlation id, when it was continued - void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null); - } - - - public class BackendFxApplicationInvoker : IBackendFxApplicationInvoker, IBackendFxApplicationAsyncInvoker - { - private readonly ICompositionRoot _compositionRoot; - private static readonly ILogger Logger = Log.Create(); - - public BackendFxApplicationInvoker(ICompositionRoot compositionRoot) - { - _compositionRoot = compositionRoot; - } - - - public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) - { - Logger.LogInformation("Invoking synchronous action as {Identity} in {TenantId}", identity, tenantId); - using (IServiceScope serviceScope = BeginScope(identity, tenantId, correlationId)) - { - using (UseDurationLogger(serviceScope)) - { - var operation = serviceScope.ServiceProvider.GetRequiredService(); - try - { - operation.Begin(); - action.Invoke(serviceScope.ServiceProvider); - operation.Complete(); - } - catch - { - operation.Cancel(); - throw; - } - } - } - } - - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) - { - Logger.LogInformation("Invoking asynchronous action as {Identity} in {TenantId}", identity, tenantId); - using (IServiceScope serviceScope = BeginScope(identity, tenantId, correlationId)) - { - using (UseDurationLogger(serviceScope)) - { - var operation = serviceScope.ServiceProvider.GetRequiredService(); - try - { - operation.Begin(); - await awaitableAsyncAction.Invoke(serviceScope.ServiceProvider).ConfigureAwait(false); - operation.Complete(); - } - catch - { - operation.Cancel(); - throw; - } - } - } - } - - - private IServiceScope BeginScope(IIdentity identity, TenantId tenantId, Guid? correlationId) - { - IServiceScope serviceScope = _compositionRoot.BeginScope(); - tenantId = tenantId ?? new TenantId(null); - serviceScope.ServiceProvider.GetRequiredService>().ReplaceCurrent(tenantId); - - identity = identity ?? new AnonymousIdentity(); - serviceScope.ServiceProvider.GetRequiredService>().ReplaceCurrent(identity); - - if (correlationId.HasValue) - { - serviceScope.ServiceProvider.GetRequiredService>().Current.Resume(correlationId.Value); - } - - return serviceScope; - } - - - private static IDisposable UseDurationLogger(IServiceScope serviceScope) - { - IIdentity identity = serviceScope.ServiceProvider.GetRequiredService>().Current; - TenantId tenantId = serviceScope.ServiceProvider.GetRequiredService>().Current; - Correlation correlation = serviceScope.ServiceProvider.GetRequiredService>().Current; - return Logger.LogInformationDuration( - $"Starting scope (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}", - $"Ended scope (correlation [{correlation.Id}]) for {identity.Name} in tenant {(tenantId.HasValue ? tenantId.Value.ToString() : "null")}"); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs deleted file mode 100644 index 47d789d2..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/DomainModule.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Linq; -using System.Reflection; -using System.Security.Principal; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; -using Backend.Fx.Features.Authorization; -using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Logging; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - /// - /// Wires all public services to be injected as scoped instances provided by the array of domain assemblies: - /// - s - /// - s - /// - s - /// - public class DomainModule : IModule - { - private static readonly ILogger Logger = Log.Create(); - private readonly Assembly[] _assemblies; - private readonly string _assembliesForLogging; - - public DomainModule(params Assembly[] assemblies) - { - _assemblies = assemblies; - _assembliesForLogging = string.Join(",", _assemblies.Select(ass => ass.GetName().Name)); - } - - public void Register(ICompositionRoot compositionRoot) - { - RegisterDomainInfrastructureServices(compositionRoot); - - RegisterDomainAndApplicationServices(compositionRoot); - - RegisterPermissiveAuthorization(compositionRoot); - } - - private static void RegisterDomainInfrastructureServices(ICompositionRoot compositionRoot) - { - compositionRoot.Register(ServiceDescriptor.Scoped()); - compositionRoot.Register(ServiceDescriptor.Scoped()); - compositionRoot.Register(ServiceDescriptor.Scoped,CurrentCorrelationHolder>()); - compositionRoot.Register(ServiceDescriptor.Scoped,CurrentIdentityHolder>()); - compositionRoot.Register(ServiceDescriptor.Scoped,CurrentTenantIdHolder>()); - } - - private void RegisterDomainAndApplicationServices(ICompositionRoot container) - { - Logger.LogDebug("Registering domain and application services from {Assemblies}", _assembliesForLogging); - - var serviceDescriptors = _assemblies.GetImplementingTypes(typeof(IDomainService)) - .Concat(_assemblies.GetImplementingTypes(typeof(IApplicationService))) - .SelectMany(type => - type.GetTypeInfo() - .ImplementedInterfaces - .Where(i => typeof(IDomainService) != i - && typeof(IApplicationService) != i - && (i.Namespace != null && i.Namespace.StartsWith("Backend") - || _assemblies.Contains(i.GetTypeInfo().Assembly))) - .Select(service => new ServiceDescriptor(service, type, ServiceLifetime.Scoped))); - - - foreach (var serviceDescriptor in serviceDescriptors) - { - Logger.LogDebug("Registering scoped service {ServiceType} with implementation {ImplementationType}", - serviceDescriptor.ServiceType.Name, - serviceDescriptor.ImplementationType.Name); - container.Register(serviceDescriptor); - } - } - - /// - /// Auto registering an "allow all" authorization for each aggregate root type - /// - private void RegisterPermissiveAuthorization(ICompositionRoot compositionRoot) - { - var aggregateRootTypes = _assemblies.GetImplementingTypes(typeof(AggregateRoot)).ToArray(); - foreach (var aggregateRootType in aggregateRootTypes) - { - compositionRoot.Register( - new ServiceDescriptor( - typeof(IAggregateAuthorization<>).MakeGenericType(aggregateRootType), - typeof(AllowAll<>).MakeGenericType(aggregateRootType), - ServiceLifetime.Singleton)); - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs deleted file mode 100644 index 733d5414..00000000 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/ExceptionLoggingAsyncInvoker.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Security.Principal; -using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Logging; - -namespace Backend.Fx.Patterns.DependencyInjection -{ - public class ExceptionLoggingAsyncInvoker : IBackendFxApplicationAsyncInvoker - { - private readonly IExceptionLogger _exceptionLogger; - private readonly IBackendFxApplicationAsyncInvoker _invoker; - - public ExceptionLoggingAsyncInvoker(IExceptionLogger exceptionLogger, IBackendFxApplicationAsyncInvoker invoker) - { - _exceptionLogger = exceptionLogger; - _invoker = invoker; - } - - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity, TenantId tenantId, Guid? correlationId = null) - { - try - { - await _invoker.InvokeAsync(awaitableAsyncAction, identity, tenantId, correlationId).ConfigureAwait(false); - } - catch (Exception ex) - { - _exceptionLogger.LogException(ex); - throw; - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/Generator.cs b/src/abstractions/Backend.Fx/RandomData/Generator.cs deleted file mode 100644 index 77595ae6..00000000 --- a/src/abstractions/Backend.Fx/RandomData/Generator.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Backend.Fx.RandomData -{ - public abstract class Generator : IEnumerable - { - private readonly HashSet _identicalPreventionMemory = new HashSet(); - - public IEnumerator GetEnumerator() - { - const int maxRetries = 1000; - int retries = 0; - while (true) - { - T next; - int hashCode; - do - { - next = Next(); - hashCode = next.GetHashCode(); - - if (retries++ > maxRetries) - { - throw new Exception($"Tried {maxRetries} times to generate a unique {typeof(T).Name} but did not succeed, aborting now."); - } - } while (_identicalPreventionMemory.Contains(hashCode)); - - _identicalPreventionMemory.Add(hashCode); - yield return next; - } - // ReSharper disable once IteratorNeverReturns - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - protected abstract T Next(); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs b/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs deleted file mode 100644 index b8986a06..00000000 --- a/src/abstractions/Backend.Fx/RandomData/LandLineGenerator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Linq; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public class LandLineGenerator : Generator - { - public static string Generate() - { - return new LandLineGenerator().Take(1).Single(); - } - - protected override string Next() - { - var generated = Numbers.LandLineNetworks.Random(); - while (generated.Length < TestRandom.Instance.Next(8, 11)) generated += Numbers.Ciphers.Random(); - return generated; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/Letters.cs b/src/abstractions/Backend.Fx/RandomData/Letters.cs deleted file mode 100644 index 675ca88d..00000000 --- a/src/abstractions/Backend.Fx/RandomData/Letters.cs +++ /dev/null @@ -1,57 +0,0 @@ -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public static class Letters - { - public static string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - public static string LowerCase = "abcdefghijklmnopqrstuvwxyz"; - - public static string RandomUpperCase(int length) - { - var random = string.Empty; - for (var i = 0; i < length; i++) random += UpperCase.Random(); - return random; - } - - public static string RandomLowerCase(int length) - { - var random = string.Empty; - for (var i = 0; i < length; i++) random += LowerCase.Random(); - return random; - } - - public static string RandomNormalCase(int length) - { - var random = string.Empty; - for (var i = 0; i < length; i++) random += i == 0 ? UpperCase.Random() : LowerCase.Random(); - return random; - } - - public static string RandomPassword(int length = 10, int numberCount = 2, int specialCharCount = 2) - { - const string letters = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"; - const string numbers = "23456789"; - const string specials = "§$%&#+*-<>"; - var password = new char[length]; - - for (var i = 0; i < password.Length; i++) - { - password[i] = letters.Random(); - } - - for (int i = 0; i < numberCount; i++) - { - password[TestRandom.Next(length)] = numbers.Random(); - } - - for (int i = 0; i < specialCharCount; i++) - { - password[TestRandom.Next(length)] = specials.Random(); - } - - return new string(password); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs b/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs deleted file mode 100644 index 4c368fe8..00000000 --- a/src/abstractions/Backend.Fx/RandomData/LinqExtensions.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using Backend.Fx.BuildingBlocks; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public static class LinqExtensions - { - /// - /// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle - /// - /// - /// - /// - public static IEnumerable Shuffle(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - var sourceAsArray = source as T[] ?? source.ToArray(); - - var n = sourceAsArray.Length; - while (n > 1) - { - var k = TestRandom.Instance.Next(n--); - (sourceAsArray[n], sourceAsArray[k]) = (sourceAsArray[k], sourceAsArray[n]); - } - - return sourceAsArray; - } - - public static T Random(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - // ReSharper disable once PossibleMultipleEnumeration - if (TryAsQueryable(source, out var sourceQueryable, out var count)) - { - if (count == 0) - throw new ArgumentException( - $"The enumerable of {typeof(T).Name} does not contain any items, therefore no random item can be returned.", nameof(source)); - - return sourceQueryable.Skip(TestRandom.Next(count - 1)).First(); - } - - // ReSharper disable once PossibleMultipleEnumeration - var sourceArray = source as T[] ?? source.ToArray(); - if (sourceArray.Length == 0) - throw new ArgumentException( - $"The enumerable of {typeof(T).Name} does not contain any items, therefore no random item can be returned.", nameof(source)); - return sourceArray.ElementAt(TestRandom.Next(sourceArray.Length)); - } - - public static T RandomOrDefault(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - // ReSharper disable once PossibleMultipleEnumeration - if (TryAsQueryable(source, out var sourceQueryable, out var count)) - { - if (count == 0) return default; - - return sourceQueryable.Skip(TestRandom.Next(count - 1)).FirstOrDefault(); - } - - // ReSharper disable once PossibleMultipleEnumeration - var sourceArray = source as T[] ?? source.ToArray(); - if (sourceArray.Length == 0) return default; - return sourceArray.ElementAt(TestRandom.Next(sourceArray.Length)); - } - - private static bool TryAsQueryable(this IEnumerable source, out IQueryable outQueryable, out int count) - { - outQueryable = null; - count = 0; - - if (source is IQueryable sourceQueryable) - { - count = sourceQueryable.Count(); - if (count == 0) - { - outQueryable = Array.Empty().AsQueryable(); - return true; - } - - PropertyInfo idProperty = typeof(T).GetProperty(nameof(Identified.Id), BindingFlags.Instance | BindingFlags.Public); - if (idProperty != null) - { - sourceQueryable = sourceQueryable.OrderBy(GetPropertyExpression(idProperty.Name)); - } - - outQueryable = sourceQueryable; - return true; - } - - return false; - } - - private static Expression> GetPropertyExpression(string propertyName) - { - var param = Expression.Parameter(typeof(T), "t"); - Expression conversion = Expression.Convert(Expression.Property(param, propertyName), typeof(object)); - return Expression.Lambda>(conversion, param); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs b/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs deleted file mode 100644 index 9caec52f..00000000 --- a/src/abstractions/Backend.Fx/RandomData/LoremIpsum.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Linq; - -namespace Backend.Fx.RandomData -{ - public class LoremIpsumGenerator : Generator - { - private static readonly string[] Words = { - "lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "proin", "eget", "iaculis", "quam", "pellentesque", "elementum", "gravida", "nulla", - "at", "tincidunt", "donec", "vulputate", "velit", "sapien", "a", "auctor", "justo", "id", "nunc", "et", "consequat", "magna", "in", "blandit", "ut", "eros", - "tempus", "condimentum", "sem", "ac", "feugiat", "tellus", "curabitur", "aliquet", "ultrices", "arcu", "eu", "lacinia", "aliquam", "integer", "non", "venenatis", - "sed", "accumsan", "massa", "nibh", "vestibulum", "nec", "porta", "libero", "vel", "ex", "molestie", "pretium", "dignissim", "ligula", "maximus", "placerat", - "nisl", "felis", "fringilla", "efficitur", "mi", "nam", "vitae", "orci", "suscipit", "porttitor", "leo", "posuere", "sollicitudin", "dictum", "tristique", "dui", - "urna", "quis", "quisque", "semper", "diam", "pulvinar", "erat", "ornare", "maecenas", "euismod", "odio", "tortor", "cursus", "convallis", "enim", "sodales", - "facilisis", "faucibus", "fusce", "scelerisque", "purus", "praesent", "interdum", "turpis", "mauris", "duis", "finibus", "augue", "nullam", "mollis", "lacus", - "egestas", "metus", "mattis", "morbi", "laoreet", "bibendum", "phasellus", "risus", "neque", "volutpat", "lobortis", "malesuada", "sagittis", "rhoncus", "est", - "imperdiet", "aenean", "fermentum", "varius", "vivamus", "suspendisse", "commodo", "luctus", "dapibus", "ullamcorper", "viverra", "congue", "hendrerit", "pharetra", - "tempor", "eleifend", "lectus", "te" - }; - - protected override string Next() - { - return Words[TestRandom.Instance.Next(Words.Length)]; - } - - public static string Generate(int minWords, int maxWords, bool asSentence = true) - { - int wordCount = TestRandom.Next(minWords, maxWords); - string loremIpsumText = string.Join(" ", new LoremIpsumGenerator().Take(wordCount)); - if (asSentence) - { - loremIpsumText += "."; - } - - return loremIpsumText; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs b/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs deleted file mode 100644 index 6088306a..00000000 --- a/src/abstractions/Backend.Fx/RandomData/MobileLineGenerator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Linq; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public class MobileLineGenerator : Generator - { - public static string Generate() - { - return new MobileLineGenerator().First(); - } - - - protected override string Next() - { - var generated = Numbers.MobileNetworks.Random(); - while (generated.Length < TestRandom.Instance.Next(11)) generated += Numbers.Ciphers.Random(); - - return generated; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/Names.cs b/src/abstractions/Backend.Fx/RandomData/Names.cs deleted file mode 100644 index caebebf6..00000000 --- a/src/abstractions/Backend.Fx/RandomData/Names.cs +++ /dev/null @@ -1,5761 +0,0 @@ -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public static class Names - { - public static readonly string[] LegalEntityTypes = - { - "GmbH", - "AG", - "KG", - "OHG", - "Ltd.", - "SA", - "GmbH & Co KG", - "" - }; - - public static readonly string[] CompanyTypes = - { - "Schuldnerberatung", - "Autohaus", - "Apotheke", - "IT Dienstleistungen", - "Tischlerei", - "Schreinerei", - "Hausmeister", - "Bauunternehmen" - }; - - public static readonly string[] Streets = - { - "Abboweg", - "Abteistraße", - "Achatweg", - "Achenbach Straße", - "Achterstraße", - "Ackerstraße", - "Ackerweg", - "Adalbertstraße", - "Adalmundstraße", - "Adamsweg", - "Adelenstraße", - "Adelhartweg", - "Adickesstraße", - "Adlerstraße", - "Adlerweg", - "Admiralstraße", - "Agavenstraße", - "Ahornweg", - "Ährenweg", - "Akazienstraße", - "Akenschockweg", - "Albert-Schweitzer-Weg", - "Albertstraße", - "Albingerstraße", - "Albrecht-Dürer-Straße", - "Albrechtstraße", - "Albroweg", - "Aldinghofer Straße", - "Alekestraße", - "Alemannenstraße", - "Alexanderstraße", - "Alfred-Berndsen-Weg", - "Alfred-Lange-Straße", - "Alfred-Nobel-Straße", - "Alfred-Reinoldsmann-Weg", - "Alfred-Trappen-Straße", - "Allensteiner Straße", - "Allerstraße", - "Alpenheide", - "Alpenrosenstraße", - "Alsenstraße", - "Alte Benninghofer Straße", - "Alte Dortmunder Straße", - "Alte Ellinghauser Straße", - "Alte Märsch", - "Alte Straße", - "Alte Teichstraße", - "Altenderner Straße", - "Altenhennestraße", - "Altenrathstraße", - "Alter Burgwall", - "Alter Erlenweg", - "Alter Heideweg", - "Alter Hellweg", - "Alter Mühlenweg", - "Alter Postweg", - "Altfriedstraße", - "Althoffstraße", - "Althüserstraße", - "Altmengeder Straße", - "Altonaer Straße", - "Altumstraße", - "Altwickeder Hellweg", - "Am Alten Forsthaus", - "Am Alten Garten", - "Am Alten Sportplatz", - "Am Amtshaus", - "Am Apelstück", - "Am Bahnhof Somborn", - "Am Bahnhof Tierpark", - "Am Ballroth", - "Am Beilstück", - "Am Beisenkamp", - "Am Beisenufer", - "Am Bentenskamp", - "Am Bergfeld", - "Am Bertholdshof", - "Am Birkenbaum", - "Am Bönner", - "Am Brandhof", - "Am Brennbusch", - "Am Bruchheck", - "Am Burhag", - "Am Büter", - "Am Dieckhof", - "Am Dimberg", - "Am Dingelkamp", - "Am Dorfplatz", - "Am Dreisch", - "Am Düppersholl", - "Am Eiserfeld", - "Am Ellberg", - "Am Erlenbruch", - "Am Feldbrand", - "Am Flachsteich", - "Am Flinsbach", - "Am Franzosensiepen", - "Am Freck", - "Am Funkturm", - "Am Gardenkamp", - "Am Geenseel", - "Am Gemeindehaus", - "Am Gerrenbach", - "Am Gottesacker", - "Am Grenzgraben", - "Am Grimmelsiepen", - "Am Großen Stück", - "Am Grünen Ufer", - "Am Gulloh", - "Am Hagedorn", - "Am Hahnenholz", - "Am Hang", - "Am Hartweg", - "Am Haselhof", - "Am Hasenberg", - "Am Häugter", - "Am Hausacker", - "Am Hedreisch", - "Am Heedbrink", - "Am Heidenpost", - "Am Heiderand", - "Am Heiligen Busch", - "Am Heisterbach", - "Am Heller", - "Am Hemesod", - "Am Heuningsfeld", - "Am Hilgenbaum", - "Am Hittenauwer", - "Am Hofstück", - "Am Hohen Teich", - "Am Höhweg", - "Am Hölzgen", - "Am Holzgraben", - "Am Hombruchsfeld", - "Am Hombruchskamp", - "Am Hülsenberg", - "Am Jungbrunnen", - "Am Kaiserhain", - "Am Kämpen", - "Am Kapellenufer", - "Am Kattenbrauck", - "Am Kirchenfeld", - "Am Klusenberg", - "Am Knapp", - "Am Knappenberg", - "Am Knie", - "Am Kohlrücken", - "Am Kraftwerk", - "Am Krähenort", - "Am Kramberg", - "Am Krankenhaus", - "Am Kreuzloh", - "Am Kucksberg", - "Am Kuhlenweg", - "Am Lindeneck", - "Am Lohbach", - "Am Lohbachhang", - "Am Marksbach", - "Am Mergelbruch", - "Am Mühlenberg", - "Am Mühlenwinkel", - "Am Münzenkamp", - "Am Nocken", - "Am Oelpfad", - "Am Oespeler Dorney", - "Am Oestricher Bruch", - "Am Oldendieck", - "Am Ossenbrink", - "Am Ostbrink", - "Am Osterbruch", - "Am Ostheck", - "Am Ostpark", - "Am Paß", - "Am Pastorenwäldchen", - "Am Petersheck", - "Am Pfauenufer", - "Am Quartus", - "Am Quellweg", - "Am Rabensmorgen", - "Am Rebstock", - "Am Remberg", - "Am Rhader Holz", - "Am Richterbusch", - "Am Rode", - "Am Roggenfeld", - "Am Rombergpark", - "Am Rondell", - "Am Rosenplätzchen", - "Am Roten Haus", - "Am Rüggen", - "Am Rundbogen", - "Am Schallacker", - "Am Schleppweg", - "Am Schlosspark", - "Am Schmandsack", - "Am Schmechtingsbach", - "Am Schoopställer", - "Am Schultenhof", - "Am Segen", - "Am Siepenhohl", - "Am Sodkamp", - "Am Sommerberg", - "Am Sonnenblick", - "Am Spörkel", - "Am Stift", - "Am Stövenhof", - "Am Stucken", - "Am Stuckenrodt", - "Am Sturmwald", - "Am Südwestfriedhof", - "Am Sümpelmannhof", - "Am Sumpf", - "Am Surck", - "Am Talenberg", - "Am Täufling", - "Am Thurmacker", - "Am Timmerbed", - "Am Tremoniapark", - "Am Trienensiepen", - "Am Trissel", - "Am Truxhof", - "Am Uhlenhorst", - "Am Voerstenhof", - "Am Volksgarten", - "Am Vosshohl", - "Am Walde", - "Am Wasserfall", - "Am Wemphof", - "Am Westfalenstadion", - "Am Westheck", - "Am Wiebusch", - "Am Wildgatter", - "Am Winkelsweg", - "Am Winterberg", - "Am Wittfeld", - "Am Zechenhof", - "Am Zehnthof", - "Am Zippen", - "Am Zitter", - "Amalienstraße", - "Ammerbaumweg", - "Ammerstraße", - "Amselstraße", - "Amselweg", - "Amsterdamer Weg", - "Amtsberg", - "Amtsstraße", - "An den Birken", - "An den Bräukenhöfen", - "An den Gräften", - "An den Rühlen", - "An den Stahlhäusern", - "An den Teichen", - "An der Buschmühle", - "An der Deipenbeck", - "An der Eiche", - "An der Fillkuhle", - "An der Gosekuhle", - "An der Goymark", - "An der Halde", - "An der Herz-Jesu-Kirche", - "An der Hordelwiese", - "An der Hühnerhecke", - "An der Hundewiese", - "An der Kemna", - "An der Margarethenkapelle", - "An der Palmweide", - "An der Panne", - "An der Scheune", - "An der schlanken Mathilde", - "An der Stipskuhle", - "An der Teithe", - "An der Wasserburg", - "An der Weustmühle", - "An der Windhake", - "An der Witwe", - "An Steffens Wiesche", - "Andreasstraße", - "Anemonenstraße", - "Anna-Siemsen-Straße", - "Annenstraße", - "Anneweihstraße", - "Apelank", - "Apelbachstraße", - "Apfelbaumweg", - "Apfeldweg", - "Aplerbecker Bahnhofstraße", - "Aplerbecker Schulstraße", - "Aplerbecker Straße", - "Aplerbecker Wald", - "Aplerbecker-Mark-Straße", - "Apolloweg", - "Apotheker-Eick-Straße", - "Appelbecke", - "Archenbecke", - "Arcostraße", - "Ardeystraße", - "Arenbergstraße", - "Arent-Rupe-Straße", - "Arminiusstraße", - "Arndtstraße", - "Arneckestraße", - "Arnold-Böcklin-Straße", - "Arnoldstraße", - "Arthur-Beringer-Straße", - "Artusweg", - "Aschebrockstraße", - "Aschenputtelweg", - "Ascloonweg", - "Aspeystraße", - "Asselburgstraße", - "Asselner Hellweg", - "Asselner Straße", - "Asternstraße", - "Astrid-Lindgren-Ring", - "Athener Weg", - "Attinghausenstraße", - "Auf dem Berge", - "Auf dem Bleck", - "Auf dem Blick", - "Auf dem Brand", - "Auf dem Brauck", - "Auf dem Brink", - "Auf dem Brümmer", - "Auf dem Feldgraben", - "Auf dem Gummel", - "Auf dem Heiken", - "Auf dem Hohwart", - "Auf dem Hövellande", - "Auf dem Kellerkamp", - "Auf dem Klai", - "Auf dem Kranz", - "Auf dem Mühlenhof", - "Auf dem Mühlenhofe", - "Auf dem Rott", - "Auf dem Schloßacker", - "Auf dem Schnee", - "Auf dem Sonneborn", - "Auf dem Springstück", - "Auf dem Steine", - "Auf dem Toren", - "Auf dem Wodeacker", - "Auf der Bicke", - "Auf der Bokkenbredde", - "Auf der Bredde", - "Auf der Burg", - "Auf der Goldbreite", - "Auf der hohen Fuhr", - "Auf der Horte", - "Auf der Kiste", - "Auf der Kluse", - "Auf der Knappule", - "Auf der Kuhweide", - "Auf der Linnert", - "Auf der Mühle", - "Auf der Wenge", - "Auf der Wieck", - "Auf der Wittbräucke", - "Aufenangerstraße", - "Auf'm Brautschatz", - "Auf'm Lehmbrink", - "Auf'm Plätzchen", - "Augustastraße", - "Auguste-Prigge-Straße", - "August-Schmidt-Straße", - "Aussigring", - "Autobahn Downhill", - "Autobahn Downhill Bittermark", - "Avermannstraße", - "Azaleenweg", - "B 1", - "B 236", - "B 54", - "Baackweg", - "Baaderweg", - "Baakskamp", - "Bachstraße", - "Backenköhlerweg", - "Bäckerstraße", - "Badische Straße", - "Badweg", - "Baedekerstraße", - "Baerweg", - "Baeumerstraße", - "Bahnebredde", - "Bahnhangstraße", - "Bahnhofstraße", - "Balkenstraße", - "Balsterstraße", - "Baltischer Weg", - "Banatstraße", - "Bandelstraße", - "Bannweg", - "Baptistaweg", - "Barbarastraße", - "Barbergestraße", - "Bärenbruch", - "Bärenkamp", - "Barichstraße", - "Baroper Bahnhofstraße", - "Baroper Bergstraße", - "Baroper Heidestraße", - "Baroper Kirchweg", - "Baroper Landwehr", - "Baroper Marktplatz", - "Baroper Schulstraße", - "Baroper Straße", - "Barsinghausenstraße", - "Bartelsstraße", - "Bartgeierweg", - "Barthstraße", - "Baseler Weg", - "Bassestraße", - "Baststraße", - "Batenburgstraße", - "Batheyweg", - "Bauerholz", - "Bauernkamp", - "Bauerstraße", - "Baukamp", - "Bauksheide", - "Baumertweg", - "Baumhaselweg", - "Baumhofweg", - "Baumwirtsweg", - "Baurat-Marx-Allee", - "Bayrische Straße", - "Bebelstraße", - "Beckleystraße", - "Beckstedtweg", - "Bedarfszufahrt DELTA", - "Bedastraße", - "Beerenweg", - "Beethovenstraße", - "Begonienstraße", - "Beguinenstraße", - "Behringstraße", - "Beisemannskamp", - "Beisenherzstraße", - "Beisterweg", - "Beitterstraße", - "Bekassinenweg", - "Belgischer Weg", - "Belle-Alliance-Straße", - "Benediktinerstraße", - "Benninghofer Feld", - "Benninghofer Heide", - "Benninghofer Mark", - "Benninghofer Straße", - "Benno-Jacob-Straße", - "Benno-Niggemeyer-Weg", - "Bennostraße", - "Bensbergweg", - "Benthausweg", - "Beratgerstraße", - "Berberisweg", - "Berchumweg", - "Berenbredde", - "Bergelchen-Ort", - "Bergerhofweg", - "Berghofer Schulstraße", - "Berghofer Straße", - "Bergiusstraße", - "Bergmannsknapp", - "Bergmannstraße", - "Bergmeisterstraße", - "Bergparte", - "Bergstraße", - "Berliner Straße", - "Bermesdickerstraße", - "Berner Weg", - "Bernhard-Letterhaus-Straße", - "Bernsteinweg", - "Bertastraße", - "Bertha-von-Suttner-Straße", - "Bertholdstraße", - "Betenstraße", - "Beukenbergstraße", - "Beurhausstraße", - "Beuthstraße", - "Beverstraße", - "Beylingstraße", - "Biberweg", - "Bielefelder Straße", - "Bienenstraße", - "Bierhoffstraße", - "Bierkamp", - "Bifangweg", - "Biggestieg", - "Binnerstraße", - "Binsengarten", - "Birkenufer", - "Birkenweg", - "Birnbaumweg", - "Bischofsgasse", - "Bismarckstraße", - "Bissenkamp", - "Bitterfeldstraße", - "Bittermarkstraße", - "Bladenhorstweg", - "Blankensteiner Straße", - "Blaumenacker", - "Blaurakenweg", - "Blausielweg", - "Bleichmärsch", - "Bleichstraße", - "Blenkerweg", - "Blickstraße", - "Blitzstraße", - "Blücherstraße", - "Blumenkamp", - "Blumenstraße", - "Blütenweg", - "Bobelohweg", - "Boberstraße", - "Bockenfelder Straße", - "Bockensiepen", - "Böcklerstraße", - "Böckmannstraße", - "Bockumweg", - "Bodeckstraße", - "Bodelschwingher Berg", - "Bodelschwingher Straße", - "Bodenweg", - "Boeselagerstraße", - "Bogenstraße", - "Boickholtstraße", - "Bojerstraße", - "Bollwerkstraße", - "Bolmkeblick", - "Bolmkeweg", - "Bomacker", - "Bömckestraße", - "Bömelburgstraße", - "Bomheuerweg", - "Bonhoefferstraße", - "Bonifatiusstraße", - "Bönschstraße", - "Bookenburgweg", - "Börgerhoffweg", - "Borgmannsweg", - "Borkenstraße", - "Bornstraße", - "Borsigplatz", - "Borsigstraße", - "Borussiastraße", - "Boschstraße", - "Botdingweg", - "Bothestraße", - "Boverfeld", - "Bovermannstraße", - "Bövinghauser Dorfstraße", - "Bövinghauser Straße", - "Bozener Straße", - "Brabänderweg", - "Brabeckweg", - "Brache", - "Brackeler Hellweg", - "Brackeler Linde", - "Brackeler Straße", - "Brahmsstraße", - "Brambauerstraße", - "Bramey", - "Bramkampsweg", - "Brandbruchstraße", - "Brandenburger Straße", - "Brandeniusstraße", - "Brandheide", - "Brandisstraße", - "Brandschachtstraße", - "Brassertstraße", - "Brauhausstraße", - "Braunsbergweg", - "Braunschweiger Straße", - "Braunstraße", - "Brechtener Heide", - "Brechtener Straße", - "Brechtstraße", - "Breddeweg", - "Bredenbeckstraße", - "Bredowstraße", - "Brehtonweg", - "Breierspfad", - "Breisenbachstraße", - "Breitscheidstraße", - "Brembuschweg", - "Bremer Straße", - "Bremerkamp", - "Bremmenstraße", - "Bremsstraße", - "Brennaborstraße", - "Brennerstraße", - "Brentweg", - "Breslaustraße", - "Briefsweg", - "Brietenstraße", - "Brinkhoffstraße", - "Brinkmannstraße", - "Brinksitzerweg", - "Britischer Weg", - "Brixener Straße", - "Brockenscheidter Weg", - "Brockhausweg", - "Brögerstraße", - "Bromberger Straße", - "Bronckhorststraße", - "Bronnerstraße", - "Brücherhofstraße", - "Brüchtenweg", - "Brückstraße", - "Brüderweg", - "Brüggenfeld", - "Brügmannstraße", - "Brühlweg", - "Bruktererstraße", - "Brunebeckweg", - "Brunnenstraße", - "Brünninghauser Straße", - "Brunostraße", - "Brunshollweg", - "Brütingsweg", - "Buchenacker", - "Buchenlandstraße", - "Buchenstraße", - "Buchsbaumweg", - "Büchtersweg", - "Buddenacker", - "Buddenhofweg", - "Buddinkstraße", - "Büderichweg", - "Buggestraße", - "Büllestraße", - "Bülowstraße", - "Bummelberg", - "Büngerstraße", - "Bünnerhelfstraße", - "Bunsen-Kirchhoff-Straße", - "Buntspechtweg", - "Bunzlaustraße", - "Bürenstraße", - "Bürgerstraße", - "Burggrafenstraße", - "Burgheisterkamp", - "Burgholzstraße", - "Burgring", - "Burgtor", - "Burgunderstraße", - "Burgwall", - "Burgweg", - "Burrichterweg", - "Buschei", - "Büscher Straße", - "Buschgarten", - "Buschstraße", - "Busenbergstraße", - "Bussardstraße", - "Bussardweg", - "Butenandtstraße", - "Büttelweg", - "Büttnerstraße", - "Buttweg", - "Butzstraße", - "Caesariusstraße", - "Calvinstraße", - "Canarisstraße", - "Carl-Duisberg-Straße", - "Carl-Holtschneider-Straße", - "Carl-von-Ossietzky-Straße", - "Castellestraße", - "Castroper Straße", - "Chattenstraße", - "Chaussee", - "Chemnitzer Straße", - "Cheruskerstraße", - "Christianstraße", - "Cimbernstraße", - "Clarenberg", - "Clarissenstraße", - "Clausthaler Straße", - "Clematisweg", - "Clemens-Veltum-Straße", - "Cobbenheimweg", - "Cobbingstraße", - "Colonatenweg", - "Conradusstraße", - "Corvarastraße", - "Cottastraße", - "Crachtstraße", - "Crispinstraße", - "Crudewagenweg", - "Cunigundeweg", - "Dachstraße", - "Dachsweg", - "Dahlienstraße", - "Dahmsfeldstraße", - "Damaschkestraße", - "Dammstraße", - "Danckwardtstraße", - "Danewerkstraße", - "Dänischer Weg", - "Danzerweg", - "Danziger Straße", - "Darbovenstraße", - "Dasselstraße", - "Daßloh", - "Däumlingsweg", - "Davidisstraße", - "Dechenstraße", - "Deggingstraße", - "Deilmannstraße", - "Deininghauser Straße", - "Deintelleweg", - "Deipenbeckstraße", - "Deitertstraße", - "Dellwiger Feld", - "Dellwiger Straße", - "Denickestraße", - "Denkmalstraße", - "Dennewitzstraße", - "Derner Bahnstraße", - "Derner Kippshof", - "Derner Straße", - "Dessauerstraße", - "Dethmar-Mülher-Straße", - "Detmarstraße", - "Detmolder Straße", - "Deusener Straße", - "Deuser Wiesen", - "Deutsche Straße", - "Deutsch-Luxemburger-Straße", - "Deutzer Weg", - "Diakon-Koch-Weg", - "Dickebankstraße", - "Dickhofskamp", - "Dickrath", - "Dieckmannweg", - "Diedenhofener Straße", - "Diedrichstraße", - "Diekmüllerbaum", - "Dielstraße", - "Diemelstraße", - "Diepenbrockstraße", - "Dietrich-Schröder-Straße", - "Dingweg", - "Dinnendahlweg", - "Dionysiusstraße", - "Dirschauer Weg", - "Disselhoffstraße", - "Distelkamp", - "Distelweg", - "Ditzschweg", - "Döbbengasse", - "Döbelner Straße", - "Doertweg", - "Dohlenstraße", - "Dollersweg", - "Domänenstraße", - "Dompfaffweg", - "Donarstraße", - "Donnerstraße", - "Dönnstraße", - "Dopheidestraße", - "Dorfgrenze", - "Dorfstraße", - "Dörhoffstraße", - "Dorhofweg", - "Dornackerstraße", - "Dornbruchstraße", - "Dorneburgweg", - "Dörnenstraße", - "Dorneystraße", - "Dornröschenweg", - "Dornstraße", - "Dorotheenstraße", - "Dorstelmannstraße", - "Dorstfelder Allee", - "Dorstfelder Hellweg", - "Dortmunder Feld", - "Dortmunder Straße", - "Dortustraße", - "Dörwerstraße", - "Dransfeldstraße", - "Drechslerweg", - "Drehbrückenstraße", - "Dreherstraße", - "Dreihüttenstraße", - "Dresdener Straße", - "Driverweg", - "Droote", - "Drosselweg", - "Droste-Hülshoff-Straße", - "Droste-zu-Vischering-Siedlung", - "Drüwelhof", - "Dubliner Weg", - "Dückerstraße", - "Dudenstraße", - "Dudweilerstraße", - "Dümpelstraße", - "Dünnebecke", - "Durchstraße", - "Dürener Dorfstraße", - "Dürener Straße", - "Durlachstraße", - "Düsseldorfer Straße", - "Düsterstraße", - "Düttelstraße", - "Düwelssiepen", - "Dyckhoffsweg", - "Ebbendorfstraße", - "Ebbinghausstraße", - "Eberstraße", - "Echeloh", - "Echtermannsweg", - "Eckardtstraße", - "Eckei", - "Eckenerstraße", - "Eddaweg", - "Edelrautenweg", - "Edelrosenstraße", - "Edelweißstraße", - "Ederstraße", - "Edingkweg", - "Eduard-Kleine-Straße", - "Egerstraße", - "Egge Wiede", - "Egilmarstraße", - "Ehmsenstraße", - "Ehrlichstraße", - "Eibenweg", - "Eichelhäherweg", - "Eichendorffstraße", - "Eichenmarkweg", - "Eichenstraße", - "Eichhoffstraße", - "Eichholzstraße", - "Eichhörnchenweg", - "Eichkamp", - "Eichlinghofer Straße", - "Eichsfeld", - "Eichwaldstraße", - "Eickensiepen", - "Eiderstraße", - "Eierkampstraße", - "Eifelweg", - "Eigenheimweg", - "Einigkeit", - "Einsteinstraße", - "Eintrachtstraße", - "Eisenacher Straße", - "Eisenhüttenweg", - "Eisenmarkt", - "Eisenstraße", - "Eisvogelweg", - "Elberskamp", - "Elbestraße", - "Elbinger Straße", - "Elchweg", - "Eleonorestraße", - "Elf Kreuzen", - "Elfenweg", - "Elisabethstraße", - "Elisabeth-Wilms-Weg", - "Ellinghauser Straße", - "Elsa-Brändstöm-Straße", - "Elsa-Brändström-Straße", - "Elsbeerenweg", - "Elsborn", - "Elverdinckweg", - "Elverfeldstraße", - "Emil-Figge-Straße", - "Emkraft", - "Emscherallee", - "Emscherbrücke", - "Emscherdelle", - "Emschergasse", - "Emscherpfad", - "Emscherpromenade", - "Emscherstraße", - "Emschertalstraße", - "Emsighofstraße", - "Emsinghofstraße", - "Enekingstraße", - "Engelbertstraße", - "Enid-Blyton-Weg", - "Enkweg", - "Ennepeweg", - "Enscheder Straße", - "Enstestraße", - "Entenpoth", - "Enzianweg", - "Erbenstraße", - "Erbstollen", - "Erdelhofstraße", - "Erdestraße", - "Erdmannstraße", - "Erenbertstraße", - "Erfurter Straße", - "Erich-Kästner-Ring", - "Erikastraße", - "Erikaweg", - "Erlemannsweg", - "Erlenbachstraße", - "Erlenkamp", - "Ermbrachtstraße", - "Ermlinghofer Straße", - "Ernestineweg", - "Ernst-Mehlich-Straße", - "Ernst-Wiechert-Straße", - "Ernteweg", - "Erpinghofstraße", - "Ertmarweg", - "Erwinstraße", - "Erzbergerstraße", - "Eschenstraße", - "Eschenwaldstraße", - "Espenstraße", - "Esselstieg", - "Essener Straße", - "Essingloh", - "Eugen-Richter-Straße", - "Eulenstraße", - "Europaplatz", - "Evastraße", - "Evertstraße", - "Evinger Berg", - "Evinger Parkweg", - "Evinger Platz", - "Evinger Straße", - "Ewald-Görshop-Straße", - "Ewald-Sprave-Weg", - "Ewaldstraße", - "Externberg", - "Exzellenzstraße", - "Ezzestraße", - "Faberstraße", - "Fächerstraße", - "Falkenstraße", - "Falläckerweg", - "Fallgatter", - "Falterweg", - "Färberstraße", - "Fasanenweg", - "Faßstraße", - "Feilerweg", - "Feineisenstraße", - "Feldahornweg", - "Feldbachacker", - "Feldbank", - "Feldblumenweg", - "Feldbrandweg", - "Feldchenstraße", - "Feldgarten", - "Feldhauskamp", - "Feldherrnstraße", - "Feldhofstraße", - "Feldmark", - "Feldstraße", - "Felheuerstraße", - "Felicitasstraße", - "Felkestraße", - "Fellhammerstraße", - "Feltmannweg", - "Ferdinandstraße", - "Fernstraße", - "Feuerbachweg", - "Feuervogelweg", - "Feuerwehr Trail", - "Fichtestraße", - "Fildeweg", - "Fine Frau", - "Finkenweg", - "Finnenbahn", - "Fischbecke", - "Flachsweg", - "Flamingoweg", - "Flaspoete", - "Flautweg", - "Flavusstraße", - "Fleckweg", - "Flegelstraße", - "Flemerskamp", - "Flensburger Straße", - "Fliederstraße", - "Flimmweg", - "Floraweg", - "Florenzer Weg", - "Florianstraße", - "Flotowstraße", - "Flözweg", - "Flügelstraße", - "Flughafenstraße", - "Flugplatz", - "Flurpeute", - "Flurstraße", - "Föbusweg", - "Fohlenkampstraße", - "Föhrenstraße", - "Försterstraße", - "Forsthausstraße", - "Frankenstraße", - "Frankfurter Straße", - "Fränkischer Friedhof", - "Franz-Hitze-Straße", - "Franziskanerstraße", - "Franziusstraße", - "Franz-Liszt-Straße", - "Französischer Weg", - "Franz-Schlüter-Straße", - "Freiastraße", - "Freiberger Weg", - "Freibergstraße", - "Freie Scholle", - "Freie-Vogel-Straße", - "Freigrafenweg", - "Freiherr-vom-Stein-Platz", - "Freihofstraße", - "Freiligrathstraße", - "Freistuhl", - "Freizeitstraße", - "Frendsdorfstraße", - "Frenkingstraße", - "Fresienstraße", - "Fridtjof-Nansen-Straße", - "Friedensplatz", - "Friedenstraße", - "Friedhof", - "Friedhofsweg", - "Friedlandweg", - "Friedrich-Ebert-Platz", - "Friedrich-Ebert-Straße", - "Friedrich-Engels-Straße", - "Friedrich-Hausemann-Allee", - "Friedrich-Henkel-Weg", - "Friedrich-Hölscher-Straße", - "Friedrich-Kohn-Straße", - "Friedrich-Loose-Straße", - "Friedrich-Menze-Weg", - "Friedrich-Naumann-Straße", - "Friedrichruher Straße", - "Friedrich-Schröder-Straße", - "Friedrichshagen", - "Friedrichstraße", - "Friedrich-Uhde-Straße", - "Friedrich-Wilhelm-Straße", - "Friedrich-Wöhler-Weg", - "Friegstraße", - "Frielinghausweg", - "Friesendorfstraße", - "Friesenstraße", - "Frischaufweg", - "Frische Luft", - "Fritz-Funke-Straße", - "Fritz-Kahl-Straße", - "Fritz-Menze-Straße", - "Fritz-Reuter-Straße", - "Fritz-Romann-Weg", - "Fröbelweg", - "Frohenort", - "Frohlinder Straße", - "Frohnauer Weg", - "Fronbotenweg", - "Fröndenbergstraße", - "Froschlake", - "Froschloch", - "Fruchtweg", - "Fuchshöhle", - "Fuchsweg", - "Fühlestraße", - "Fuhmannstraße", - "Fuhrmannstraße", - "Fuldastraße", - "Füllort", - "Fündlingsweg", - "Fürstenbergweg", - "Fürst-Hardenberg-Allee", - "Füssmannstraße", - "Gabelinckstraße", - "Gabelsbergerstraße", - "Gabelstraße", - "Gablonzstraße", - "Gahmener Straße", - "Galenstraße", - "Galmeiweg", - "Galoppstraße", - "Gänsemarkt", - "Gansmannshof", - "Gantenhals", - "Garbenweg", - "Gartenstraße", - "Gartenweg", - "Gasenbergstraße", - "Gastkamp", - "Gaudingstraße", - "Gäuseland", - "Gaußstraße", - "Gebrüder-Grimm-Straße", - "Gecks Heide", - "Gedingeweg", - "Gehrenstraße", - "Geismerg", - "Geitlingsweg", - "Geleitstraße", - "Gemeinschaftsweg", - "Genter Weg", - "Gentzweg", - "Gerader Weg", - "Geranienstraße", - "Gerberstraße", - "Gerdesweg", - "Gerhard-Hohendahl-Straße", - "Gerhart-Hauptmann-Straße", - "Gerichtsstraße", - "Gerlachweg", - "Gerlindstraße", - "Germaniastraße", - "Gernotstraße", - "Gerockstraße", - "Gersdorffstraße", - "Gerstenstraße", - "Gertrudstraße", - "Gerwinweg", - "Geschwister-Scholl-Straße", - "Gesenhofstraße", - "Geßlerstraße", - "Gevelsbergstraße", - "Gewerbeparkstraße", - "Gewerbeweg", - "Gibbenhey", - "Giesenberg", - "Gießereistraße", - "Giesweg", - "Gildenstraße", - "Ginsterstraße", - "Ginsterweg", - "Girondelle", - "Giselherstraße", - "Gitschiner Straße", - "Gladiolenstraße", - "Gleichheitsstraße", - "Gleiwitzsstraße", - "Gleiwitzstraße", - "Glennestieg", - "Glimmerstraße", - "Glockengießerweg", - "Glockenwiese", - "Glörstraße", - "Glückaufsegenstraße", - "Glückaufstraße", - "Gnadenort", - "Gneisenaustraße", - "Gnesener Weg", - "Godefriedstraße", - "Godekinstraße", - "Goebenstraße", - "Goerdelerstraße", - "Goethestraße", - "Goldammerweg", - "Goldschmiedingweg", - "Golfplatz Wurzel Trail", - "Göllenkamp", - "Görresstraße", - "Gosestraße", - "Goslarstraße", - "Gössingstraße", - "Goswinweg", - "Gotenstraße", - "Gottesbergstraße", - "Gotthelfstraße", - "Gottlieb-Levermann-Straße", - "Gottscheestraße", - "Goyenfeld", - "Goystraße", - "Grabbeplatz", - "Grabbestraße", - "Gradigenweg", - "Graebnerstraße", - "Grafenhof", - "Graffweg", - "Graf-Haeseler-Straße", - "Graf-Konrad-Straße", - "Grasenkamp", - "Grasmückenweg", - "Gratzstraße", - "Graudenzer Straße", - "Grävingholzstraße", - "Gregorstraße", - "Grenzweg", - "Gretelweg", - "Greveler Straße", - "Grevendicks Feld", - "Grevenhecke", - "Griebweg", - "Griechischer Weg", - "Grimbergsweg", - "Grimmeweg", - "Grisarstraße", - "Grollmannsweg", - "Gronaustraße", - "Gröningweg", - "Groppenbrucher Straße", - "Gröpperheide", - "Großbeerenstraße", - "Große Heimstraße", - "Große Riedbruchstraße", - "Großholthauser Straße", - "Grotenbachstraße", - "Grotenkamp", - "Grotestraße", - "Grothusweg", - "Grubenweg", - "Gruelsiepenstraße", - "Grüggelsort", - "Grummetweg", - "Grünbergstraße", - "Grundstraße", - "Grüne Straße", - "Grüner Bogen", - "Grüner Weg", - "Grunewald", - "Grüningsweg", - "Grüntalstraße", - "Gruwellstraße", - "Gudrunstraße", - "Gugelweg", - "Güldene Eiche", - "Gundlachstraße", - "Güntherstraße", - "Gurlittstraße", - "Gürtlerstraße", - "Gustav-Korthen-Allee", - "Gustav-Schade-Weg", - "Gustavstraße", - "Gutenbergstraße", - "Gut-Heil-Straße", - "Haardtstraße", - "Haarstrang", - "Haberkamps Vöhde", - "Haberlandstraße", - "Habichtstraße", - "Habichtweg", - "Hacheneyer Kirchweg", - "Hacheneyer Straße", - "Hackländerplatz", - "Hadubrandstraße", - "Haenischstraße", - "Haferfeldstraße", - "Hafer-Vöhde", - "Hafnerstraße", - "Hagebuttenweg", - "Hagenauweg", - "Hagener Straße", - "Hahnenmühlenweg", - "Hainallee", - "Hainbuchenweg", - "Hakenstraße", - "Haldenstraße", - "Halfmannstraße", - "Hallerey", - "Hallermannstraße", - "Hallesche Straße", - "Halmweg", - "Hamburger Straße", - "Hamelmannstraße", - "Hamey", - "Hammer Straße", - "Hampittelknapp", - "Hamsterweg", - "Händelstraße", - "Handweiserstraße", - "Hanebeckstraße", - "Hänflingweg", - "Hangeneystraße", - "Hangstraße", - "Hannöversche Straße", - "Hansaplatz", - "Hansastraße", - "Hansbergstraße", - "Hänselweg", - "Hansemannstraße", - "Hans-Grüning-Weg", - "Hans-Holbein-Straße", - "Hans-Litten-Straße", - "Hansmannstraße", - "Hans-Peters-Straße", - "Hardenackerweg", - "Hardenbergstraße", - "Harkortshof", - "Harkortstraße", - "Harkortweg", - "Harnackstraße", - "Harpener Hellweg", - "Hartwinkel", - "Harzweg", - "Haselhoffstraße", - "Hasenberg", - "Hasenkamp", - "Haslindestraße", - "Hatzfeldstraße", - "Haubachstraße", - "Hauerstraße", - "Hauert", - "Haumannstraße", - "Hauptfeld", - "Hausdorfstraße", - "Häuskenweg", - "Hausmannstraße", - "Havelandsheck", - "Haydnstraße", - "Hebelenhof", - "Hebelerweg", - "Heckelbeckstraße", - "Heckelingweg", - "Heckenstraße", - "Hedingsmorgen", - "Hedwigstraße", - "Hegemanns Heide", - "Heideblick", - "Heidekopf", - "Heidekrugweg", - "Heideweg", - "Heidstrang", - "Heiduferweg", - "Heilbronner Straße", - "Heiligegartenstraße", - "Heiliger Weg", - "Heilsberger Weg", - "Heimannstrße", - "Heimbaustraße", - "Heimbrügge", - "Heimsenstraße", - "Heimstättenweg", - "Heino-Brauckhoff-Weg", - "Heinrich-August-Schulte-Straße", - "Heinrich-Hertz-Straße", - "Heinrich-Mann-Straße", - "Heinrich-Munsbeck-Straße", - "Heinrich-Pieper-Straße", - "Heinrich-Staubach-Straße", - "Heinrich-Stephan-Straße", - "Heinrichstraße", - "Heinrich-Sträter-Straße", - "Heinstück", - "Heisenbergstraße", - "Heisterkamp", - "Heisterstraße", - "Heiterkeitsweg", - "Heitkampstraße", - "Helene-Meiser-Weg", - "Helenenbergweg", - "Helenenstraße", - "Helene-Wessel-Straße", - "Helgaweg", - "Helgoland", - "Heliosweg", - "Helle", - "Hellenbank", - "Hellerstraße", - "Helmutstraße", - "Helsinkistraße", - "Hengsener Straße", - "Hengstenbergweg", - "Hengsteystraße", - "Hennetwiete", - "Henningsweg", - "Henriettenweg", - "Heraweg", - "Herbersknapp", - "Herbert-Frommberger-Weg", - "Herderstraße", - "Herdstraße", - "Heribertstraße", - "Heringenstraße", - "Herkulesstraße", - "Hermann-Löns-Straße", - "Hermannstraße", - "Hermelinweg", - "Hermelskamp", - "Hermerichweg", - "Herner Straße", - "Heroldstraße", - "Herpersbusch", - "Herrekestraße", - "Herrenstraße", - "Herrenwiesenstraße", - "Hertastraße", - "Herwingweg", - "Hesselkamp", - "Hessenbank", - "Hesseweg", - "Hessische Straße", - "Heßlingsweg", - "Hesternweg", - "Heuerlingsweg", - "Heunerstraße", - "Heuweg", - "Hevesteige", - "Heydbrekenstraße", - "Heyden-Rynsch-Straße", - "Heyerstraße", - "Heyneckenweg", - "Hiddingstraße", - "Hildastraße", - "Hildebrandstraße", - "Hildegundweg", - "Hildesheimer Straße", - "Hilgenloh", - "Hilgenstockstraße", - "Hillermannweg", - "Hiltropwall", - "Himpendahlweg", - "Hinnenberg", - "Hinter dem Garten", - "Hinter der Wiese", - "Hinter Holtein", - "Hintere Schildstraße", - "Hinterer Remberg", - "Hirschweg", - "Hirtenstraße", - "Hobestadt", - "Hochfelder Straße", - "Hochofenstraße", - "Höchstener Straße", - "Hochstraße", - "Hochwaldstraße", - "Hoddenfeld", - "Hoeteweg", - "Hofeswiese", - "Hofgerichtsweg", - "Höfkerstraße", - "Hofstadtweg", - "Hofstraße", - "Hohbrinkstraße", - "Hohe Braukstraße", - "Hohe Luft", - "Hohe Straße", - "Hohenfriedberger Straße", - "Hohensyburgstraße", - "Höhenweg", - "Hohenzollernstraße", - "Hoher Wall", - "Höhfuhr", - "Hohle Eiche", - "Hollandstraße", - "Hollestraße", - "Hollmannstraße", - "Holsteiner Straße", - "Holtbeuteweg", - "Holtbrügge", - "Holter Weg", - "Holtestraße", - "Holthauser Straße", - "Holtingsweg", - "Holtkottenweg", - "Holtzplatz", - "Holunderweg", - "Holzener Straße", - "Holzener Weg", - "Holzerweg", - "Holzheck", - "Holzrichterweg", - "Holzwickeder Straße", - "Hombrucher Straße", - "Hönnestieg", - "Hopfenstraße", - "Hopmanns Mühlenweg", - "Hordemannshof", - "Hörder Bahnhofstraße", - "Hörder Bruch", - "Hörder Kämpchen", - "Hörder Kampweg", - "Hörder Neumarkt", - "Hörder Rathausstraße", - "Hörder Semerteichstraße", - "Hörder Straße", - "Hörigstraße", - "Horstmarer Straße", - "Hortensienstraße", - "Hosbachstraße", - "Hospitalstraße", - "Hostedder Heide", - "Hostedder Straße", - "Hövelstraße", - "Hövischestraße", - "Hubertusstraße", - "Hubertusweg", - "Huckarder Allee", - "Huckarder Bruch", - "Huckarder Heide", - "Huckarder Hölzchen", - "Huckarder Straße", - "Hückerstraße", - "Hueckstraße", - "Huestraße", - "Hufelandstraße", - "Hügelstraße", - "Hugo-Heimsath-Straße", - "Hugo-Pork-Straße", - "Hugo-Sickmann-Straße", - "Hugostraße", - "Hüllbergstraße", - "Hülsenbuschstraße", - "Hülshof", - "Hultschiner Straße", - "Humboldtstraße", - "Hummelbank", - "Humperdinckweg", - "Hünefeldstraße", - "Hunnentränke", - "Hünninghausstraße", - "Hunoltstraße", - "Husarenstraße", - "Husemannstraße", - "Husener Eichwaldstraße", - "Husener Straße", - "Hüsingheide", - "Hutererweg", - "Hüttemannstraße", - "Hüttenbruchweg", - "Hüttenhospitalstraße", - "Hüttenstraße", - "Hüttnerstraße", - "Hyazinthenstraße", - "I. Bickestraße", - "Ibbebbürenstraße", - "Ibbenbürenstraße", - "Idastraße", - "Igelweg", - "Iggelhorst", - "Ihlanden", - "II. Bickestraße", - "Iltisweg", - "Im Apen", - "Im Aufferoth", - "Im Bromkamp", - "Im Bruch", - "Im Buschholz", - "Im Dahl", - "Im Defdahl", - "Im Dorfe", - "Im Dorloh", - "Im Dreieck", - "Im Eck", - "Im Grubenfeld", - "Im Heidegrund", - "Im Heidewinkel", - "Im Hilger", - "Im Honigstal", - "Im Horst", - "Im Hundeswinkel", - "Im Kallenrott", - "Im Karrenberg", - "Im Löken", - "Im Odemsloh", - "Im Orde", - "Im Ostfeld", - "Im Papenkamp", - "Im Rabenloh", - "Im Rauhen Holz", - "Im Rübel", - "Im Schellenkai", - "Im Schlingen", - "Im Siepen", - "Im Siesack", - "Im Spähenfelde", - "Im Sperrfeld", - "Im Streitfeld", - "Im Telgei", - "Im Velm", - "Im Weidkamp", - "Im Weißen Feld", - "Im Wiengarten", - "Im Wiesengrund", - "Im Wiesenkamp", - "Imbuschweg", - "Imckebank", - "Imigstraße", - "Immanuel-Kant-Straße", - "Immermannstraße", - "In den Böcken", - "In den Börten", - "In den Breen", - "In den Erlen", - "In den Hüchten", - "In den Kämpen", - "In den Ostgärten", - "In den Stämmen", - "In den Weidbüschen", - "In den Westenkempen", - "In der Esche", - "In der Fühle", - "In der Großen Heide", - "In der Heide", - "In der Liethe", - "In der Lohwiese", - "In der Meile", - "In der Mulde", - "In der Oeverscheidt", - "In der Rünsterte", - "In der Schmechting", - "In der Teufe", - "In der Wollmei", - "Innsbruckstraße", - "Inselstraße", - "Insterburger Straße", - "Intückenweg", - "Irischer Weg", - "Irisstraße", - "Irmgardstraße", - "Irminsulstraße", - "Italienischer Weg", - "Jadeweg", - "Jagdhausstraße", - "Jägerstraße", - "Jägerweg", - "Jasminstraße", - "Joachim-Neander-Straße", - "Joachimstraße", - "Johanna-Melzer-Straße", - "Johanna-Spyri-Weg", - "Johannes-Gronowski-Straße", - "Johannes-Menne-Weg", - "Johannesstraße", - "Johann-Heckeroth-Straße", - "Johannisbergstraße", - "Johannisborn", - "Jonathanstraße", - "Joseph-Cremer-Straße", - "Joseph-Scherer-Straße", - "Josephstraße", - "Joseph-von-Fraunhofer-Straße", - "Jubachweg", - "Juchostraße", - "Jülicher Straße", - "Juliusstraße", - "Julius-Vogel-Straße", - "Jungferntalstraße", - "Junggesellenstraße", - "Jung-Stilling-Weg", - "Junoweg", - "Jupiterstraße", - "Jürgensstraße", - "Justusweg", - "K 3", - "Kafkastraße", - "Kahle Hege", - "Kahlsiepe", - "Kaiseradlerweg", - "Kaiserstraße", - "Kaldehofweg", - "Kaldernstraße", - "Kalmeichweg", - "Kamener Straße", - "Kameradschaftsweg", - "Kamergstraße", - "Kammerstück", - "Kämpchenstraße", - "Kamphecke", - "Kampmannsweg", - "Kampstraße", - "Kanalstraße", - "Kannengießerweg", - "Kanzlerstraße", - "Kapellenstraße", - "Kapellenweg", - "Kapitelwiese", - "Kappenberger Straße", - "Karbonweg", - "Karinstraße", - "Karl-Brühne-Weg", - "Karl-Ernst-Straße", - "Karl-Funke-Straße", - "Karl-Harr-Straße", - "Karl-Klose-Weg", - "Karl-Liebknecht-Straße", - "Karl-Marx-Straße", - "Karl-Prümer-Straße", - "Karl-Rübel-Straße", - "Karlsbader Straße", - "Karlsbank", - "Karl-Schwartz-Straße", - "Karlsglückstraße", - "Karlsruhestraße", - "Karl-Wenk-Straße", - "Karl-Zahn-Straße", - "Kärntenstraße", - "Karolinenstraße", - "Karrenpad", - "Kaspar-Schulte-Straße", - "Kasseler Straße", - "Kastanienallee", - "Kastanienplatz", - "Kastanienstraße", - "Katharinenstraße", - "Käthe-Kollwitz-Straße", - "Käthe-Schaub-Weg", - "Kattenkuhle", - "Kattenstert", - "Kattenstraße", - "Kattowitzstraße", - "Katzbachstraße", - "Kaubomstraße", - "Kaubstraße", - "Kautskystraße", - "Kavernenweg", - "Kebbestraße", - "Kehrbrock", - "Keilhausstraße", - "Keinstraße", - "Keldermannweg", - "Kellerstraße", - "Keltenstraße", - "Kemminghauser Straße", - "Kemnaderweg", - "Keplerstraße", - "KercKeringswiese", - "Kerschensteinerstraße", - "Kesselborn", - "Kesselstraße", - "Kettelerweg", - "Kettlerskamp", - "Kiebitzweg", - "Kieferstraße", - "Kiefholz", - "Kielstraße", - "Kiewitzweg", - "Kinkelstraße", - "Kippstraße", - "Kipsburg", - "Kirchbruchstraße", - "Kirchderner Straße", - "Kirchenkamp", - "Kirchenstraße", - "Kirchhörder Berg", - "Kirchhörder Kopf", - "Kirchhörder Straße", - "Kirchlinder Feld", - "Kirchlinder Straße", - "Kirchnerstraße", - "Kirschbaumweg", - "Klarastraße", - "Kleiberweg", - "Kleine Beurhausstraße", - "Kleine Brücke", - "Kleine Burgholzstraße", - "Kleine Gildenstraße", - "Kleine Grisarstraße", - "Kleine Heide", - "Kleine Herderstraße", - "Kleine Kielstraße", - "Kleine Kleiststraße", - "Kleine Riedbruchstraße", - "Kleine Rückertstraße", - "Kleine Schwerter Straße", - "Kleine Uhlandstraße", - "Kleine Wannestraße", - "Kleiner Floraweg", - "Kleiner Pfad", - "Kleiner Schewenort", - "Kleiner Waldhausweg", - "Kleiststraße", - "Klemptweg", - "Kleppingstraße", - "Klever Straße", - "Kleveskamp", - "Kleybergstraße", - "Kleybredde", - "Kleyer Dorfstraße", - "Kleyer Feld", - "Kleyer Weg", - "Kleymannsweg", - "Klink", - "Klobesstraße", - "Klöcknerstraße", - "Klönnestraße", - "Klöppelweg", - "Klosterbredde", - "Klosterstraße", - "Klüsenerskamp", - "Klusenweg", - "Klütingweg", - "Knappenstraße", - "Knappmannweg", - "Knappstraße", - "Knauerweg", - "Kneebuschstraße", - "Knospenweg", - "Knyphausenstraße", - "Kobbendelle", - "Kocklinckeweg", - "Koerstarße", - "Kohlenbankweg", - "Kohlensiepenstraße", - "Kohlgartenstraße", - "Kohlweißlingsweg", - "Kokshohlweg", - "Kölbestraße", - "Kolbstraße", - "Kolibriweg", - "Kolingestraße", - "Kolmarer Straße", - "Köln-Berliner-Straße", - "Kolpingstraße", - "Kometenstraße", - "König Grätzer Straße", - "Königsbergstraße", - "Königshalt", - "Königsheide", - "Königshüttestraße", - "Königs-Sunden", - "Königstraße", - "Königswall", - "Konrad-Adenauer-Allee", - "Konrad-Glocker-Straße", - "Konradstraße", - "Könzgenstraße", - "Kopernikusstraße", - "Koppelweg", - "Koppweg", - "Korallenweg", - "Körfken", - "Kornacker", - "Kornblumenstraße", - "Körnebachstraße", - "Korneliusstraße", - "Körner Grund", - "Körner Hellweg", - "Körnerstraße", - "Kornweg", - "Korte Geitke", - "Kortenstraße", - "Kortental", - "Körtingsweg", - "Kortschstraße", - "Kortumweg", - "Kosselstraße", - "Kösterstraße", - "Kötterkamp", - "Kötterweg", - "Kraepelinweg", - "Krähenbruch", - "Krählweg", - "Krämerbank", - "Krampelo", - "Kräutergarten", - "Kreftenscher", - "Kreigershofstraße", - "Kreisstraße", - "Kressenweg", - "Kreuzheide", - "Kreuzstraße", - "Kreyenbachweg", - "Kriemhildstraße", - "Krimstraße", - "Krinkelbach", - "Krinkelweg", - "Krokusweg", - "Kronenburgallee", - "Kronenstraße", - "Kronprinzenstraße", - "Kruckeler Straße", - "Kruckelhoek", - "Krückenweg", - "Kuckelbusch", - "Kuckelke", - "Kückshauser Straße", - "Kuckucksweg", - "Kühlingstraße", - "Kühlkamp", - "Kuhlmannstraße", - "Kühnstraße", - "Kuhstraße", - "Kuithanstraße", - "Kükenhöhlerweg", - "Kullenberg", - "Kullrichstraße", - "Kümper Heide", - "Kümperheide", - "Kumpstraße", - "Kunibertstraße", - "Kuntzestraße", - "Küpenweg", - "Küpferstraße", - "Kuppenweg", - "Kurfürstenstraße", - "Kurler Straße", - "Kurze Hecke", - "Kurze Heed", - "Kurze Reihe", - "Kurze Straße", - "Kurzer Morgen", - "Kußnachtstraße", - "Küsterkamp", - "Kymnastraße", - "Labandstraße", - "Lachsweg", - "Lachterweg", - "Lagerhausstraße", - "Lambachstraße", - "Lambergstraße", - "Landgrafenstraße", - "Landmannweg", - "Landoisweg", - "Landskroner Straße", - "Landwehrstraße", - "Lange Fuhr", - "Lange Hecke", - "Lange Heed", - "Lange Reihe", - "Lange Straße", - "Lange Wiese", - "Langeloh", - "Langenacker", - "Langerohstraße", - "Langobardenstraße", - "Langschedestraße", - "Lanstroper Straße", - "Lantfridweg", - "Lappenbergsbank", - "Lappenkreutz", - "Laubsängerweg", - "Lauenburger Straße", - "Lauestraße", - "Lautastraße", - "Lavendelweg", - "Leberstraße", - "Lechlohweg", - "Lehmkuhle", - "Lehmufer", - "Lehnemannsweg", - "Lehnertweg", - "Leibnizstraße", - "Leideckerweg", - "Leierweg", - "Leiloh", - "Leinbergerstraße", - "Leineweberstraße", - "Leineweg", - "Leipziger Straße", - "Leisse", - "Leitmeritzstraße", - "Lemberger Feld", - "Lenderichstraße", - "Leneckeweg", - "Lenhoffweg", - "Lenigheck", - "Leni-Rommel-Straße", - "Lenneweg", - "Lenninghausstraße", - "Lensingstraße", - "Lenteninsel", - "Leonhard-Euler-Straße", - "Leopold-Schütte-Straße", - "Leopoldstraße", - "Leostraße", - "Leppinghof", - "Lerchenweg", - "Lessenstraße", - "Lessingstraße", - "Leßnerweg", - "Lesumstraße", - "Leukelwiese", - "Leunenschloßstraße", - "Leuschnerstraße", - "Leuthardstraße", - "Leveringstraße", - "Leythestraße", - "Liboristraße", - "Lichtendorfer Straße", - "Liebermannstraße", - "Liebfrauenstraße", - "Liebigstraße", - "Liedweg", - "Liegendanfahrt", - "Liesemorgen", - "Liethschulteweg", - "Ligusterweg", - "Lilienthalstraße", - "Limbecker Postweg", - "Limbecker Straße", - "Limburger Postweg", - "Lina-Schäfer-Straße", - "Lindberghstraße", - "Lindbreiteweg", - "Lindemannstraße", - "Lindenhorster Straße", - "Lindenstraße", - "Lindentalweg", - "Lindstraße", - "Linienstraße", - "Linkestraße", - "Linneweberstraße", - "Linnigmannstraße", - "Lippestraße", - "Lippmannstraße", - "Lippstädter Straße", - "Lissaboner Allee", - "Listertwiete", - "Littgenloh", - "Littweg", - "Lodemannsweg", - "Lohacker", - "Lohheide", - "Lohkampweg", - "Lohoffstraße", - "Londoner Bogen", - "Lorenweg", - "Lorzingstraße", - "Lotenkamp", - "Lotharstraße", - "Lotosweg", - "Lots Siepen", - "Löttringhauser Straße", - "Löwenstraße", - "Lowenthal", - "Lübbertweg", - "Lübbringweg", - "Lübckerhofstraße", - "Lübecker Straße", - "Lubigweg", - "Lübkestraße", - "Ludeckeweg", - "Lüdinghauser Straße", - "Ludolfweg", - "Ludwig-Lohner-Straße", - "Ludwigstraße", - "Lueckestraße", - "Luegstraße", - "Luerwaldstraße", - "Luftschacht", - "Lugaustraße", - "Lugierstraße", - "Lührmannstraße", - "Luisenglück", - "Luisenhoffnung", - "Luisenplatz", - "Luisenschachtstraße", - "Luisenstraße", - "Luise-Rinser-Weg", - "Lüneburger Straße", - "Lünener Straße", - "Lunestraße", - "Luninkhofstraße", - "Lupinenweg", - "Lüserbachstraße", - "Lütge Brückstraße", - "Lütge Heidestraße", - "Lütge Vöhde", - "Lütgendortmunder Hellweg", - "Lütgendortmunder Straße", - "Lütgenholthauser Straße", - "Lütgenholz", - "Lüttenwiese", - "Lützowstraße", - "Machariusstraße", - "Mackenrothweg", - "Magdalenenstraße", - "Magdeburger Straße", - "Mahlscheidt", - "Maienweg", - "Maiglöckchenweg", - "Mailoh", - "Mallinckrodtstraße", - "Malmessiepen", - "Malritzstraße", - "Malterweg", - "Malvenweg", - "Malzstraße", - "Mannheimplatz", - "Mansfeldstraße", - "Manteuffelstraße", - "Märchenweg", - "Marderweg", - "Margaretenstraße", - "Maria-Block-Sraße", - "Maria-Goeppert-Mayer-Straße", - "Mariannenstraße", - "Marie-Juchacz-Straße", - "Marienbader Straße", - "Marienborn", - "Marienburger Weg", - "Marienstraße", - "Markbauernstraße", - "Markenhudeweg", - "Markenwaldweg", - "Märker Feld", - "Märker Grund", - "Märker Höhe", - "Markgrafenstraße", - "Markhege", - "Märkische Straße", - "Markscheiderstraße", - "Marksweg", - "Markt", - "Marktplatz", - "Markusstraße", - "Marsbruchstraße", - "Marschallstraße", - "Marsstraße", - "Martener Brücke", - "Martener Hellweg", - "Martener Straße", - "Martha-Gillessen-Straße", - "Martha-Neumann-Straße", - "Marthastraße", - "Martin-Schmeißer-Platz", - "Martin-Schmeißer-Weg", - "Martinstraße", - "Märtmannstraße", - "Massener Hellweg", - "Massener Straße", - "Massener Weg", - "Massenezstraße", - "Materna Parkplatz", - "Mathildenstraße", - "Matilda-Wrede-Straße", - "Matthias-Grünewald-Straße", - "Mattlacke", - "Maulwurfsweg", - "Maurice-Vast-Straße", - "Max-Brandes-Straße", - "Max-Brod-Straße", - "Max-Eyth-Straße", - "Maximiliam-Kolbe-Straße", - "Max-Ophüls-Platz", - "Mc Drive", - "Mechtildstraße", - "Mehlbeerenweg", - "Meinbergstraße", - "Meinertstraße", - "Meinhardstraße", - "Meininghausstraße", - "Meisenweg", - "Meißener Straße", - "Meitnerweg", - "Melanchthonstraße", - "Melchtalstraße", - "Melscheder Weg", - "Memeler Straße", - "Mendestraße", - "Mengeder Markt", - "Mengeder Schulstraße", - "Mengeder Straße", - "Menglinghauser Straße", - "Mentlerstraße", - "Merckenbuschweg", - "Mergelkopfweg", - "Mergelkuhle", - "Mergelteichstraße", - "Merklinder Straße", - "Merkurstraße", - "Messelinckstraße", - "Mettestraße", - "Metzer Straße", - "Meuselwitzstraße", - "Meylantstraße", - "Michael-Ende-Straße", - "Michael-Holzach-Weg", - "Michaelstraße", - "Milanweg", - "Milchgasse", - "Millkottenweg", - "Mimosenweg", - "Mindener Straße", - "Minister-Stein-Allee", - "Minoritenstraße", - "Missundestraße", - "Mittelstraße", - "Mittelweg", - "Möckernstraße", - "Mödershof", - "Möhneweg", - "Mohnweg", - "Molenarkweg", - "Molkereistraße", - "Mollenacker", - "Möllenbeckstraße", - "Möllenhoffstraße", - "Möllerstraße", - "Mollwitzer Straße", - "Molnerweg", - "Moltkestraße", - "Mommsenweg", - "Mönchengang", - "Mönchenwordt", - "Mondstraße", - "Mönninghofstraße", - "Mooskamp", - "Morgenstraße", - "Moritzgasse", - "Morterstraße", - "Mortmannshof", - "Moskauer Straße", - "Mosselde", - "Mountainbike-Hügel", - "Mozartstraße", - "Muddepenningweg", - "Mühlackerstraße", - "Mühlenstraße", - "Müllerstraße", - "Mulmannweg", - "Münsterstraße", - "Müserstraße", - "Museumsgasse", - "Muspelheimstraße", - "Myrtenweg", - "Nachoder Straße", - "Nachtigallenweg", - "Nackhofstraße", - "Nagelpötchen", - "Nagelschmiedgasse", - "Narzissenstraße", - "Nasses Holz", - "Nathebachstraße", - "Natherweg", - "Nathmerichstraße", - "Natorpweg", - "Naturfreundehaus Downhill", - "Nauenstraße", - "Neben dem Brand", - "Nebenbruch", - "Nederhoffstraße", - "Neißestraße", - "Nelkenstraße", - "Nelly-Sachs-Straße", - "Neptunstraße", - "Nertusstraße", - "Nervierstraße", - "Nerzweg", - "Nettelbeckstraße", - "Neu-Crengeldanz-Straße", - "Neue Dorfstraße", - "Neue Ringstraße", - "Neue Sendstraße", - "Neue Tremoniastraße", - "Neuer Graben", - "Neuflöz", - "Neuhammerweg", - "Neuhoffstraße", - "Neu-Iserlohn-Straße", - "Neulandplatz", - "Neulandstraße", - "Neumarkstraße", - "Neunkirchenstraße", - "Neurodestraße", - "Niederadener Straße", - "Niederhofener Straße", - "Niederhofer Holz", - "Niederhofer Kohlenweg", - "Niedernetter Straße", - "Niedersachsenweg", - "Niederste Feldweg", - "Niergartenstraße", - "Nierhausstraße", - "Nierstefeldstraße", - "Nießstraße", - "Nixenweg", - "Nollendorfplatz", - "Nollendorfstraße", - "Nordblick", - "Nordbruch", - "Nordmarkt", - "Nordstraße", - "Nortkirchenstraße", - "Noskestraße", - "Nöthenort", - "Nottebaumweg", - "Notweg", - "NS IX", - "Nußbaumweg", - "Ob der Kolmke", - "Oberadener Straße", - "Oberbank", - "Oberbecker Straße", - "Oberdelle", - "Oberdorfstraße", - "Obere Brinkstraße", - "Obere Egge", - "Obere Gartenstraße", - "Obere Hangstraße", - "Obere Pekingstraße", - "Oberevinger Straße", - "Oberfeldstraße", - "Oberhausstraße", - "Oberholte", - "Obermarkstraße", - "Obernetter Straße", - "Obernkirchenstraße", - "Oberschlesierstraße", - "Oberste Kamp", - "Oberste-Wilms-Straße", - "Oderstraße", - "Oelmühlenweg", - "Oerfeld", - "Oespeler Dorfstraße", - "Oespeler Kirchweg", - "Oesterholzstraße", - "Oestermärsch", - "Oesterstraße", - "Oestricher Straße", - "Oetringhauser Straße", - "Oleanderweg", - "Oleariusstraße", - "Olgastraße", - "Olof-Palme-Straße", - "Olpe", - "Olpketalstraße", - "Olufsweg", - "Orchideenweg", - "Ordalweg", - "Orensteinstraße", - "Örlinghauser Weg", - "Örlingweg", - "Ortdieck", - "Ortelsburger Weg", - "Ortfeld", - "Ortli", - "Ortsmühle", - "Ortwinkel", - "Oskar-Wachtel-Weg", - "Oskarweg", - "Oslostraße", - "Osningstraße", - "Ostberger Feldweg", - "Ostberger Straße", - "Ostenbergstraße", - "Ostenhellweg", - "Ostenschleifweg", - "Osterfeldstraße", - "Osterkuhle", - "Osterlandwehr", - "Ostermannstraße", - "Osterrothweg", - "Osterschleppweg", - "Osterymweg", - "Ostfalenstraße", - "Osthoffstraße", - "Ostholzstraße", - "Ostkirchstraße", - "Ostwall", - "Oswaldstraße", - "Otto-Brenner-Weg", - "Otto-Hahn-Straße", - "Ottostraße", - "Ottweilerstraße", - "Ötztaler Straße", - "Ovelackerstraße", - "Overbeck", - "Overbeckstraße", - "Overgünne", - "Overhoffstraße", - "Pacellistraße", - "Paderborner Straße", - "Pallandtweg", - "Papenacker", - "Papengasse", - "Papenhofskamp", - "Pappelstraße", - "Paradiesstraße", - "Parkplatzstraße", - "Parkstraße", - "Parsevalstraße", - "Paschknappstraße", - "Päßchen", - "Passmannweg", - "Passweg", - "Pastoratsweg", - "Patroklusweg", - "Paul-Gerhardt-Straße", - "Paulinenstraße", - "Paul-Winzen-Straße", - "Peddenbrink", - "Penningskamp", - "Pentelingstraße", - "Pepperstraße", - "Persebecker Straße", - "Peschweg", - "Pestalozzistraße", - "Peter-Florenz-Weddingen-Straße", - "Petergasse", - "Peter-Hille-Straße", - "Petermannsweg", - "Peter-Paul-Rubens-Straße", - "Petershagenstraße", - "Petrikirchhof", - "Petrystraße", - "Pfahlstück", - "Pfarrer-Barheine-Weg", - "Pfarrer-Beule-Weg", - "Pfarrer-Klinzing-Weg", - "Pfarrer-Kneipp-Straße", - "Pfarrstraße", - "Pfauenaugenweg", - "Phönixstraße", - "Piepenbrink", - "Piepenstockplatz", - "Piepenstockstraße", - "Pilgermannsweg", - "Pinienweg", - "Pirolweg", - "Planckstraße", - "Planetenfeldstraße", - "Plaßstraße", - "Platanenweg", - "Platz der Alten Synagoge", - "Platz von Hiroshima", - "Platz von Leeds", - "Platz von Rostow am Don", - "Plauener Straße", - "Pleckenbrinck", - "Pleckenbrink", - "Plettenbergstraße", - "Plümers Ort", - "Plutoweg", - "Pohlmannweg", - "Pöllerstraße", - "Polliusweg", - "Poppelsdorfer Straße", - "Portmannsweg", - "Portugiesischer Weg", - "Posener Straße", - "Posthornweg", - "Postkutschenstraße", - "Poststraße", - "Postweg", - "Potacker", - "Potgasse", - "Pothecke", - "Pothmorgenweg", - "Pottenkamp", - "Potthöferei", - "Prager Weg", - "Preinstraße", - "Prellerstraße", - "Preußische Straße", - "Priesterwiese", - "Primelstraße", - "Prinzenstraße", - "Prinz-Friedrich-Karl-Straße", - "Priorstraße", - "Probstheidastraße", - "Pröbstingkamp", - "Propsteihof", - "Provinzialstraße", - "Prüferweg", - "Pruzzenweg", - "Pulverstraße", - "Putmanstraße", - "Püttbeckenstraße", - "Püttlingenstraße", - "Püttweg", - "Pyrmonter Straße", - "Quadbeckstraße", - "Quakmannsweg", - "Quartlenbeckstraße", - "Quarzweg", - "Querstraße", - "Rabboltstraße", - "Rabenstraße", - "Rahestraße", - "Rählwiese", - "Rahmer Straße", - "Rahmkesweg", - "Rahmsloher Weg", - "Raiderweg", - "Ramhofstraße", - "Rankenweg", - "Rappäusweg", - "Rapunzelweg", - "Rastenburger Straße", - "Rathenaustraße", - "Rathoffsweg", - "Raudestraße", - "Rauher Dorn", - "Rauher Kamp", - "Raulfskamp", - "Rauschenbuschstraße", - "Raveike", - "Ravensberger Straße", - "Ravensweg", - "Rebhuhnweg", - "Reckerdingstraße", - "Recklinghauser Straße", - "Redtenbacherstraße", - "Regenpfeiferweg", - "Reichenberger Straße", - "Reichmarkstraße", - "Reichshofstraße", - "Reichsmarkstraße", - "Reichswehrstraße", - "Reiherhorst", - "Reiner-Daelen-Straße", - "Reinickendorfer Weg", - "Reinoldistraße", - "Reinwardtstraße", - "Reiserstraße", - "Reitweg", - "Remigiusstraße", - "Remigustrße", - "Rennweg", - "Resedastraße", - "Revierstraße", - "Rhader Weg", - "Rhedeweg", - "Rheinische Straße", - "Rheinischer Esel", - "Rheinlanddamm", - "Rhönweg", - "Richardstraße", - "Richard-Wagner-Straße", - "Richtersdorfstraße", - "Richterstraße", - "Richtsteig", - "Riesestraße", - "Riewepläßken", - "Rigwinstraße", - "Rindenstraße", - "Ringeloh", - "Ringelohstraße", - "Ringofenstraße", - "Ringstraße", - "Rinscheweg", - "Rispenstraße", - "Ristweg", - "Ritsartweg", - "Rittershausstraße", - "Rittershofer Straße", - "Ritterstraße", - "Robert-Koch-Straße", - "Robertstraße", - "Robinienweg", - "Röddingsbaumweg", - "Rodenbergstraße", - "Roggenkamp", - "Rohdesdiek", - "Roholte", - "Röhrsteige", - "Rohwedderstraße", - "Rolandstraße", - "Rolevinckstraße", - "Rombergstraße", - "Römermorgen", - "Römerstraße", - "Römerweg", - "Roningweg", - "Röntgenstraße", - "Roonheide", - "Roonstraße", - "Rosa-Luxemburg-Straße", - "Roseggerstraße", - "Rosemeyerstraße", - "Rosenowstraße", - "Rosenstraße", - "Rosental", - "Rosmarinweg", - "Roßbachstraße", - "Rösselmannstraße", - "Rotariusstraße", - "Rotbuchenweg", - "Rotdornallee", - "Rotdornweg", - "Rote Fuhr", - "Rote-Becker-Straße", - "Roter Morgen", - "Roter Weg", - "Rotgerweg", - "Rothöfstraße", - "Rotkäppchenweg", - "Rotkehlchenweg", - "Röttgersbank", - "RTW/KTW", - "Rubbertweg", - "Rübenkamp", - "Rübenstraße", - "Rübezahlweg", - "Rubinstraße", - "Ruckebierstraße", - "Rückertstraße", - "Rüdigerstraße", - "Rüdinghauser Straße", - "Rügecke", - "Ruhfusstraße", - "Ruhrallee", - "Ruhrschnellweg", - "Ruhrwaldstraße", - "Ruinenstraße", - "Rumpstraße", - "Rundstraße", - "Rupinghofstraße", - "Rüschebrinkstraße", - "Rüschenstraße", - "Rüsterweg", - "Ruthgerusstraße", - "Ruthstraße", - "Rütlistraße", - "Rybnikstraße", - "Saarbrücker Straße", - "Saarlandstraße", - "Sachsenwaldstraße", - "Sadelhof", - "Salamanderweg", - "Salweyweg", - "Salzbrunnstraße", - "Salzburger Straße", - "Salzgasse", - "Salzwedeler Straße", - "Sämannweg", - "Sandbirkenweg", - "Sanderoth", - "Sanitätsrat-Hallermann-Straße", - "Sankt-Georg-Straße", - "Sartoristraße", - "Sattelweg", - "Saturnstraße", - "Sauerländer Straße", - "Schaarstraße", - "Schachtstraße", - "Schachtweg", - "Schafackerweg", - "Schäferkampstraße", - "Schäferstraße", - "Schäfflerweg", - "Schafstallstraße", - "Schalkenbergsiepen", - "Schanzenweg", - "Schaperstraße", - "Schaphusstraße", - "Schärenhof", - "Scharnhorststraße", - "Scheffelstraße", - "Schelerweg", - "Schemmersfeld", - "Schenkebierweg", - "Scheuseweg", - "Schewenort", - "Schichtweg", - "Schieferbank", - "Schiffhorst", - "Schiffstraße", - "Schildplatz", - "Schildstraße", - "Schillerknapp", - "Schillerstraße", - "Schillingstraße", - "Schillstraße", - "Schimmelreiterweg", - "Schimmelstraße", - "Schirrmannweg", - "Schlagbaumstraße", - "Schlangenweg", - "Schleefstraße", - "Schlehenweg", - "Schleifenstraße", - "Schleipweg", - "Schleppbahnstraße", - "Schleswiger Straße", - "Schlickenkamp", - "Schliepstraße", - "Schlosserstraße", - "Schloßstraße", - "Schloß-Westhusener-Straße", - "Schlotweg", - "Schlüsselweg", - "Schmaler Weg", - "Schmelzgerweg", - "Schmemannsweg", - "Schmerkottenstraße", - "Schmetterlingsweg", - "Schmettowstraße", - "Schmiedestraße", - "Schmiedingstraße", - "Schmölterweg", - "Schmuckstraße", - "Schneewittchenweg", - "Schneiderstraße", - "Schnepfenweg", - "Schnettkerweg", - "Schnitterweg", - "Schöffenweg", - "Schoffsweg", - "Schölerpatt", - "Schönaichstraße", - "Schönaustraße", - "Schondellestraße", - "Schöneichensiepen", - "Schöner Pfad", - "Schönhalsweg", - "Schönhauser Straße", - "Schönstraße", - "Schönwaldstraße", - "Schoppenbergweg", - "Schorlemmerskamp", - "Schotteweg", - "Schragmüllerstraße", - "Schramweg", - "Schröderstraße", - "Schübbestraße", - "Schubertstraße", - "Schüchtermannstraße", - "Schuhhof", - "Schulenburgstraße", - "Schulstraße", - "Schulte-Heuthaus-Straße", - "Schultenhude", - "Schultenstraße", - "Schulte-Sodingen-Straße", - "Schulzstraße", - "Schumannstraße", - "Schumpeterweg", - "Schürbankstraße", - "Schürener Straße", - "Schürener Vorstadt", - "Schurfweg", - "Schürhoffstraße", - "Schüruferstraße", - "Schüttersort", - "Schützenstraße", - "Schwäbische Straße", - "Schwalbenbrink", - "Schwanenstraße", - "Schwanenwall", - "Schwärmerweg", - "Schwarzdornweg", - "Schwarzdrosselweg", - "Schwarze-Becker-Straße", - "Schwarze-Brüder-Weg", - "Schwarze-Ewald-Straße", - "Schwarzenbergstraße", - "Schwarzer Weg", - "Schwarzerlenweg", - "Schweizer Allee", - "Schwerter Kirchweg", - "Schwerter Straße", - "Schwieringhauser Straße", - "Schwimmweg", - "Sckellstraße", - "Sebrathweg", - "Seekante", - "Seepenweg", - "Seerosenweg", - "Seibertzweg", - "Seidenspinnerweg", - "Seilbahnweg", - "Seilerstraße", - "Selbachstraße", - "Selbsthilfstraße", - "Selma-Lagerlöf-Straße", - "Selzerstraße", - "Semerteichstraße", - "Sendstraße", - "Senftenbergstraße", - "Sengsbank", - "Sennestraße", - "Severingstraße", - "Seydlitzstraße", - "Sichelstraße", - "Sichterweg", - "Siebenbürgenstraße", - "Siebensternweg", - "Siedlung Salingen", - "Siedlungsweg", - "Siegburgstraße", - "Siegelbaumweg", - "Siegelstraße", - "Siegenstraße", - "Siegfried-Drupp-Straße", - "Siegfriedstraße", - "Siemensstraße", - "Siepmannstraße", - "Sigurdweg", - "Silberhecke", - "Silberknapp", - "Silberstraße", - "Silberweidenweg", - "Simmelweg", - "Sindernweg", - "Sindfeld", - "Singerhoffstraße", - "Soester Straße", - "Solbergweg", - "Sölder Bruch", - "Sölder Kirchweg", - "Sölder Straße", - "Sölder Waldstraße", - "Solinggut", - "Solmstraße", - "Sombartweg", - "Somborner Feldweg", - "Somborner Höh", - "Somborner Straße", - "Sommerberger Kirchweg", - "Sommerbergweg", - "Sommerlindenweg", - "Sommerseite", - "Sonnenplatz", - "Sonnenscheineck", - "Sonnenstraße", - "Sonnenwendstraße", - "Sophie-Thiemann-Straße", - "Sorbenweg", - "Sorpeliet", - "Spanbreite", - "Spanischer Weg", - "Spannerweg", - "Spannstraße", - "Spechtstraße", - "Speckacker", - "Speckestraße", - "Speestraße", - "Speierlingsweg", - "Spenhofweg", - "Sperberstraße", - "Sperberweg", - "Sperkelweg", - "Sperlingstraße", - "Speyerstraße", - "Spicherner Straße", - "Spickufer", - "Spiegelstraße", - "Spinnheide", - "Spissenagelstraße", - "Spitzwegstraße", - "Splintstraße", - "Spohrstraße", - "Spornerweg", - "Spraveweg", - "Spreestraße", - "Sprengelweg", - "Sprickmannweg", - "Springmorgen", - "Springorumstraße", - "Springweg", - "Staatsbusch", - "Stabelpfad", - "Stadtgärtnerei", - "Stadtrat-Cremer-Allee", - "Stahlhöfer Weg", - "Stahlwerkstraße", - "Stallbaumstraße", - "Stangefolstraße", - "Stapelweg", - "Stargarder Weg", - "Starweg", - "Stattskamp", - "Staudenweg", - "Staufenstraße", - "Stauffacherstraße", - "Stauffenbergstraße", - "Steckestraße", - "Stefan-Albringer-Straße", - "Stefanstraße", - "Steglitzeck", - "Stehmannstraße", - "Stehrstraße", - "Steiermarkstraße", - "Steigerstraße", - "Steile Straße", - "Steinäckerstraße", - "Steinauweg", - "Steinbreite", - "Steinbrinkstraße", - "Steinbruchstraße", - "Steinerne Kirche", - "Steinfurtweg", - "Steinhammerstraße", - "Steinhauser Weg", - "Steinhofstraße", - "Steinkauzweg", - "Steinkühlerweg", - "Steinmetzstraße", - "Steinstraße", - "Steinsweg", - "Stemmering", - "Stemmkeweg", - "Stempelweg", - "Stengelweg", - "Sterie", - "Sternstraße", - "Sterntalerweg", - "Sterzinger Straße", - "Stettiner Straße", - "Steubenstraße", - "Steyler Straße", - "Stiegenweg", - "Stieglitzweg", - "Stiftsgehölz", - "Stiftskamp", - "Stiftstraße", - "Stille Gasse", - "Stockholmer Allee", - "Stockumer Bruch", - "Stockumer Straße", - "Stofferstraße", - "Stollenstraße", - "Stolzestraße", - "Stoppelheck", - "Stoppelmannsweg", - "Storch Straße", - "Stortsweg", - "Straßburger Straße", - "Straußstraße", - "Strebweg", - "Streckenstraße", - "Stresemannstraße", - "Strickerstraße", - "Strobelallee", - "Strohnstraße", - "Strümpenbusch", - "Strüningweg", - "Strünkedestraße", - "Stübbenstraße", - "Stubengasse", - "Stuchteystraße", - "Stuckmannshof", - "Studtstraße", - "Stufenweg", - "Stürzelbreite", - "Stuttgartstraße", - "Stypelmanweg", - "Südbecke", - "Südblick", - "Sudermannstraße", - "Südfeld", - "Südflügelweg", - "Südrandweg", - "Südwall", - "Suebenstraße", - "Sugambrerstraße", - "Süggelberg", - "Süggelrandweg", - "Süggelweg", - "Suitbertstraße", - "Sülbeckstraße", - "Suledestraße", - "Sulpkestraße", - "Sulzbacher Straße", - "Sumbecks Holz", - "Sümpelmannstraße", - "Sundagskamp", - "Sunderweg", - "Sunthoffstraße", - "Swedestraße", - "Syburger Dorfstraße", - "Syburger Kirchstraße", - "Syburger Straße", - "Sydowstraße", - "Talbrücke Enderbach", - "Talbrücke Grotenbach", - "Talbrücke Isensteinsiepen", - "Talbrücke Rombergholz", - "Talstraße", - "Tannenkamp", - "Tannenstraße", - "Tassiloweg", - "Taubenweg", - "Tauentzienstraße", - "Taunusweg", - "Tecklenborn", - "Teimannweg", - "Teinerstraße", - "Teislerweg", - "Tellstraße", - "Tengelmannweg", - "Tennenweg", - "Ter-Nedden-Straße", - "Terwestenstraße", - "Tetschener Straße", - "Tettenbachstraße", - "Teutoburger Straße", - "Teutonenstraße", - "Tewaagstraße", - "Tewagastraße", - "Thälmannstraße", - "Theißstraße", - "Thelenort", - "Theodor-Freywald-Weg", - "Theodor-Hürth-Straße", - "Theresenstraße", - "Thieheuerstraße", - "Thielenstraße", - "Thierschweg", - "Thomasgasse", - "Thomas-Mann-Straße", - "Thoniesstraße", - "Thorner Straße", - "Thranestraße", - "Thüringer Straße", - "Thusneldastraße", - "Tidbaldweg", - "Tidemannweg", - "Tiefe Mark", - "Tiefe Straße", - "Tiefenbachtal", - "Tiefenweg", - "Tielkenweg", - "Tierschweg", - "Tilmonweg", - "Tiranaweg", - "Tiroler Straße", - "Toblacher Straße", - "Toepkenweg", - "Tölckestraße", - "Töllenkamp", - "Töllnerstraße", - "Tönnishof", - "Tönnisweg", - "Topasstraße", - "Toppstraße", - "Torckstraße", - "Torgauer Straße", - "Tospelliweg", - "Traddeweg", - "Trakehnerweg", - "Trapmannweg", - "Trapphofstraße", - "Traubenweg", - "Trauermantelweg", - "Traugottweg", - "Trautenauer Straße", - "Treckmannsweg", - "Treibstraße", - "Tremoniabogen", - "Tremoniastraße", - "Trippestraße", - "Tronjestraße", - "Tropauer Straße", - "Trumweg", - "Tschechischer Weg", - "Tucholskystraße", - "Tullstraße", - "Tulpenstraße", - "Tunnel Berghofen", - "Tunnelweg", - "Türkisweg", - "Turmalinweg", - "Turmfalkenstraße", - "Tüselmannweg", - "Tutenweg", - "Twerskuhle", - "Tybbinkstraße", - "Tye", - "Tymannstraße", - "U-Bahnsteig Dortmund-Hafen", - "Übelgönne", - "Überwasserstraße", - "Ubinckstraße", - "Uferstraße", - "Uhlandstraße", - "Uhlmann-Bixterheide-Weg", - "Uhustraße", - "Ulmenstraße", - "Ulmenweg", - "Ulrich-Bonse-Weg", - "Umbreitstraße", - "Unionstraße", - "Universitätsstraße", - "Unnaer Straße", - "Unten im Felde", - "Unter den Linden", - "Unterbank", - "Unterdelle", - "Untere Brinkstraße", - "Untere Egge", - "Untere Gartenstraße", - "Untere Hangstraße", - "Untere Pekingstraße", - "Unterer Sendweg", - "Unterer Weg", - "Unterfeldstraße", - "Untermarkstraße", - "Unterste-Wilms-Straße", - "Unterwaldener Straße", - "Unverhofftstraße", - "Uranusstraße", - "Urbanusstraße", - "Ursulastraße", - "Vahleweg", - "Vahrenort", - "Valmeweg", - "Varstbruch", - "Varusstraße", - "Varziner Straße", - "Veilchenstraße", - "Veitstraße", - "Vellinghauser Straße", - "Velthusstraße", - "Venusstraße", - "Veraweg", - "Verbindungsweg", - "Vereinsstraße", - "Verlorenes Holz", - "Verseweg", - "Vestingweg", - "Veteranenstraße", - "Vethackeweg", - "Vierkandtweg", - "Viermärker Weg", - "Vieselerhofstraße", - "Vikar-Kleffmann-Weg", - "Viktoriastraße", - "Viktor-Toyka-Straße", - "Vinckeplatz", - "Vinckestraße", - "Vinklöther Mark", - "Virchowstraße", - "Voerste-Dieckhof-Straße", - "Vogelinckweg", - "Vogelpothsweg", - "Vogelsangskamp", - "Vogtsstück", - "Vöhdekamp", - "Vöhdeweg", - "Völklinger Straße", - "Volksbundstraße", - "Volksgartenstraße", - "Völksmannweg", - "Volmarsteiner Straße", - "Volmehang", - "Voltaweg", - "Von-der-Berken-Straße", - "Von-der-Goltz-Straße", - "Von-der-Mark-Straße", - "Von-der-Recke-Straße", - "Von-der-Tann-Straße", - "Vor der Brügge", - "Vorhölterstraße", - "Vorläuferweg", - "Vormbrockweg", - "Vorsteherstraße", - "Vorstenstraße", - "Vorwärtsstraße", - "Voßkuhle", - "Vossloh", - "Vrydagweg", - "Vulkanstraße", - "Waarbaum", - "Wacholderstraße", - "Wachteloh", - "Wachtelweg", - "Wagenfeldstraße", - "Wahne Uhle", - "Wahrbuschstraße", - "Waidmannslust", - "Walbertstraße", - "Waldblickweg", - "Waldecker Straße", - "Waldenburgstraße", - "Waldental", - "Walderseestraße", - "Waldhausweg", - "Waldpförtnerweg", - "Waldrodeweg", - "Waldsängerweg", - "Waldstraße", - "Walkmühlenweg", - "Wallachstraße", - "Wallbaumstraße", - "Wallnussweg", - "Wallrabenhof", - "Wallrabestraße", - "Wallstraße", - "Walpkestieg", - "Walstattstraße", - "Walter-Berg-Weg", - "Walter-Dirks-Straße", - "Walter-Schücking-Straße", - "Walter-Welp-Straße", - "Walther-Kohlmann-Straße", - "Waltroper Straße", - "Wambeler Heide", - "Wambeler Hellweg", - "Wambeler Holz", - "Wambeler Straße", - "Wandweg", - "Waneckerweg", - "Wannebachstraße", - "Wannestraße", - "Warburger Straße", - "Wasserbank", - "Wasserfuhr", - "Wasserkunst", - "Wassermannweg", - "Wasserscheide", - "Wasserstraße", - "Waterloostraße", - "Watermannberg", - "Wattenscheidskamp", - "Webershohl", - "Weberstraße", - "Weckherlinweg", - "Weckweg", - "Weddepoth", - "Wedelstraße", - "Weidenbohrerweg", - "Weidenhope", - "Weidenstraße", - "Weilberg", - "Weilkeweg", - "Weingartenstraße", - "Weisbachstraße", - "Weischedestraße", - "Weißdornweg", - "Weiße Hecke", - "Weiße Taube", - "Weiße-Ewald-Straße", - "Weißenburger Straße", - "Weißsteinweg", - "Weitacker", - "Welkenerstraße", - "Wellinghofer Amtsstraße", - "Wellinghofer Hecke", - "Wellinghofer Straße", - "Wembersweg", - "Wendenweg", - "Wenemarstraße", - "Wengestraße", - "Wenkerstraße", - "Wennestieg", - "Wenzelstraße", - "Werdauer Weg", - "Werderstraße", - "Werimboldstraße", - "Werkloh", - "Werkmeisterstraße", - "Werner Hellweg", - "Werner Straße", - "Werner-Petermann-Weg", - "Werrastraße", - "Werswand", - "Werzenkamp", - "Weserstraße", - "Wesselingweg", - "Westbrink", - "Westendorfstraße", - "Westenhellweg", - "Westentor", - "Westerbleichstraße", - "Westererbenstraße, Pottgießerstraße", - "Westerfilder Straße", - "Westerholzstraße", - "Westermannstraße", - "Westerwaldweg", - "Westerwikstraße", - "Westfalendamm", - "Westfaliastraße", - "Westfälische Straße", - "Westhang", - "Westheide", - "Westhofener Straße", - "Westhoffstraße", - "Westholz", - "Westhusener Straße", - "Westicker Straße", - "Westkamp", - "Westricher Dorfstraße", - "Westricher Straße", - "Wetterschachtweg", - "Wetterstraße", - "Wichburgstraße", - "Wichlinghofer Bergstraße", - "Wichlinghofer Markstraße", - "Wickeder Hellweg", - "Wickeder Straße", - "Wicker Heck", - "Widumer Platz", - "Widumer Straße", - "Wieckesweg", - "Wiedbusch", - "Wiedeloh", - "Wielandstraße", - "Wiemerstraße", - "Wiendahlsbank", - "Wienstraße", - "Wieselweg", - "Wiesengrund", - "Wiesenkamp", - "Wiesenstraße", - "Wiesenweg", - "Wiesnerstraße", - "Wiethagenweg", - "Wiggerstraße", - "Wilberstraße", - "Wildbannweg", - "Wildermannstraße", - "Wildrosenstraße", - "Wilhelm-Brand-Straße", - "Wilhelm-Crüwell-Straße", - "Wilhelm-Dilthey-Straße", - "Wilhelm-Dresing-Straße", - "Wilhelm-Kaiser-Weg", - "Wilhelmplatz", - "Wilhelm-Schmidt-Straße", - "Wilhelmshöh", - "Wilhelmstraße", - "Willem-van-Vloten-Straße", - "Williburgstraße", - "Willstätterstraße", - "Willy-Brandt-Platz", - "Willy-Reinke-Straße", - "Wilmsmannstraße", - "Wilsingweg", - "Winandweg", - "Windausstraße", - "Windflügelweg", - "Windhorststraße", - "Windmühlenweg", - "Winkelriedweg", - "Winkelstraße", - "Winkshohlweg", - "Winterfeldtstraße", - "Winterkamp", - "Winterkampweg", - "Winterlindenweg", - "Winzerweg", - "Wipfelweg", - "Wipperkamp", - "Wiscelusweg", - "Wischlinger Weg", - "Wiskottstraße", - "Wißstraße", - "Wittbräucker Straße", - "Wittekindstraße", - "Wittelsbacherstraße", - "Wittener Straße", - "Witthausstraße", - "Wittichstraße", - "Witzlebenstraße", - "Wodanstraße", - "Woerderfeld", - "Woldenmey", - "Wormannsweg", - "Wormsstraße", - "Wörthstraße", - "Wrangelstraße", - "Wulebringweg", - "Wülferichstraße", - "Wulffsweg", - "Wulfgraben", - "Wulfshofstraße", - "Wulfskamp", - "Wunnebergstraße", - "Wupperstraße", - "Württemberger Straße", - "Xaveriweg", - "Yorckstraße", - "Zaunkönigweg", - "Zeche Oespel", - "Zeche-Freiberg-Straße", - "Zeche-Kaiser-Friedrich-Straße", - "Zeche-Margarete-Straße", - "Zeche-Norm-Straße", - "Zechenstraße", - "Zedernweg", - "Zeppelinstraße", - "Zeusweg", - "Zickenbrink", - "Ziegelbrandstraße", - "Ziegelhüttenstraße", - "Ziegelofenweg", - "Ziegelwiese", - "Ziethenstraße", - "Zillestraße", - "Zimmerstraße", - "Zinsweg", - "Zipsstraße", - "Zittauer Straße", - "Zobelweg", - "Zollernstraße", - "Zollvereinstraße", - "Zugstraße", - "Zum Buchenhain", - "Zum Burgkamp", - "Zum Erdbeerfeld", - "Zum Hallenbad", - "Zum Hövelteich", - "Zum Ihnedieck", - "Zum Knapp", - "Zum Lonnenhohl", - "Zum Mühlenheck", - "Zum Nubbental", - "Zum Stadtwald", - "Zum Steigeturm", - "Zum Uhlenbrauck", - "Zum Wäldchen", - "Zünslerweg", - "Zur Hockeneicke", - "Zur Hunnebboke", - "Zweigstraße", - "Zweiwiedenstraße", - "Zwergenweg", - "Zwergweg", - "Zwickauer Straße", - "Zwitschergasse" - }; - - public static readonly string[] Female = - { - "Andrea", - "Angelika", - "Anja", - "Anke", - "Anna", - "Anne", - "Annett", - "Antje", - "Barbara", - "Birgit", - "Brigitte", - "Christin", - "Kristin", - "Christina", - "Claudia", - "Daniela", - "Diana", - "Doreen", - "Franziska", - "Gabriele", - "Heike", - "Ines", - "Jana", - "Janina", - "Jennifer", - "Jessica", - "Jessika", - "Julia", - "Juliane", - "Karin", - "Karolin", - "Katharina", - "Kathrin", - "Katrin", - "Katja", - "Kerstin", - "Laura", - "Lea", - "Lena", - "Lisa", - "Mandy", - "Manuela", - "Maria", - "Marie", - "Marina", - "Martina", - "Melanie", - "Monika", - "Nadine", - "Nicole", - "Petra", - "Sabine", - "Sabrina", - "Sandra", - "Sara", - "Sarah", - "Silke", - "Simone", - "Sophia", - "Sophie", - "Stefanie", - "Stephanie", - "Susanne", - "Tanja", - "Ulrike", - "Ursula", - "Uta", - "Ute", - "Vanessa", - "Yvonne" - }; - - public static readonly string[] Family = - { - "Müller", - "Schmidt", - "Schneider", - "Fischer", - "Weber", - "Meyer", - "Wagner", - "Becker", - "Schulz", - "Hoffmann", - "Schäfer", - "Koch", - "Bauer", - "Richter", - "Klein", - "Wolf", - "Schröder", - "Neumann", - "Schwarz", - "Zimmermann", - "Braun", - "Krüger", - "Hofmann", - "Hartmann", - "Lange", - "Schmitt", - "Werner", - "Schmitz", - "Krause", - "Meier", - "Lehmann", - "Schmid", - "Schulze", - "Maier", - "Köhler", - "Herrmann", - "König", - "Walter", - "Mayer", - "Huber", - "Kaiser", - "Fuchs", - "Peters", - "Lang", - "Scholz", - "Möller", - "Weiß", - "Jung", - "Hahn", - "Schubert", - "Vogel", - "Friedrich", - "Keller", - "Günther", - "Frank", - "Berger", - "Winkler", - "Roth", - "Beck", - "Lorenz", - "Baumann", - "Franke", - "Albrecht", - "Schuster", - "Simon", - "Ludwig", - "Böhm", - "Winter", - "Kraus", - "Martin", - "Schumacher", - "Krämer", - "Vogt", - "Stein", - "Jäger", - "Otto", - "Sommer", - "Groß", - "Seidel", - "Heinrich", - "Brandt", - "Haas", - "Schreiber", - "Graf", - "Schulte", - "Dietrich", - "Ziegler", - "Kuhn", - "Kühn", - "Pohl", - "Engel", - "Horn", - "Busch", - "Bergmann", - "Thomas", - "Voigt", - "Sauer", - "Arnold", - "Wolff", - "Pfeiffer" - }; - - public static readonly string[] Male = - { - "Alexander", - "Andreas", - "Benjamin", - "Bernd", - "Christian", - "Daniel", - "David", - "Dennis", - "Dieter", - "Dirk", - "Dominik", - "Eric", - "Erik", - "Felix", - "Florian", - "Frank", - "Jan", - "Jens", - "Jonas", - "Jörg", - "Jürgen", - "Kevin", - "Klaus", - "Claus", - "Leon", - "Lukas", - "Lucas", - "Marcel", - "Marco", - "Marko", - "Mario", - "Markus", - "Martin", - "Mathias", - "Matthias", - "Max", - "Maximilian", - "Michael", - "Mike", - "Maik", - "Niklas", - "Patrick", - "Paul", - "Peter", - "Philipp", - "Phillipp", - "Ralf", - "Ralph", - "René", - "Robert", - "Sebastian", - "Stefan", - "Stephan", - "Steffen", - "Sven", - "Swen", - "Thomas", - "Thorsten", - "Tim", - "Tobias", - "Tom", - "Torsten", - "Ulrich", - "Uwe", - "Wolfgang" - }; - - public static readonly string[] Departments = - { - "Vertrieb", - "Einkauf", - "IT", - "Kundenbetreuung", - "Geschäftsleitung" - }; - - public static readonly string[] Cities = - { - "Aach (Hegau)", - "Aach", - "Aachen", - "Aalen", - "Abenberg", - "Abensberg", - "Achern", - "Achim (Landkreis Verden)", - "Adelsheim", - "Adenau", - "Adorf/Vogtl.", - "Ahaus", - "Ahlen", - "Ahrensburg", - "Aichach", - "Aichtal", - "Aken (Elbe)", - "Albstadt", - "Alfeld (Leine)", - "Allendorf (Lumda)", - "Allstedt", - "Alpirsbach", - "Alsfeld", - "Alsdorf", - "Alsleben (Saale)", - "Altdorf bei Nürnberg", - "Altena", - "Altenau", - "Altenberg (Erzgebirge)", - "Altenburg", - "Altenkirchen (Westerwald)", - "Altensteig", - "Altentreptow", - "Altlandsberg", - "Altötting", - "Alzenau", - "Alzey", - "Amberg", - "Amöneburg", - "Amorbach", - "Andernach", - "Angermünde", - "Anklam", - "Annaberg-Buchholz", - "Annaburg", - "Annweiler am Trifels", - "Ansbach", - "Apolda", - "Arendsee (Altmark)", - "Arneburg", - "Arnis", - "Arnsberg", - "Arnstadt", - "Arnstein (Unterfranken)", - "Arnstein (Sachsen-Anhalt)", - "Artern/Unstrut", - "Arzberg (Oberfranken)", - "Aschaffenburg", - "Aschersleben", - "Asperg", - "Aßlar", - "Attendorn", - "Aub", - "Aue (Sachsen)", - "Auerbach in der Oberpfalz", - "Auerbach/Vogtl.", - "Augsburg", - "Augustusburg", - "Aulendorf", - "Auma-Weidatal", - "Aurich", - "Babenhausen (Hessen)", - "Bacharach", - "Backnang", - "Bad Aibling", - "Bad Arolsen", - "Bad Belzig", - "Bad Bentheim", - "Bad Bergzabern", - "Bad Berka", - "Bad Berleburg", - "Bad Berneck im Fichtelgebirge", - "Bad Bevensen", - "Bad Bibra", - "Bad Blankenburg", - "Bad Bramstedt", - "Bad Breisig", - "Bad Brückenau", - "Bad Buchau", - "Bad Camberg", - "Bad Colberg-Heldburg", - "Bad Doberan", - "Bad Driburg", - "Bad Düben", - "Bad Dürkheim", - "Bad Dürrenberg", - "Bad Dürrheim", - "Bad Elster", - "Bad Ems", - "Baden-Baden", - "Bad Fallingbostel", - "Bad Frankenhausen/Kyffhäuser", - "Bad Freienwalde (Oder)", - "Bad Friedrichshall", - "Bad Gandersheim", - "Bad Gottleuba-Berggießhübel", - "Bad Griesbach im Rottal", - "Bad Harzburg", - "Bad Herrenalb", - "Bad Hersfeld", - "Bad Homburg vor der Höhe", - "Bad Honnef", - "Bad Hönningen", - "Bad Iburg", - "Bad Karlshafen", - "Bad Kissingen", - "Bad König", - "Bad Königshofen im Grabfeld", - "Bad Köstritz", - "Bad Kötzting", - "Bad Kreuznach", - "Bad Krozingen", - "Bad Laasphe", - "Bad Langensalza", - "Bad Lauchstädt", - "Bad Lausick", - "Bad Lauterberg im Harz", - "Bad Liebenstein (Gemeinde)", - "Bad Liebenwerda", - "Bad Liebenzell", - "Bad Lippspringe", - "Bad Lobenstein", - "Bad Marienberg (Westerwald)", - "Bad Mergentheim", - "Bad Münder am Deister", - "Bad Münster am Stein-Ebernburg", - "Bad Münstereifel", - "Bad Muskau", - "Bad Nauheim", - "Bad Nenndorf", - "Bad Neuenahr-Ahrweiler", - "Bad Neustadt an der Saale", - "Bad Oeynhausen", - "Bad Oldesloe", - "Bad Orb", - "Bad Pyrmont", - "Bad Rappenau", - "Bad Reichenhall", - "Bad Rodach", - "Bad Sachsa", - "Bad Säckingen", - "Bad Salzdetfurth", - "Bad Salzuflen", - "Bad Salzungen", - "Bad Saulgau", - "Bad Schandau", - "Bad Schmiedeberg", - "Bad Schussenried", - "Bad Schwalbach", - "Bad Schwartau", - "Bad Segeberg", - "Bad Sobernheim", - "Bad Soden am Taunus", - "Bad Soden-Salmünster", - "Bad Sooden-Allendorf", - "Bad Staffelstein", - "Bad Sulza", - "Bad Sülze", - "Bad Teinach-Zavelstein", - "Bad Tennstedt", - "Bad Tölz", - "Bad Urach", - "Bad Vilbel", - "Bad Waldsee", - "Bad Wildbad", - "Bad Wildungen", - "Bad Wilsnack", - "Bad Wimpfen", - "Bad Windsheim", - "Bad Wörishofen", - "Bad Wünnenberg", - "Bad Wurzach", - "Baesweiler", - "Baiersdorf", - "Balingen", - "Ballenstedt", - "Balve", - "Bamberg", - "Barby", - "Bargteheide", - "Barmstedt", - "Bärnau", - "Barntrup", - "Barsinghausen", - "Barth", - "Baruth/Mark", - "Bassum", - "Battenberg (Eder)", - "Baumholder", - "Baunach", - "Baunatal", - "Bautzen", - "Bayreuth", - "Bebra", - "Beckum", - "Bedburg", - "Beelitz", - "Beerfelden", - "Beeskow", - "Beilngries", - "Beilstein (Württemberg)", - "Belgern-Schildau", - "Bendorf", - "Bensheim", - "Berching", - "Berga/Elster", - "Bergen (Landkreis Celle)", - "Bergen auf Rügen", - "Bergheim", - "Bergisch Gladbach", - "Bergkamen", - "Bergneustadt", - "Berka/Werra", - "Berlin", - "Bernau bei Berlin", - "Bernburg (Saale)", - "Bernkastel-Kues", - "Bernsdorf (Oberlausitz)", - "Bernstadt a. d. Eigen", - "Bersenbrück", - "Besigheim", - "Betzdorf", - "Betzenstein", - "Beverungen", - "Bexbach", - "Biberach an der Riß", - "Biedenkopf", - "Bielefeld", - "Biesenthal", - "Bietigheim-Bissingen", - "Billerbeck", - "Bingen am Rhein", - "Birkenfeld (Nahe)", - "Bischofsheim an der Rhön", - "Bischofswerda", - "Bismark (Altmark)", - "Bitburg", - "Bitterfeld-Wolfen", - "Blankenburg (Harz)", - "Blankenhain", - "Blaubeuren", - "Bleckede", - "Bleicherode", - "Blieskastel", - "Blomberg", - "Blumberg", - "Bobingen", - "Böblingen", - "Bocholt", - "Bochum", - "Bockenem", - "Bodenwerder", - "Bogen (Stadt)", - "Böhlen (Sachsen)", - "Boizenburg/Elbe", - "Bonn", - "Bonndorf im Schwarzwald", - "Bönnigheim", - "Bopfingen", - "Boppard", - "Borgentreich", - "Borgholzhausen", - "Borken", - "Borken (Hessen)", - "Borkum", - "Borna", - "Bornheim (Rheinland)", - "Bottrop", - "Boxberg (Baden)", - "Brackenheim", - "Brake (Unterweser)", - "Brakel", - "Bramsche", - "Brandenburg an der Havel", - "Brand-Erbisdorf", - "Brandis", - "Braubach", - "Braunfels", - "Braunlage", - "Bräunlingen", - "Braunsbedra", - "Braunschweig", - "Breckerfeld", - "Bredstedt", - "Breisach am Rhein", - "Bremen", - "Bremerhaven", - "Bremervörde", - "Bretten", - "Breuberg", - "Brilon", - "Brotterode-Trusetal", - "Bruchköbel", - "Bruchsal", - "Brück", - "Brüel", - "Brühl (Rheinland)", - "Brunsbüttel", - "Brüssow", - "Buchen (Odenwald)", - "Buchholz in der Nordheide", - "Buchloe", - "Bückeburg", - "Buckow (Märkische Schweiz)", - "Büdelsdorf", - "Büdingen", - "Bühl (Baden)", - "Bünde", - "Büren (Westfalen)", - "Burg (bei Magdeburg)", - "Burgau", - "Burgbernheim", - "Burgdorf (Region Hannover)", - "Bürgel (Thüringen)", - "Burghausen", - "Burgkunstadt", - "Burglengenfeld", - "Burgstädt", - "Burg Stargard", - "Burgwedel", - "Burladingen", - "Burscheid", - "Bürstadt", - "Buttelstedt", - "Buttstädt", - "Butzbach", - "Bützow", - "Buxtehude", - "Calau", - "Calbe (Saale)", - "Calw", - "Castrop-Rauxel", - "Celle", - "Cham (Oberpfalz)", - "Chemnitz", - "Clausthal-Zellerfeld", - "Clingen", - "Cloppenburg", - "Coburg", - "Cochem", - "Coesfeld", - "Colditz", - "Coswig (Sachsen)", - "Coswig (Anhalt)", - "Cottbus", - "Crailsheim", - "Creglingen", - "Creußen", - "Creuzburg", - "Crimmitschau", - "Crivitz", - "Cuxhaven", - "Dachau", - "Dahlen (Sachsen)", - "Dahme/Mark", - "Dahn", - "Damme (Dümmer)", - "Dannenberg (Elbe)", - "Dargun", - "Darmstadt", - "Dassel", - "Dassow", - "Datteln", - "Daun", - "Deggendorf", - "Deidesheim", - "Delbrück", - "Delitzsch", - "Delmenhorst", - "Demmin", - "Dessau-Roßlau", - "Detmold", - "Dettelbach", - "Dieburg", - "Diemelstadt", - "Diepholz", - "Dierdorf", - "Dietenheim", - "Dietfurt an der Altmühl", - "Dietzenbach", - "Diez", - "Dillenburg", - "Dillingen an der Donau", - "Dillingen/Saar", - "Dingelstädt", - "Dingolfing", - "Dinkelsbühl", - "Dinklage", - "Dinslaken", - "Dippoldiswalde", - "Dissen am Teutoburger Wald", - "Ditzingen", - "Döbeln", - "Doberlug-Kirchhain", - "Döbern", - "Dohna", - "Dömitz", - "Dommitzsch", - "Donaueschingen", - "Donauwörth", - "Donzdorf", - "Dorfen", - "Dormagen", - "Dornburg-Camburg", - "Dornhan", - "Dornstetten", - "Dorsten", - "Dortmund", - "Dransfeld", - "Drebkau", - "Dreieich", - "Drensteinfurt", - "Dresden", - "Drolshagen", - "Duderstadt", - "Duisburg", - "Dülmen", - "Düren", - "Düsseldorf", - "Ebeleben", - "Eberbach", - "Ebermannstadt", - "Ebern", - "Ebersbach an der Fils", - "Ebersbach-Neugersdorf", - "Ebersberg", - "Eberswalde", - "Eckartsberga", - "Eckernförde", - "Edenkoben", - "Egeln", - "Eggenfelden", - "Eggesin", - "Ehingen (Donau)", - "Ehrenfriedersdorf", - "Eibelstadt", - "Eibenstock", - "Eichstätt", - "Eilenburg", - "Einbeck", - "Eisenach", - "Eisenberg (Thüringen)", - "Eisenberg (Pfalz)", - "Eisenhüttenstadt", - "Eisfeld", - "Lutherstadt Eisleben", - "Eislingen/Fils", - "Ellingen", - "Ellrich", - "Ellwangen (Jagst)", - "Elmshorn", - "Elsdorf (Rheinland)", - "Elsfleth", - "Elsterberg", - "Elsterwerda", - "Elstra", - "Elterlein", - "Eltmann", - "Eltville am Rhein", - "Elzach", - "Elze", - "Emden", - "Emmelshausen", - "Emmendingen", - "Emmerich am Rhein", - "Emsdetten", - "Endingen am Kaiserstuhl", - "Engen", - "Enger", - "Ennepetal", - "Ennigerloh", - "Eppelheim", - "Eppingen", - "Eppstein", - "Erbach (Donau)", - "Erbach (Odenwald)", - "Erbendorf", - "Erding", - "Erftstadt", - "Erfurt", - "Erkelenz", - "Erkner", - "Erkrath", - "Erlangen", - "Erlenbach am Main", - "Erlensee", - "Erwitte", - "Eschborn", - "Eschenbach in der Oberpfalz", - "Eschershausen", - "Eschwege", - "Eschweiler", - "Esens", - "Espelkamp", - "Essen", - "Esslingen am Neckar", - "Ettenheim", - "Ettlingen", - "Euskirchen", - "Eutin", - "Falkenberg/Elster", - "Falkensee", - "Falkenstein/Harz", - "Falkenstein/Vogtl.", - "Fehmarn", - "Fellbach", - "Felsberg (Hessen)", - "Feuchtwangen", - "Filderstadt", - "Finsterwalde", - "Fladungen", - "Flensburg", - "Flöha", - "Flörsheim am Main", - "Florstadt", - "Forchheim", - "Forchtenberg", - "Forst (Lausitz)", - "Frankenau", - "Frankenberg (Eder)", - "Frankenberg/Sa.", - "Frankenthal (Pfalz)", - "Frankfurt am Main", - "Frankfurt (Oder)", - "Franzburg", - "Frauenstein (Erzgebirge)", - "Frechen", - "Freiberg am Neckar", - "Freiberg", - "Freiburg im Breisgau", - "Freilassing", - "Freinsheim", - "Freising", - "Freital", - "Freren", - "Freudenberg (Baden)", - "Freudenberg (Siegerland)", - "Freudenstadt", - "Freyburg (Unstrut)", - "Freystadt", - "Freyung", - "Fridingen an der Donau", - "Friedberg (Bayern)", - "Friedberg (Hessen)", - "Friedland (Mecklenburg)", - "Friedland (Niederlausitz)", - "Friedrichroda", - "Friedrichsdorf", - "Friedrichshafen", - "Friedrichstadt", - "Friedrichsthal (Saar)", - "Friesack", - "Friesoythe", - "Fritzlar", - "Frohburg", - "Fröndenberg/Ruhr", - "Fulda", - "Fürstenau", - "Fürstenberg/Havel", - "Fürstenfeldbruck", - "Fürstenwalde/Spree", - "Fürth", - "Furth im Wald", - "Furtwangen im Schwarzwald", - "Füssen", - "Gadebusch", - "Gaggenau", - "Gaildorf", - "Gammertingen", - "Garbsen", - "Garching bei München", - "Gardelegen", - "Garding", - "Gartz (Oder)", - "Garz/Rügen", - "Gau-Algesheim", - "Gebesee", - "Gedern", - "Geesthacht", - "Gefell", - "Gefrees", - "Gehrden", - "Gehren", - "Geilenkirchen", - "Geisa", - "Geiselhöring", - "Geisenfeld", - "Geisenheim", - "Geisingen", - "Geislingen (Zollernalbkreis)", - "Geislingen an der Steige", - "Geithain", - "Geldern", - "Gelnhausen", - "Gelsenkirchen", - "Gemünden am Main", - "Gemünden (Wohra)", - "Gengenbach", - "Genthin", - "Georgsmarienhütte", - "Gera", - "Gerabronn", - "Gerbstedt", - "Geretsried", - "Geringswalde", - "Gerlingen", - "Germering", - "Germersheim", - "Gernsbach", - "Gernrode (Harz)", - "Gernsheim", - "Gerolstein", - "Gerolzhofen", - "Gersfeld (Rhön)", - "Gersthofen", - "Gescher", - "Geseke", - "Gevelsberg", - "Geyer", - "Giengen an der Brenz", - "Gießen", - "Gifhorn", - "Ginsheim-Gustavsburg", - "Gladbeck", - "Gladenbach", - "Glashütte (Sachsen)", - "Glauchau", - "Glinde", - "Glücksburg (Ostsee)", - "Glückstadt", - "Gnoien", - "Goch", - "Goldberg (Mecklenburg)", - "Goldkronach", - "Golßen", - "Gommern", - "Göppingen", - "Görlitz", - "Goslar", - "Gößnitz (Thüringen)", - "Gotha", - "Göttingen", - "Grabow (Elde)", - "Grafenau (Niederbayern)", - "Gräfenberg", - "Gräfenhainichen", - "Gräfenthal", - "Grafenwöhr", - "Grafing bei München", - "Gransee", - "Grebenau", - "Grebenstein", - "Greding", - "Greifswald", - "Greiz", - "Greußen", - "Greven", - "Grevenbroich", - "Grevesmühlen", - "Griesheim", - "Grimma", - "Grimmen", - "Gröditz", - "Groitzsch", - "Gronau (Leine)", - "Gronau (Westf.)", - "Gröningen", - "Großalmerode", - "Groß-Bieberau", - "Großbottwar", - "Großbreitenbach", - "Großenehrich", - "Großenhain", - "Groß-Gerau", - "Großräschen", - "Großröhrsdorf", - "Großschirma", - "Groß-Umstadt", - "Grünberg (Hessen)", - "Grünhain-Beierfeld", - "Grünsfeld", - "Grünstadt", - "Guben", - "Gudensberg", - "Güglingen", - "Gummersbach", - "Gundelfingen an der Donau", - "Gundelsheim (Württemberg)", - "Günzburg", - "Gunzenhausen", - "Güsten", - "Güstrow", - "Gütersloh", - "Gützkow", - "Haan", - "Hachenburg", - "Hadamar", - "Hagen", - "Hagenbach", - "Hagenow", - "Haiger", - "Haigerloch", - "Hainichen", - "Haiterbach", - "Halberstadt", - "Haldensleben", - "Halle (Saale)", - "Halle (Westf.)", - "Hallenberg", - "Hallstadt", - "Haltern am See", - "Halver", - "Hamburg", - "Hameln", - "Hamm", - "Hammelburg", - "Hamminkeln", - "Hanau", - "Hannover", - "Hann. Münden", - "Harburg (Schwaben)", - "Hardegsen", - "Haren (Ems)", - "Harsewinkel", - "Hartenstein (Sachsen)", - "Hartha", - "Harzgerode", - "Haselünne", - "Haslach im Kinzigtal", - "Haßfurt", - "Hattersheim am Main", - "Hattingen", - "Hatzfeld (Eder)", - "Hausach", - "Hauzenberg", - "Havelberg", - "Havelsee", - "Hayingen", - "Hechingen", - "Hecklingen", - "Heide (Holstein)", - "Heideck", - "Heidelberg", - "Heidenau (Sachsen)", - "Heidenheim an der Brenz", - "Heilbad Heiligenstadt", - "Heilbronn", - "Heiligenhafen", - "Heiligenhaus", - "Heilsbronn", - "Heimbach (Eifel)", - "Heimsheim", - "Heinsberg", - "Heitersheim", - "Heldrungen", - "Helmbrechts", - "Helmstedt", - "Hemau", - "Hemer", - "Hemmingen (Niedersachsen)", - "Hemmoor", - "Hemsbach", - "Hennef (Sieg)", - "Hennigsdorf", - "Heppenheim (Bergstraße)", - "Herbolzheim", - "Herborn", - "Herbrechtingen", - "Herbstein", - "Herdecke", - "Herdorf", - "Herford", - "Heringen/Helme", - "Heringen (Werra)", - "Hermeskeil", - "Hermsdorf (Thüringen)", - "Herne", - "Herrenberg", - "Herrieden", - "Herrnhut", - "Hersbruck", - "Herten", - "Herzberg am Harz", - "Herzberg (Elster)", - "Herzogenaurach", - "Herzogenrath", - "Hessisch Lichtenau", - "Hessisch Oldendorf", - "Hettingen", - "Hettstedt", - "Heubach", - "Heusenstamm", - "Hilchenbach", - "Hildburghausen", - "Hilden", - "Hildesheim", - "Hillesheim (Eifel)", - "Hilpoltstein", - "Hirschau", - "Hirschberg (Saale)", - "Hirschhorn (Neckar)", - "Hitzacker (Elbe)", - "Hochheim am Main", - "Höchstadt an der Aisch", - "Höchstädt an der Donau", - "Hockenheim", - "Hof (Saale)", - "Hofgeismar", - "Hofheim am Taunus", - "Hofheim in Unterfranken", - "Hohenberg an der Eger", - "Hohenleuben", - "Hohenmölsen", - "Hohen Neuendorf", - "Hohenstein-Ernstthal", - "Hohnstein (Sächsische Schweiz)", - "Höhr-Grenzhausen", - "Hollfeld", - "Holzgerlingen", - "Holzminden", - "Homberg (Efze)", - "Homberg (Ohm)", - "Homburg", - "Horb am Neckar", - "Hornbach", - "Horn-Bad Meinberg", - "Hornberg", - "Hornburg", - "Hörstel", - "Horstmar", - "Höxter", - "Hoya", - "Hoyerswerda", - "Hückelhoven", - "Hückeswagen", - "Hüfingen", - "Hünfeld", - "Hungen", - "Hürth", - "Husum", - "Ibbenbüren", - "Ichenhausen", - "Idar-Oberstein", - "Idstein", - "Illertissen", - "Ilmenau", - "Ilsenburg (Harz)", - "Ilshofen", - "Immenhausen", - "Immenstadt im Allgäu", - "Ingelfingen", - "Ingelheim am Rhein", - "Ingolstadt", - "Iphofen", - "Iserlohn", - "Isny im Allgäu", - "Isselburg", - "Itzehoe", - "Jarmen", - "Jena", - "Jerichow", - "Jessen (Elster)", - "Jever", - "Joachimsthal (Barnim)", - "Johanngeorgenstadt", - "Jöhstadt", - "Jülich", - "Jüterbog", - "Kaarst", - "Kahla", - "Kaisersesch", - "Kaiserslautern", - "Kalbe (Milde)", - "Kalkar", - "Kaltenkirchen", - "Kaltennordheim", - "Kamen", - "Kamenz", - "Kamp-Lintfort", - "Kandel (Pfalz)", - "Kandern", - "Kappeln", - "Karben", - "Karlsruhe", - "Karlstadt", - "Kassel", - "Kastellaun", - "Katzenelnbogen", - "Kaub", - "Kaufbeuren", - "Kehl", - "Kelbra (Kyffhäuser)", - "Kelheim", - "Kelkheim (Taunus)", - "Kellinghusen", - "Kelsterbach", - "Kemberg", - "Kemnath", - "Kempen", - "Kempten (Allgäu)", - "Kenzingen", - "Kerpen", - "Ketzin/Havel", - "Kevelaer", - "Kiel", - "Kierspe", - "Kindelbrück", - "Kirchberg (Sachsen)", - "Kirchberg an der Jagst", - "Kirchberg (Hunsrück)", - "Kirchen (Sieg)", - "Kirchenlamitz", - "Kirchhain", - "Kirchheimbolanden", - "Kirchheim unter Teck", - "Kirn", - "Kirtorf", - "Kitzingen", - "Kitzscher", - "Kleve", - "Klingenberg am Main", - "Klingenthal", - "Klötze", - "Klütz", - "Knittlingen", - "Koblenz", - "Kohren-Sahlis", - "Kolbermoor", - "Kölleda", - "Köln", - "Königsberg in Bayern", - "Königsbrück", - "Königsbrunn", - "Königsee-Rottenbach", - "Königslutter am Elm", - "Königstein im Taunus", - "Königstein (Sächsische Schweiz)", - "Königswinter", - "Königs Wusterhausen", - "Könnern", - "Konstanz", - "Konz", - "Korbach", - "Korntal-Münchingen", - "Kornwestheim", - "Korschenbroich", - "Köthen (Anhalt)", - "Kraichtal", - "Krakow am See", - "Kranichfeld", - "Krautheim (Jagst)", - "Krefeld", - "Kremmen", - "Krempe (Steinburg)", - "Kreuztal", - "Kronach", - "Kronberg im Taunus", - "Kröpelin", - "Kroppenstedt", - "Krumbach (Schwaben)", - "Kühlungsborn", - "Kulmbach", - "Külsheim", - "Künzelsau", - "Kupferberg", - "Kuppenheim", - "Kusel", - "Kyllburg", - "Kyritz", - "Laage", - "Laatzen", - "Ladenburg", - "Lage (Lippe)", - "Lahnstein", - "Lahr/Schwarzwald", - "Laichingen", - "Lambrecht (Pfalz)", - "Lampertheim", - "Landau an der Isar", - "Landau in der Pfalz", - "Landsberg am Lech", - "Landsberg (Saalekreis)", - "Landshut", - "Landstuhl", - "Langelsheim", - "Langen (bei Bremerhaven)", - "Langen (Hessen)", - "Langenau", - "Langenburg", - "Langenfeld (Rheinland)", - "Langenhagen", - "Langenselbold", - "Langenzenn", - "Langewiesen", - "Lassan", - "Laubach", - "Laucha an der Unstrut", - "Lauchhammer", - "Lauchheim", - "Lauda-Königshofen", - "Lauenburg/Elbe", - "Lauf an der Pegnitz", - "Laufen (Salzach)", - "Laufenburg (Baden)", - "Lauffen am Neckar", - "Lauingen (Donau)", - "Laupheim", - "Lauscha", - "Lauta", - "Lauter-Bernsbach", - "Lauterbach (Hessen)", - "Lauterecken", - "Lauterstein", - "Lebach", - "Lebus", - "Leer (Ostfriesland)", - "Lehesten (Thüringer Wald)", - "Lehrte", - "Leichlingen (Rheinland)", - "Leimen (Baden)", - "Leinefelde-Worbis", - "Leinfelden-Echterdingen", - "Leipheim", - "Leipzig", - "Leisnig", - "Lemgo", - "Lengefeld", - "Lengenfeld (Vogtland)", - "Lengerich (Westfalen)", - "Lennestadt", - "Lenzen (Elbe)", - "Leonberg", - "Leun", - "Leuna", - "Leutenberg", - "Leutershausen", - "Leutkirch im Allgäu", - "Leverkusen", - "Lich", - "Lichtenau (Baden)", - "Lichtenau (Westfalen)", - "Lichtenberg (Oberfranken)", - "Lichtenfels (Oberfranken)", - "Lichtenfels (Hessen)", - "Lichtenstein/Sa.", - "Liebenau (Hessen)", - "Liebenwalde", - "Lieberose", - "Liebstadt", - "Limbach-Oberfrohna", - "Limburg an der Lahn", - "Lindau (Bodensee)", - "Linden (Hessen)", - "Lindenberg im Allgäu", - "Lindenfels", - "Lindow (Mark)", - "Lingen (Ems)", - "Linnich", - "Linz am Rhein", - "Lippstadt", - "Löbau", - "Löffingen", - "Lohmar", - "Lohne (Oldenburg)", - "Löhne", - "Lohr am Main", - "Loitz", - "Lollar", - "Lommatzsch", - "Löningen", - "Lorch (Württemberg)", - "Lorch (Rheingau)", - "Lörrach", - "Lorsch", - "Lößnitz (Erzgebirge)", - "Löwenstein", - "Lübbecke", - "Lübben (Spreewald)", - "Lübbenau/Spreewald", - "Lübeck", - "Lübtheen", - "Lübz", - "Lüchow (Wendland)", - "Lucka", - "Luckau", - "Luckenwalde", - "Lüdenscheid", - "Lüdinghausen", - "Ludwigsburg", - "Ludwigsfelde", - "Ludwigshafen am Rhein", - "Ludwigslust", - "Ludwigsstadt", - "Lugau/Erzgeb.", - "Lügde", - "Lüneburg", - "Lünen", - "Lunzenau", - "Lütjenburg", - "Lützen", - "Lychen", - "Magdala", - "Magdeburg", - "Mahlberg", - "Mainbernheim", - "Mainburg", - "Maintal", - "Mainz", - "Malchin", - "Malchow (Mecklenburg)", - "Mannheim", - "Manderscheid", - "Mansfeld", - "Marbach am Neckar", - "Marburg", - "Marienberg", - "Marienmünster", - "Markdorf", - "Markgröningen", - "Märkisch Buchholz", - "Markkleeberg", - "Markneukirchen", - "Markranstädt", - "Marktbreit", - "Marktheidenfeld", - "Marktleuthen", - "Marktoberdorf", - "Marktredwitz", - "Marktsteft", - "Marl", - "Marlow", - "Marne (Holstein)", - "Marsberg", - "Maulbronn", - "Maxhütte-Haidhof", - "Mayen", - "Mechernich", - "Meckenheim (Rheinland)", - "Medebach", - "Meerane", - "Meerbusch", - "Meersburg", - "Meinerzhagen", - "Meiningen", - "Meisenheim", - "Meißen", - "Meldorf", - "Melle", - "Mellrichstadt", - "Melsungen", - "Memmingen", - "Menden (Sauerland)", - "Mendig", - "Mengen", - "Meppen", - "Merkendorf (Mittelfranken)", - "Merseburg", - "Merzig", - "Meschede", - "Meßkirch", - "Meßstetten", - "Mettmann", - "Metzingen", - "Meuselwitz", - "Meyenburg", - "Michelstadt", - "Miesbach", - "Miltenberg", - "Mindelheim", - "Minden", - "Mirow", - "Mittenwalde", - "Mitterteich", - "Mittweida", - "Möckern", - "Möckmühl", - "Moers", - "Mölln", - "Mönchengladbach", - "Monheim am Rhein", - "Monheim (Schwaben)", - "Monschau", - "Montabaur", - "Moosburg an der Isar", - "Mörfelden-Walldorf", - "Moringen", - "Mosbach", - "Mössingen", - "Mücheln (Geiseltal)", - "Mügeln", - "Mühlacker", - "Mühlberg/Elbe", - "Mühldorf am Inn", - "Mühlhausen/Thüringen", - "Mühlheim am Main", - "Mühlheim an der Donau", - "Mülheim an der Ruhr", - "Mülheim-Kärlich", - "Müllheim (Baden)", - "Müllrose", - "Münchberg", - "Müncheberg", - "München", - "Münchenbernsdorf", - "Munderkingen", - "Münnerstadt", - "Münsingen (Württemberg)", - "Munster (Örtze)", - "Münster (Westfalen)", - "Münstermaifeld", - "Münzenberg", - "Murrhardt", - "Mylau", - "Nabburg", - "Nagold (Stadt)", - "Naila", - "Nassau (Lahn)", - "Nastätten", - "Nauen", - "Naumburg (Hessen)", - "Naumburg (Saale)", - "Naunhof", - "Nebra (Unstrut)", - "Neckarbischofsheim", - "Neckargemünd", - "Neckarsteinach", - "Neckarsulm", - "Neresheim", - "Netphen", - "Nettetal", - "Netzschkau", - "Neu-Anspach", - "Neubrandenburg", - "Neubukow", - "Neubulach", - "Neuburg an der Donau", - "Neudenau", - "Neuenbürg", - "Neuenburg am Rhein", - "Neuenhaus", - "Neuenrade", - "Neuenstadt am Kocher", - "Neuenstein (Hohenlohe)", - "Neuerburg", - "Neuffen", - "Neuhaus am Rennweg", - "Neu-Isenburg", - "Neukalen", - "Neukirchen (Knüll)", - "Neukirchen-Vluyn", - "Neukloster", - "Neumark (bei Weimar)", - "Neumarkt in der Oberpfalz", - "Neumarkt-Sankt Veit", - "Neumünster", - "Neunburg vorm Wald", - "Neunkirchen (Saar)", - "Neuötting", - "Neuruppin", - "Neusalza-Spremberg", - "Neusäß", - "Neuss", - "Neustadt an der Aisch", - "Neustadt an der Donau", - "Neustadt an der Waldnaab", - "Neustadt am Kulm", - "Neustadt am Rübenberge", - "Neustadt an der Orla", - "Neustadt an der Weinstraße", - "Neustadt bei Coburg", - "Neustadt (Dosse)", - "Neustadt-Glewe", - "Neustadt (Hessen)", - "Neustadt in Holstein", - "Neustadt in Sachsen", - "Neustrelitz", - "Neutraubling", - "Neu-Ulm", - "Neuwied", - "Nidda", - "Niddatal", - "Nidderau", - "Nideggen", - "Niebüll", - "Niedenstein", - "Niederkassel", - "Niedernhall", - "Nieder-Olm", - "Niederstetten", - "Niederstotzingen", - "Nieheim", - "Niemegk", - "Nienburg (Saale)", - "Nienburg/Weser", - "Nierstein", - "Niesky", - "Nittenau", - "Norden (Ostfriesland)", - "Nordenham", - "Norderney", - "Norderstedt", - "Nordhausen", - "Nordhorn", - "Nördlingen", - "Northeim", - "Nortorf", - "Nossen", - "Nürnberg", - "Nürtingen", - "Oberasbach", - "Oberharz am Brocken", - "Oberhausen", - "Oberhof", - "Oberkirch (Baden)", - "Oberkochen", - "Oberlungwitz", - "Obermoschel", - "Obernburg am Main", - "Oberndorf am Neckar", - "Obernkirchen", - "Ober-Ramstadt", - "Oberriexingen", - "Obertshausen", - "Oberursel (Taunus)", - "Oberviechtach", - "Oberweißbach/Thüringer Wald", - "Oberwesel", - "Oberwiesenthal", - "Ochsenfurt", - "Ochsenhausen", - "Ochtrup", - "Oderberg", - "Oebisfelde-Weferlingen", - "Oederan", - "Oelde", - "Oelsnitz/Erzgeb.", - "Oelsnitz/Vogtl.", - "Oer-Erkenschwick", - "Oerlinghausen", - "Oestrich-Winkel", - "Oettingen in Bayern", - "Offenbach am Main", - "Offenburg", - "Ohrdruf", - "Öhringen", - "Olbernhau", - "Olching", - "Oldenburg (Oldenburg)", - "Oldenburg in Holstein", - "Olfen", - "Olpe", - "Olsberg", - "Oppenau", - "Oppenheim", - "Oranienbaum-Wörlitz", - "Oranienburg", - "Orlamünde", - "Ornbau", - "Ortenberg (Hessen)", - "Ortrand", - "Oschatz", - "Oschersleben (Bode)", - "Osnabrück", - "Osterburg (Altmark)", - "Osterburken", - "Osterfeld (bei Naumburg)", - "Osterhofen", - "Osterholz-Scharmbeck", - "Osterode am Harz", - "Osterwieck", - "Ostfildern", - "Ostheim vor der Rhön", - "Osthofen", - "Östringen", - "Ostritz", - "Otterberg", - "Otterndorf", - "Ottweiler", - "Overath", - "Owen", - "Paderborn", - "Papenburg", - "Pappenheim", - "Parchim", - "Parsberg", - "Pasewalk", - "Passau", - "Pattensen", - "Pausa-Mühltroff", - "Pegau", - "Pegnitz (Stadt)", - "Peine", - "Peitz", - "Penig", - "Penkun", - "Penzberg", - "Penzlin", - "Perleberg", - "Petershagen", - "Pfaffenhofen an der Ilm", - "Pfarrkirchen", - "Pforzheim", - "Pfreimd", - "Pfullendorf", - "Pfullingen", - "Pfungstadt", - "Philippsburg", - "Pinneberg", - "Pirmasens", - "Pirna", - "Plattling", - "Plau am See", - "Plaue", - "Plauen", - "Plettenberg", - "Pleystein", - "Plochingen", - "Plön", - "Pocking", - "Pohlheim", - "Polch", - "Porta Westfalica (Stadt)", - "Pößneck", - "Potsdam", - "Pottenstein (Oberfranken)", - "Preetz", - "Premnitz", - "Prenzlau", - "Pressath", - "Preußisch Oldendorf", - "Prichsenstadt", - "Pritzwalk", - "Prüm", - "Puchheim", - "Pulheim", - "Pulsnitz", - "Putbus", - "Putlitz", - "Püttlingen", - "Quakenbrück", - "Quedlinburg", - "Querfurt", - "Quickborn", - "Rabenau (Sachsen)", - "Radeberg", - "Radebeul", - "Radeburg", - "Radevormwald", - "Radolfzell am Bodensee", - "Raguhn-Jeßnitz", - "Rahden", - "Rain (Lech)", - "Ramstein-Miesenbach", - "Ranis", - "Ransbach-Baumbach", - "Rastatt", - "Rastenberg", - "Rathenow", - "Ratingen", - "Ratzeburg", - "Rauenberg", - "Raunheim", - "Rauschenberg", - "Ravensburg", - "Ravenstein", - "Recklinghausen", - "Rees", - "Regen (Stadt)", - "Regensburg", - "Regis-Breitingen", - "Rehau", - "Rehburg-Loccum", - "Rehna", - "Reichelsheim (Wetterau)", - "Reichenbach im Vogtland", - "Reichenbach/O.L.", - "Reinbek", - "Reinfeld (Holstein)", - "Reinheim", - "Remagen", - "Remda-Teichel", - "Remscheid", - "Remseck am Neckar", - "Renchen", - "Rendsburg", - "Rennerod", - "Renningen", - "Rerik", - "Rethem (Aller)", - "Reutlingen", - "Rheda-Wiedenbrück", - "Rhede", - "Rheinau (Baden)", - "Rheinbach", - "Rheinberg", - "Rheinböllen", - "Rheine", - "Rheinfelden (Baden)", - "Rheinsberg", - "Rheinstetten", - "Rhens", - "Rhinow", - "Ribnitz-Damgarten", - "Richtenberg", - "Riedenburg", - "Riedlingen", - "Riedstadt", - "Rieneck", - "Riesa", - "Rietberg", - "Rinteln", - "Röbel/Müritz", - "Rochlitz", - "Rockenhausen", - "Rodalben", - "Rodenberg", - "Rödental", - "Rödermark", - "Rodewisch", - "Rodgau", - "Roding", - "Römhild", - "Romrod", - "Ronneburg (Thüringen)", - "Ronnenberg", - "Rosbach vor der Höhe", - "Rosenfeld", - "Rosenheim", - "Rosenthal (Hessen)", - "Rösrath", - "Roßleben", - "Roßwein", - "Rostock", - "Rotenburg an der Fulda", - "Rotenburg (Wümme)", - "Roth", - "Rötha", - "Röthenbach an der Pegnitz", - "Rothenburg/Oberlausitz", - "Rothenburg ob der Tauber", - "Rothenfels", - "Rottenburg am Neckar", - "Rottenburg an der Laaber", - "Röttingen", - "Rottweil", - "Rötz", - "Rüdesheim am Rhein", - "Rudolstadt", - "Ruhla", - "Ruhland", - "Runkel", - "Rüsselsheim", - "Rutesheim", - "Rüthen", - "Saalburg-Ebersdorf", - "Saalfeld/Saale", - "Saarbrücken", - "Saarburg", - "Saarlouis", - "Sachsenhagen", - "Sachsenheim", - "Salzgitter", - "Salzkotten", - "Salzwedel", - "Sandau (Elbe)", - "Sandersdorf-Brehna", - "Sangerhausen", - "Sankt Augustin", - "Sankt Goar", - "Sankt Goarshausen", - "Sarstedt", - "Sassenberg", - "Sassnitz", - "Sayda", - "Schalkau", - "Schauenstein", - "Scheer", - "Scheibenberg", - "Scheinfeld", - "Schelklingen", - "Schenefeld (Kreis Pinneberg)", - "Scheßlitz", - "Schieder-Schwalenberg", - "Schifferstadt", - "Schillingsfürst", - "Schiltach", - "Schirgiswalde-Kirschau", - "Schkeuditz", - "Schkölen", - "Schleiden", - "Schleiz", - "Schleswig", - "Schlettau", - "Schleusingen", - "Schlieben", - "Schlitz (Vogelsbergkreis)", - "Schloß Holte-Stukenbrock", - "Schlotheim", - "Schlüchtern", - "Schlüsselfeld", - "Schmalkalden", - "Schmallenberg", - "Schmölln", - "Schnackenburg", - "Schnaittenbach", - "Schneeberg (Erzgebirge)", - "Schneverdingen", - "Schömberg (Zollernalbkreis)", - "Schönau (Odenwald)", - "Schönau im Schwarzwald", - "Schönberg (Mecklenburg)", - "Schönebeck (Elbe)", - "Schöneck/Vogtl.", - "Schönewalde", - "Schongau", - "Schöningen", - "Schönsee", - "Schönwald (Bayern)", - "Schopfheim", - "Schöppenstedt", - "Schorndorf", - "Schortens", - "Schotten (Stadt)", - "Schramberg", - "Schraplau", - "Schriesheim", - "Schrobenhausen", - "Schrozberg", - "Schüttorf", - "Schwaan", - "Schwabach", - "Schwäbisch Gmünd", - "Schwäbisch Hall", - "Schwabmünchen", - "Schwaigern", - "Schwalbach am Taunus", - "Schwalmstadt", - "Schwandorf", - "Schwanebeck", - "Schwarzenbach am Wald", - "Schwarzenbach an der Saale", - "Schwarzenbek", - "Schwarzenberg/Erzgeb.", - "Schwarzenborn (Knüll)", - "Schwarzheide", - "Schwedt/Oder", - "Schweich", - "Schweinfurt", - "Schwelm", - "Schwentinental", - "Schwerin", - "Schwerte", - "Schwetzingen", - "Sebnitz", - "Seehausen (Altmark)", - "Seeland (Sachsen-Anhalt)", - "Seelow", - "Seelze", - "Seesen", - "Sehnde", - "Seifhennersdorf", - "Selb", - "Selbitz (Oberfranken)", - "Seligenstadt", - "Selm", - "Selters (Westerwald)", - "Senden (Bayern)", - "Sendenhorst", - "Senftenberg", - "Seßlach", - "Siegburg", - "Siegen", - "Sigmaringen", - "Simbach am Inn", - "Simmern/Hunsrück", - "Sindelfingen", - "Singen (Hohentwiel)", - "Sinsheim", - "Sinzig", - "Soest", - "Solingen", - "Solms", - "Soltau", - "Sömmerda", - "Sondershausen", - "Sonneberg", - "Sonnewalde", - "Sonthofen", - "Sontra", - "Spaichingen", - "Spalt", - "Spangenberg", - "Speicher (Eifel)", - "Spenge", - "Speyer", - "Spremberg", - "Springe", - "Sprockhövel", - "Stade", - "Stadtallendorf", - "Stadtbergen", - "Stadthagen", - "Stadtilm", - "Stadtlengsfeld", - "Stadtlohn", - "Stadtoldendorf", - "Stadtprozelten", - "Stadtroda", - "Stadtsteinach", - "Stadt Wehlen", - "Starnberg", - "Staßfurt", - "Staufen im Breisgau", - "Staufenberg (Hessen)", - "Stavenhagen", - "St. Blasien", - "Stein (Mittelfranken)", - "Steinach (Thüringen)", - "Steinau an der Straße", - "Steinbach-Hallenberg", - "Steinbach (Taunus)", - "Steinfurt", - "Steinheim an der Murr", - "Steinheim (Westfalen)", - "Stendal", - "Sternberg", - "St. Ingbert", - "St. Georgen im Schwarzwald", - "Stockach", - "Stolberg (Rhld.)", - "Stollberg/Erzgeb.", - "Stolpen", - "Storkow (Mark)", - "Stößen", - "Straelen", - "Stralsund", - "Strasburg (Uckermark)", - "Straubing", - "Strausberg", - "Strehla", - "Stromberg (Hunsrück)", - "Stühlingen", - "Stutensee", - "Stuttgart", - "St. Wendel", - "Suhl", - "Sulingen", - "Sulz am Neckar", - "Sulzbach/Saar", - "Sulzbach-Rosenberg", - "Sulzburg", - "Sundern (Sauerland)", - "Südliches Anhalt", - "Süßen", - "Syke", - "Tambach-Dietharz", - "Tangerhütte", - "Tangermünde", - "Tann (Rhön)", - "Tanna", - "Tauberbischofsheim", - "Taucha", - "Taunusstein", - "Tecklenburg", - "Tegernsee (Stadt)", - "Telgte", - "Teltow", - "Templin", - "Tengen", - "Tessin (bei Rostock)", - "Teterow", - "Tettnang", - "Teublitz", - "Teuchern", - "Teupitz", - "Teuschnitz", - "Thale", - "Thalheim/Erzgeb.", - "Thannhausen (Schwaben)", - "Tharandt", - "Themar", - "Thum", - "Tirschenreuth", - "Titisee-Neustadt", - "Tittmoning", - "Todtnau", - "Töging am Inn", - "Tönisvorst", - "Tönning", - "Torgau", - "Torgelow", - "Tornesch", - "Traben-Trarbach", - "Traunreut", - "Traunstein", - "Trebbin", - "Trebsen/Mulde", - "Treffurt", - "Trendelburg", - "Treuchtlingen", - "Treuen", - "Treuenbrietzen", - "Triberg im Schwarzwald", - "Tribsees", - "Trier", - "Triptis", - "Trochtelfingen", - "Troisdorf", - "Trossingen", - "Trostberg", - "Tübingen", - "Tuttlingen", - "Twistringen", - "Übach-Palenberg", - "Überlingen", - "Uebigau-Wahrenbrück", - "Ueckermünde", - "Uelzen", - "Uetersen", - "Uffenheim", - "Uhingen", - "Ulm", - "Ulmen (Eifel)", - "Ulrichstein", - "Ummerstadt", - "Unkel", - "Unna", - "Unterschleißheim", - "Usedom (Stadt)", - "Usingen", - "Uslar", - "Vacha", - "Vaihingen an der Enz", - "Vallendar", - "Varel", - "Vechta", - "Velbert", - "Velburg", - "Velden (Pegnitz)", - "Vellberg", - "Velen", - "Vellmar", - "Velten", - "Verden (Aller)", - "Veringenstadt", - "Verl", - "Versmold", - "Vetschau/Spreewald", - "Viechtach", - "Vienenburg", - "Viernheim", - "Viersen", - "Villingen-Schwenningen", - "Vilsbiburg", - "Vilseck", - "Vilshofen an der Donau", - "Visselhövede", - "Vlotho", - "Voerde (Niederrhein)", - "Vogtsburg im Kaiserstuhl", - "Vohburg an der Donau", - "Vohenstrauß", - "Vöhrenbach", - "Vöhringen (Iller)", - "Volkach", - "Völklingen", - "Volkmarsen", - "Vreden", - "Wachenheim an der Weinstraße", - "Wächtersbach", - "Wadern", - "Waghäusel", - "Wahlstedt", - "Waiblingen", - "Waibstadt", - "Waischenfeld", - "Waldbröl", - "Waldeck (Stadt)", - "Waldenbuch", - "Waldenburg (Sachsen)", - "Waldenburg (Württemberg)", - "Waldershof", - "Waldheim", - "Waldkappel", - "Waldkirch", - "Waldkirchen", - "Waldkraiburg", - "Waldmünchen", - "Waldsassen", - "Waldshut-Tiengen", - "Walldorf (Baden)", - "Walldürn", - "Wallenfels", - "Walsrode", - "Waltershausen", - "Waltrop", - "Wanfried", - "Wangen im Allgäu", - "Wanzleben-Börde", - "Warburg", - "Waren (Müritz)", - "Warendorf", - "Warin", - "Warstein", - "Wassenberg", - "Wasserburg am Inn", - "Wassertrüdingen", - "Wasungen", - "Wedel", - "Weener", - "Wegberg", - "Wegeleben", - "Wehr (Baden)", - "Weida", - "Weiden in der Oberpfalz", - "Weikersheim", - "Weil am Rhein", - "Weilburg", - "Weil der Stadt", - "Weilheim an der Teck", - "Weilheim in Oberbayern", - "Weimar", - "Weingarten (Württemberg)", - "Weinheim", - "Weinsberg", - "Weinstadt", - "Weismain", - "Weißenberg", - "Weißenburg in Bayern", - "Weißenfels", - "Weißenhorn", - "Weißensee (Thüringen)", - "Weißenstadt", - "Weißenthurm", - "Weißwasser/Oberlausitz", - "Weiterstadt", - "Welzheim", - "Welzow", - "Wemding", - "Wendlingen am Neckar", - "Werben (Elbe)", - "Werdau", - "Werder (Havel)", - "Werdohl", - "Werl", - "Wermelskirchen", - "Wernau (Neckar)", - "Werne", - "Werneuchen", - "Wernigerode", - "Wertheim", - "Werther (Westf.)", - "Wertingen", - "Wesel", - "Wesenberg (Mecklenburg)", - "Wesselburen", - "Wesseling", - "Westerburg", - "Westerstede", - "Wetter (Ruhr)", - "Wetter (Hessen)", - "Wettin-Löbejün", - "Wetzlar", - "Widdern", - "Wiehe", - "Wiehl", - "Wiesbaden", - "Wiesmoor", - "Wiesensteig", - "Wiesloch", - "Wildau", - "Wildberg (Schwarzwald)", - "Wildemann", - "Wildenfels", - "Wildeshausen", - "Wilhelmshaven", - "Wilkau-Haßlau", - "Willebadessen", - "Willich", - "Wilsdruff", - "Wilster", - "Wilthen", - "Windischeschenbach", - "Windsbach", - "Winnenden", - "Winsen (Luhe)", - "Winterberg", - "Wipperfürth", - "Wirges", - "Wismar", - "Wissen (Stadt)", - "Witten", - "Lutherstadt Wittenberg", - "Wittenberge", - "Wittenburg", - "Wittichenau", - "Wittlich", - "Wittingen", - "Wittmund", - "Wittstock/Dosse", - "Witzenhausen", - "Woldegk", - "Wolfach", - "Wolfenbüttel", - "Wolfhagen", - "Wolframs-Eschenbach", - "Wolfratshausen", - "Wolfsburg", - "Wolfstein", - "Wolgast", - "Wolkenstein (Erzgebirge)", - "Wolmirstedt", - "Worms", - "Wörrstadt", - "Wörth am Rhein", - "Wörth an der Donau", - "Wörth am Main", - "Wriezen", - "Wülfrath", - "Wunsiedel", - "Wunstorf", - "Wuppertal", - "Würselen", - "Wurzbach", - "Würzburg", - "Wurzen", - "Wustrow (Wendland)", - "Wyk auf Föhr", - "Xanten", - "Zahna-Elster", - "Zarrentin am Schaalsee", - "Zehdenick", - "Zeil am Main", - "Zeitz", - "Zell am Harmersbach", - "Zell im Wiesental", - "Zell (Mosel)", - "Zella-Mehlis", - "Zerbst/Anhalt", - "Zeulenroda-Triebes", - "Zeven", - "Ziegenrück", - "Zierenberg", - "Ziesar", - "Zirndorf", - "Zittau", - "Zörbig", - "Zossen", - "Zschopau", - "Zülpich", - "Zweibrücken", - "Zwenkau", - "Zwickau", - "Zwiesel", - "Zwingenberg (Bergstraße)", - "Zwönitz" - }; - - public static readonly string[] Countries = - { - "Austria", - "Germany", - "Switzerland" - }; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/Numbers.cs b/src/abstractions/Backend.Fx/RandomData/Numbers.cs deleted file mode 100644 index 182cd7c8..00000000 --- a/src/abstractions/Backend.Fx/RandomData/Numbers.cs +++ /dev/null @@ -1,5298 +0,0 @@ -using System.Globalization; -using System.Linq; - -namespace Backend.Fx.RandomData -{ - public static class Numbers - { - public static readonly string[] Ciphers = Enumerable.Range(0, 10).Select(i => i.ToString()).ToArray(); - public static readonly string[] PostalCodes = Enumerable.Range(9000, 90999).Select(i => i.ToString("00000")).ToArray(); - - public static readonly string[] LandLineNetworks = - { - "0201", - "0202", - "0203", - "02041", - "02043", - "02045", - "02051", - "02052", - "02053", - "02054", - "02056", - "02058", - "02064", - "02065", - "02066", - "0208", - "0209", - "02102", - "02103", - "02104", - "0211", - "0212", - "02129", - "02131", - "02132", - "02133", - "02137", - "0214", - "02150", - "02151", - "02152", - "02153", - "02154", - "02156", - "02157", - "02158", - "02159", - "02161", - "02162", - "02163", - "02164", - "02165", - "02166", - "02171", - "02173", - "02174", - "02175", - "02181", - "02182", - "02183", - "02191", - "02192", - "02193", - "02195", - "02196", - "02202", - "02203", - "02204", - "02205", - "02206", - "02207", - "02208", - "0221", - "02222", - "02223", - "02224", - "02225", - "02226", - "02227", - "02228", - "02232", - "02233", - "02234", - "02235", - "02236", - "02237", - "02238", - "02241", - "02242", - "02243", - "02244", - "02245", - "02246", - "02247", - "02248", - "02251", - "02252", - "02253", - "02254", - "02255", - "02256", - "02257", - "02261", - "02262", - "02263", - "02264", - "02265", - "02266", - "02267", - "02268", - "02269", - "02271", - "02272", - "02273", - "02274", - "02275", - "0228", - "02291", - "02292", - "02293", - "02294", - "02295", - "02296", - "02297", - "02301", - "02302", - "02303", - "02304", - "02305", - "02306", - "02307", - "02308", - "02309", - "0231", - "02323", - "02324", - "02325", - "02327", - "02330", - "02331", - "02332", - "02333", - "02334", - "02335", - "02336", - "02337", - "02338", - "02339", - "0234", - "02351", - "02352", - "02353", - "02354", - "02355", - "02357", - "02358", - "02359", - "02360", - "02361", - "02362", - "02363", - "02364", - "02365", - "02366", - "02367", - "02368", - "02369", - "02371", - "02372", - "02373", - "02374", - "02375", - "02377", - "02378", - "02379", - "02381", - "02382", - "02383", - "02384", - "02385", - "02387", - "02388", - "02389", - "02391", - "02392", - "02393", - "02394", - "02395", - "02401", - "02402", - "02403", - "02404", - "02405", - "02406", - "02407", - "02408", - "02409", - "0241", - "02421", - "02422", - "02423", - "02424", - "02425", - "02426", - "02427", - "02428", - "02429", - "02431", - "02432", - "02433", - "02434", - "02435", - "02436", - "02440", - "02441", - "02443", - "02444", - "02445", - "02446", - "02447", - "02448", - "02449", - "02451", - "02452", - "02453", - "02454", - "02455", - "02456", - "02461", - "02462", - "02463", - "02464", - "02465", - "02471", - "02472", - "02473", - "02474", - "02482", - "02484", - "02485", - "02486", - "02501", - "02502", - "02504", - "02505", - "02506", - "02507", - "02508", - "02509", - "0251", - "02520", - "02521", - "02522", - "02523", - "02524", - "02525", - "02526", - "02527", - "02528", - "02529", - "02532", - "02533", - "02534", - "02535", - "02536", - "02538", - "02541", - "02542", - "02543", - "02545", - "02546", - "02547", - "02548", - "02551", - "02552", - "02553", - "02554", - "02555", - "02556", - "02557", - "02558", - "02561", - "02562", - "02563", - "02564", - "02565", - "02566", - "02567", - "02568", - "02571", - "02572", - "02573", - "02574", - "02575", - "02581", - "02582", - "02583", - "02584", - "02585", - "02586", - "02587", - "02588", - "02590", - "02591", - "02592", - "02593", - "02594", - "02595", - "02596", - "02597", - "02598", - "02599", - "02601", - "02602", - "02603", - "02604", - "02605", - "02606", - "02607", - "02608", - "0261", - "02620", - "02621", - "02622", - "02623", - "02624", - "02625", - "02626", - "02627", - "02628", - "02630", - "02631", - "02632", - "02633", - "02634", - "02635", - "02636", - "02637", - "02638", - "02639", - "02641", - "02642", - "02643", - "02644", - "02645", - "02646", - "02647", - "02651", - "02652", - "02653", - "02654", - "02655", - "02656", - "02657", - "02661", - "02662", - "02663", - "02664", - "02666", - "02667", - "02671", - "02672", - "02673", - "02674", - "02675", - "02676", - "02677", - "02678", - "02680", - "02681", - "02682", - "02683", - "02684", - "02685", - "02686", - "02687", - "02688", - "02689", - "02691", - "02692", - "02693", - "02694", - "02695", - "02696", - "02697", - "0271", - "02721", - "02722", - "02723", - "02724", - "02725", - "02732", - "02733", - "02734", - "02735", - "02736", - "02737", - "02738", - "02739", - "02741", - "02742", - "02743", - "02744", - "02745", - "02747", - "02750", - "02751", - "02752", - "02753", - "02754", - "02755", - "02758", - "02759", - "02761", - "02762", - "02763", - "02764", - "02770", - "02771", - "02772", - "02773", - "02774", - "02775", - "02776", - "02777", - "02778", - "02779", - "02801", - "02802", - "02803", - "02804", - "0281", - "02821", - "02822", - "02823", - "02824", - "02825", - "02826", - "02827", - "02828", - "02831", - "02832", - "02833", - "02834", - "02835", - "02836", - "02837", - "02838", - "02839", - "02841", - "02842", - "02843", - "02844", - "02845", - "02850", - "02851", - "02852", - "02853", - "02855", - "02856", - "02857", - "02858", - "02859", - "02861", - "02862", - "02863", - "02864", - "02865", - "02866", - "02867", - "02871", - "02872", - "02873", - "02874", - "02902", - "02903", - "02904", - "02905", - "0291", - "02921", - "02922", - "02923", - "02924", - "02925", - "02927", - "02928", - "02931", - "02932", - "02933", - "02934", - "02935", - "02937", - "02938", - "02941", - "02942", - "02943", - "02944", - "02945", - "02947", - "02948", - "02951", - "02952", - "02953", - "02954", - "02955", - "02957", - "02958", - "02961", - "02962", - "02963", - "02964", - "02971", - "02972", - "02973", - "02974", - "02975", - "02977", - "02981", - "02982", - "02983", - "02984", - "02985", - "02991", - "02992", - "02993", - "02994", - "030", - "03301", - "03302", - "03303", - "03304", - "033051", - "033053", - "033054", - "033055", - "033056", - "03306", - "03307", - "033080", - "033082", - "033083", - "033084", - "033085", - "033086", - "033087", - "033088", - "033089", - "033093", - "033094", - "0331", - "033200", - "033201", - "033202", - "033203", - "033204", - "033205", - "033206", - "033207", - "033208", - "033209", - "03321", - "03322", - "033230", - "033231", - "033232", - "033233", - "033234", - "033235", - "033237", - "033238", - "033239", - "03327", - "03328", - "03329", - "03331", - "03332", - "033331", - "033332", - "033333", - "033334", - "033335", - "033336", - "033337", - "033338", - "03334", - "03335", - "033361", - "033362", - "033363", - "033364", - "033365", - "033366", - "033367", - "033368", - "033369", - "03337", - "03338", - "033393", - "033394", - "033395", - "033396", - "033397", - "033398", - "03341", - "03342", - "033432", - "033433", - "033434", - "033435", - "033436", - "033437", - "033438", - "033439", - "03344", - "033451", - "033452", - "033454", - "033456", - "033457", - "033458", - "03346", - "033470", - "033472", - "033473", - "033474", - "033475", - "033476", - "033477", - "033478", - "033479", - "0335", - "033601", - "033602", - "033603", - "033604", - "033605", - "033606", - "033607", - "033608", - "033609", - "03361", - "03362", - "033631", - "033632", - "033633", - "033634", - "033635", - "033636", - "033637", - "033638", - "03364", - "033652", - "033653", - "033654", - "033655", - "033656", - "033657", - "03366", - "033671", - "033672", - "033673", - "033674", - "033675", - "033676", - "033677", - "033678", - "033679", - "033701", - "033702", - "033703", - "033704", - "033708", - "03371", - "03372", - "033731", - "033732", - "033733", - "033734", - "033741", - "033742", - "033743", - "033744", - "033745", - "033746", - "033747", - "033748", - "03375", - "033760", - "033762", - "033763", - "033764", - "033765", - "033766", - "033767", - "033768", - "033769", - "03377", - "03378", - "03379", - "03381", - "03382", - "033830", - "033831", - "033832", - "033833", - "033834", - "033835", - "033836", - "033837", - "033838", - "033839", - "033841", - "033843", - "033844", - "033845", - "033846", - "033847", - "033848", - "033849", - "03385", - "03386", - "033870", - "033872", - "033873", - "033874", - "033875", - "033876", - "033877", - "033878", - "03391", - "033920", - "033921", - "033922", - "033923", - "033924", - "033925", - "033926", - "033928", - "033929", - "033931", - "033932", - "033933", - "03394", - "03395", - "033962", - "033963", - "033964", - "033965", - "033966", - "033967", - "033968", - "033969", - "033970", - "033971", - "033972", - "033973", - "033974", - "033975", - "033976", - "033977", - "033978", - "033979", - "033981", - "033982", - "033983", - "033984", - "033986", - "033989", - "0340", - "0341", - "034202", - "034203", - "034204", - "034205", - "034206", - "034207", - "034208", - "03421", - "034221", - "034222", - "034223", - "034224", - "03423", - "034241", - "034242", - "034243", - "034244", - "03425", - "034261", - "034262", - "034263", - "034291", - "034292", - "034293", - "034294", - "034295", - "034296", - "034297", - "034298", - "034299", - "03431", - "034321", - "034322", - "034324", - "034325", - "034327", - "034328", - "03433", - "034341", - "034342", - "034343", - "034344", - "034345", - "034346", - "034347", - "034348", - "03435", - "034361", - "034362", - "034363", - "034364", - "03437", - "034381", - "034382", - "034383", - "034384", - "034385", - "034386", - "03441", - "034422", - "034423", - "034424", - "034425", - "034426", - "03443", - "034441", - "034443", - "034444", - "034445", - "034446", - "03445", - "034461", - "034462", - "034463", - "034464", - "034465", - "034466", - "034467", - "03447", - "03448", - "034491", - "034492", - "034493", - "034494", - "034495", - "034496", - "034497", - "034498", - "0345", - "034600", - "034601", - "034602", - "034603", - "034604", - "034605", - "034606", - "034607", - "034609", - "03461", - "03462", - "034632", - "034633", - "034635", - "034636", - "034637", - "034638", - "034639", - "03464", - "034651", - "034652", - "034653", - "034654", - "034656", - "034658", - "034659", - "03466", - "034671", - "034672", - "034673", - "034691", - "034692", - "03471", - "034721", - "034722", - "03473", - "034741", - "034742", - "034743", - "034745", - "034746", - "03475", - "03476", - "034771", - "034772", - "034773", - "034774", - "034775", - "034776", - "034779", - "034781", - "034782", - "034783", - "034785", - "034901", - "034903", - "034904", - "034905", - "034906", - "034907", - "034909", - "03491", - "034920", - "034921", - "034922", - "034923", - "034924", - "034925", - "034926", - "034927", - "034928", - "034929", - "03493", - "03494", - "034953", - "034954", - "034955", - "034956", - "03496", - "034973", - "034975", - "034976", - "034977", - "034978", - "034979", - "03501", - "035020", - "035021", - "035022", - "035023", - "035024", - "035025", - "035026", - "035027", - "035028", - "035032", - "035033", - "03504", - "035052", - "035053", - "035054", - "035055", - "035056", - "035057", - "035058", - "0351", - "035200", - "035201", - "035202", - "035203", - "035204", - "035205", - "035206", - "035207", - "035208", - "035209", - "03521", - "03522", - "03523", - "035240", - "035241", - "035242", - "035243", - "035244", - "035245", - "035246", - "035247", - "035248", - "035249", - "03525", - "035263", - "035264", - "035265", - "035266", - "035267", - "035268", - "03528", - "03529", - "03531", - "035322", - "035323", - "035324", - "035325", - "035326", - "035327", - "035329", - "03533", - "035341", - "035342", - "035343", - "03535", - "035361", - "035362", - "035363", - "035364", - "035365", - "03537", - "035383", - "035384", - "035385", - "035386", - "035387", - "035388", - "035389", - "03541", - "03542", - "035433", - "035434", - "035435", - "035436", - "035439", - "03544", - "035451", - "035452", - "035453", - "035454", - "035455", - "035456", - "03546", - "035471", - "035472", - "035473", - "035474", - "035475", - "035476", - "035477", - "035478", - "0355", - "035600", - "035601", - "035602", - "035603", - "035604", - "035605", - "035606", - "035607", - "035608", - "035609", - "03561", - "03562", - "03563", - "03564", - "035691", - "035692", - "035693", - "035694", - "035695", - "035696", - "035697", - "035698", - "03571", - "035722", - "035723", - "035724", - "035725", - "035726", - "035727", - "035728", - "03573", - "03574", - "035751", - "035752", - "035753", - "035754", - "035755", - "035756", - "03576", - "035771", - "035772", - "035773", - "035774", - "035775", - "03578", - "035792", - "035793", - "035795", - "035796", - "035797", - "03581", - "035820", - "035822", - "035823", - "035825", - "035826", - "035827", - "035828", - "035829", - "03583", - "035841", - "035842", - "035843", - "035844", - "03585", - "03586", - "035872", - "035873", - "035874", - "035875", - "035876", - "035877", - "03588", - "035891", - "035892", - "035893", - "035894", - "035895", - "03591", - "03592", - "035930", - "035931", - "035932", - "035933", - "035934", - "035935", - "035936", - "035937", - "035938", - "035939", - "03594", - "035951", - "035952", - "035953", - "035954", - "035955", - "03596", - "035971", - "035973", - "035974", - "035975", - "03601", - "036020", - "036021", - "036022", - "036023", - "036024", - "036025", - "036026", - "036027", - "036028", - "036029", - "03603", - "036041", - "036042", - "036043", - "03605", - "03606", - "036071", - "036072", - "036074", - "036075", - "036076", - "036077", - "036081", - "036082", - "036083", - "036084", - "036085", - "036087", - "0361", - "036200", - "036201", - "036202", - "036203", - "036204", - "036205", - "036206", - "036207", - "036208", - "036209", - "03621", - "03622", - "03623", - "03624", - "036252", - "036253", - "036254", - "036255", - "036256", - "036257", - "036258", - "036259", - "03628", - "03629", - "03631", - "03632", - "036330", - "036331", - "036332", - "036333", - "036334", - "036335", - "036336", - "036337", - "036338", - "03634", - "03635", - "03636", - "036370", - "036371", - "036372", - "036373", - "036374", - "036375", - "036376", - "036377", - "036378", - "036379", - "03641", - "036421", - "036422", - "036423", - "036424", - "036425", - "036426", - "036427", - "036428", - "03643", - "03644", - "036450", - "036451", - "036452", - "036453", - "036454", - "036458", - "036459", - "036461", - "036462", - "036463", - "036464", - "036465", - "03647", - "036481", - "036482", - "036483", - "036484", - "0365", - "036601", - "036602", - "036603", - "036604", - "036605", - "036606", - "036607", - "036608", - "03661", - "036621", - "036622", - "036623", - "036624", - "036625", - "036626", - "036628", - "03663", - "036640", - "036642", - "036643", - "036644", - "036645", - "036646", - "036647", - "036648", - "036649", - "036651", - "036652", - "036653", - "036691", - "036692", - "036693", - "036694", - "036695", - "036701", - "036702", - "036703", - "036704", - "036705", - "03671", - "03672", - "036730", - "036731", - "036732", - "036733", - "036734", - "036735", - "036736", - "036737", - "036738", - "036739", - "036741", - "036742", - "036743", - "036744", - "03675", - "036761", - "036762", - "036764", - "036766", - "03677", - "036781", - "036782", - "036783", - "036784", - "036785", - "03679.", - "03681", - "03682", - "03683", - "036840", - "036841", - "036842", - "036843", - "036844", - "036845", - "036846", - "036847", - "036848", - "036849", - "03685", - "03686", - "036870", - "036871", - "036873", - "036874", - "036875", - "036878", - "03691", - "036920", - "036921", - "036922", - "036923", - "036924", - "036925", - "036926", - "036927", - "036928", - "036929", - "03693", - "036940", - "036941", - "036943", - "036944", - "036945", - "036946", - "036947", - "036948", - "036949", - "03695", - "036961", - "036962", - "036963", - "036964", - "036965", - "036966", - "036967", - "036968", - "036969", - "0371", - "037200", - "037202", - "037203", - "037204", - "037206", - "037207", - "037208", - "037209", - "03721", - "03722", - "03723", - "03724", - "03725", - "03726", - "03727", - "037291", - "037292", - "037293", - "037294", - "037295", - "037296", - "037297", - "037298", - "03731", - "037320", - "037321", - "037322", - "037323", - "037324", - "037325", - "037326", - "037327", - "037328", - "037329", - "03733", - "037341", - "037342", - "037343", - "037344", - "037346", - "037347", - "037348", - "037349", - "03735", - "037360", - "037361", - "037362", - "037363", - "037364", - "037365", - "037366", - "037367", - "037368", - "037369", - "03737", - "037381", - "037382", - "037383", - "037384", - "03741", - "037421", - "037422", - "037423", - "037430", - "037431", - "037432", - "037433", - "037434", - "037435", - "037436", - "037437", - "037438", - "037439", - "03744", - "03745", - "037462", - "037463", - "037464", - "037465", - "037467", - "037468", - "0375", - "037600", - "037601", - "037602", - "037603", - "037604", - "037605", - "037606", - "037607", - "037608", - "037609", - "03761", - "03762", - "03763", - "03764", - "03765", - "03771", - "03772", - "03773", - "03774", - "037752", - "037754", - "037755", - "037756", - "037757", - "0381", - "038201", - "038202", - "038203", - "038204", - "038205", - "038206", - "038207", - "038208", - "038209", - "03821", - "038220", - "038221", - "038222", - "038223", - "038224", - "038225", - "038226", - "038227", - "038228", - "038229", - "038231", - "038232", - "038233", - "038234", - "038292", - "038293", - "038294", - "038295", - "038296", - "038297", - "038300", - "038301", - "038302", - "038303", - "038304", - "038305", - "038306", - "038307", - "038308", - "038309", - "03831", - "038320", - "038321", - "038322", - "038323", - "038324", - "038325", - "038326", - "038327", - "038328", - "038331", - "038332", - "038333", - "038334", - "03834", - "038351", - "038352", - "038353", - "038354", - "038355", - "038356", - "03836", - "038370", - "038371", - "038372", - "038373", - "038374", - "038375", - "038376", - "038377", - "038378,", - "038379", - "03838", - "038391", - "038392", - "038393", - "03841", - "038422", - "038423", - "038424", - "038425", - "038426", - "038427", - "038428", - "038429", - "03843", - "03844", - "038450", - "038451", - "038452", - "038453", - "038454", - "038455", - "038456", - "038457", - "038458", - "038459", - "038461", - "038462", - "038464", - "038466", - "03847", - "038481", - "038482", - "038483", - "038484", - "038485", - "038486", - "038488", - "0385", - "03860", - "03861", - "03863", - "03865", - "03866", - "03867", - "03868", - "03869", - "03871", - "038720", - "038721", - "038722", - "038723", - "038724", - "038725", - "038726", - "038727", - "038728", - "038729", - "038731", - "038732", - "038733", - "038735", - "038736", - "038737", - "038738", - "03874", - "038750", - "038751", - "038752", - "038753", - "038754", - "038755", - "038756", - "038757", - "038758", - "038759", - "03876", - "03877", - "038780", - "038781", - "038782", - "038783", - "038784", - "038785", - "038787", - "038788", - "038789", - "038791", - "038792", - "038793", - "038794", - "038796", - "038797", - "03881", - "038821", - "038822.", - "038823", - "038824", - "038825", - "038826", - "038827", - "038828", - "03883", - "038841", - "038842", - "038843", - "038844", - "038845", - "038847", - "038848", - "038850", - "038851", - "038852", - "038853", - "038854", - "038855", - "038856", - "038858", - "038859", - "03886", - "038871", - "038872", - "038873", - "038874", - "038875", - "038876", - "039000", - "039001", - "039002", - "039003", - "039004", - "039005", - "039006", - "039007", - "039008", - "039009", - "03901", - "03902", - "039030", - "039031", - "039032", - "039033", - "039034", - "039035", - "039036", - "039037", - "039038", - "039039", - "03904", - "039050", - "039051", - "039052", - "039053", - "039054", - "039055", - "039056", - "039057", - "039058", - "039059", - "039061", - "039062", - "03907", - "039080", - "039081", - "039082", - "039083", - "039084", - "039085", - "039086", - "039087", - "039088", - "039089", - "03909", - "0391", - "039200", - "039201", - "039202", - "039203", - "039204", - "039205", - "039206", - "039207", - "039208", - "039209", - "03921", - "039221", - "039222", - "039223", - "039224", - "039225", - "039226", - "03923", - "039241", - "039242", - "039243", - "039244", - "039245", - "039246", - "039247", - "039248", - "03925", - "039262", - "039263", - "039264", - "039265", - "039266", - "039267", - "039268", - "03928", - "039291", - "039292", - "039293", - "039294", - "039295", - "039296", - "039297", - "039298", - "03931", - "039320", - "039321", - "039322", - "039323", - "039324", - "039325", - "039327", - "039328", - "039329", - "03933", - "039341", - "039342", - "039343", - "039344", - "039345", - "039346", - "039347", - "039348", - "039349", - "03935", - "039361", - "039362", - "039363", - "039364", - "039365", - "039366", - "03937", - "039382", - "039383", - "039384", - "039386", - "039387", - "039388", - "039389", - "039390", - "039391", - "039392", - "039393", - "039394", - "039395", - "039396", - "039397", - "039398", - "039399", - "039400", - "039401", - "039402", - "039403", - "039404", - "039405", - "039406", - "039407", - "039408", - "039409", - "03941", - "039421", - "039422", - "039423", - "039424", - "039425", - "039426", - "039427", - "039428", - "03943", - "03944", - "039451", - "039452", - "039453", - "039454", - "039455", - "039456", - "039457", - "039458", - "039459", - "03946", - "03947", - "039481", - "039482", - "039483", - "039484", - "039485", - "039487", - "039488", - "039489", - "03949", - "0395", - "039600", - "039601", - "039602", - "039603", - "039604", - "039605", - "039606", - "039607", - "039608", - "03961", - "03962", - "03963", - "03964", - "03965", - "03966", - "03967", - "03968", - "03969", - "03971", - "039721", - "039722", - "039723", - "039724", - "039726", - "039727", - "039728", - "03973", - "039740", - "039741", - "039742", - "039743", - "039744", - "039745", - "039746", - "039747", - "039748", - "039749", - "039751", - "039752", - "039753", - "039754", - "03976", - "039771", - "039772", - "039773", - "039774", - "039775", - "039776", - "039777", - "039778", - "039779", - "03981", - "039820", - "039821", - "039822", - "039823", - "039824", - "039825", - "039826", - "039827", - "039828", - "039829", - "039831", - "039832", - "039833", - "03984", - "039851", - "039852", - "039853", - "039854", - "039855", - "039856", - "039857", - "039858", - "039859", - "039861", - "039862", - "039863", - "03987", - "039881", - "039882", - "039883", - "039884", - "039885", - "039886", - "039887", - "039888", - "039889", - "03991", - "039921", - "039922", - "039923", - "039924", - "039925", - "039926", - "039927", - "039928", - "039929", - "039931", - "039932", - "039933", - "039934", - "03994", - "039951", - "039952", - "039953", - "039954", - "039955", - "039956", - "039957", - "039959", - "03996", - "039971", - "039972", - "039973", - "039975", - "039976", - "039977", - "039978", - "03998", - "039991", - "039992", - "039993", - "039994", - "039995", - "039996", - "039997", - "039998", - "039999", - "040", - "040", - "040", - "040", - "040", - "04101", - "04102", - "04103", - "04104", - "04105", - "04106", - "04107", - "04108", - "04109", - "04120", - "04121", - "04122", - "04123", - "04124", - "04125", - "04126", - "04127", - "04128", - "04129", - "04131", - "04132", - "04133", - "04134", - "04135", - "04136", - "04137", - "04138", - "04139", - "04140", - "04141", - "04142", - "04143", - "04144", - "04146", - "04148", - "04149", - "04151", - "04152", - "04153", - "04154", - "04155", - "04156", - "04158", - "04159", - "04161", - "04162", - "04163", - "04164", - "04165", - "04166", - "04167", - "04168", - "04169", - "04171", - "04172", - "04173", - "04174", - "04175", - "04176", - "04177", - "04178", - "04179", - "04180", - "04181", - "04182", - "04183", - "04184", - "04185", - "04186", - "04187", - "04188", - "04189", - "04191", - "04192", - "04193", - "04194", - "04195", - "04202", - "04203", - "04204", - "04205", - "04206", - "04207", - "04208", - "04209", - "0421", - "04221", - "04222", - "04223", - "04224", - "04230", - "04231", - "04232", - "04233", - "04234", - "04235", - "04236", - "04237", - "04238", - "04239", - "04240", - "04241", - "04242", - "04243", - "04244", - "04245", - "04246", - "04247", - "04248", - "04249", - "04251", - "04252", - "04253", - "04254", - "04255", - "04256", - "04257", - "04258", - "04260", - "04261", - "04262", - "04263", - "04264", - "04265", - "04266", - "04267", - "04268", - "04269", - "04271", - "04272", - "04273", - "04274", - "04275", - "04276", - "04277", - "04281", - "04282", - "04283", - "04284", - "04285", - "04286", - "04287", - "04288", - "04289", - "04292", - "04293", - "04294", - "04295", - "04296", - "04297", - "04298", - "04302", - "04303", - "04305", - "04307", - "04308", - "0431", - "04320", - "04321", - "04322", - "04323", - "04324", - "04326", - "04327", - "04328", - "04329", - "04330", - "04331", - "04332", - "04333", - "04334", - "04335", - "04336", - "04337", - "04338", - "04339", - "04340", - "04342", - "04343", - "04344", - "04346", - "04347", - "04348", - "04349", - "04351", - "04352", - "04353", - "04354", - "04355", - "04356", - "04357", - "04358", - "04361", - "04362", - "04363", - "04364", - "04365", - "04366", - "04367", - "04371", - "04372", - "04381", - "04382", - "04383", - "04384", - "04385", - "04392", - "04393", - "04394", - "04401", - "04402", - "04403", - "04404", - "04405", - "04406", - "04407", - "04408", - "04409", - "0441", - "04421", - "04422", - "04423", - "04425", - "04426", - "04431", - "04432", - "04433", - "04434", - "04435", - "04441", - "04442", - "04443", - "04444", - "04445", - "04446", - "04447", - "04451", - "04452", - "04453", - "04454", - "04455", - "04456", - "04458", - "04461", - "04462", - "04463", - "04464", - "04465", - "04466", - "04467", - "04468", - "04469", - "04471", - "04472", - "04473", - "04474", - "04475", - "04477", - "04478", - "04479", - "04480", - "04481", - "04482", - "04483", - "04484", - "04485", - "04486", - "04487", - "04488", - "04489", - "04491", - "04492", - "04493", - "04494", - "04495", - "04496", - "04497", - "04498", - "04499", - "04501", - "04502", - "04503", - "04504", - "04505", - "04506", - "04508", - "04509", - "0451", - "04521", - "04522", - "04523", - "04524", - "04525", - "04526", - "04527", - "04528", - "04529", - "04531", - "04532", - "04533", - "04534", - "04535", - "04536", - "04537", - "04539", - "04541", - "04542", - "04543", - "04544", - "04545", - "04546", - "04547", - "04550", - "04551", - "04552", - "04553", - "04554", - "04555", - "04556", - "04557", - "04558", - "04559", - "04561", - "04562", - "04563", - "04564", - "04602", - "04603", - "04604", - "04605", - "04606", - "04607", - "04608", - "04609", - "0461", - "04621", - "04622", - "04623", - "04624", - "04625", - "04626", - "04627", - "04630", - "04631", - "04632", - "04633", - "04634", - "04635", - "04636", - "04637", - "04638", - "04639", - "04641", - "04642", - "04643", - "04644", - "04646", - "04651", - "04661", - "04662", - "04663", - "04664", - "04665", - "04666", - "04667", - "04668", - "04671", - "04672", - "04673", - "04674", - "04681", - "04682", - "04683", - "04684", - "04702", - "04703", - "04704", - "04705", - "04706", - "04707", - "04708", - "0471", - "04721", - "04722", - "04723", - "04724", - "04725", - "04731", - "04732", - "04733", - "04734", - "04735", - "04736", - "04737", - "04740", - "04741", - "04742", - "04743", - "04744", - "04745", - "04746", - "04747", - "04748", - "04749", - "04751", - "04752", - "04753", - "04754", - "04755", - "04756", - "04757", - "04758", - "04761", - "04762", - "04763", - "04764", - "04765", - "04766", - "04767", - "04768", - "04769", - "04770", - "04771", - "04772", - "04773", - "04774", - "04775", - "04776", - "04777", - "04778", - "04779", - "04791", - "04792", - "04793", - "04794", - "04795", - "04796", - "04802", - "04803", - "04804", - "04805", - "04806", - "0481", - "04821", - "04822", - "04823", - "04824", - "04825", - "04826", - "04827", - "04828", - "04829", - "04830", - "04832", - "04833", - "04834", - "04835", - "04836", - "04837", - "04838", - "04839", - "04841", - "04842", - "04843", - "04844", - "04845", - "04846", - "04847", - "04848", - "04849", - "04851", - "04852", - "04853", - "04854", - "04855", - "04856", - "04857", - "04858", - "04859", - "04861", - "04862", - "04863", - "04864", - "04865", - "04871", - "04872", - "04873", - "04874", - "04875", - "04876", - "04877", - "04881", - "04882", - "04883", - "04884", - "04885", - "04892", - "04893", - "04902", - "04903", - "0491", - "04920", - "04921", - "04922", - "04923", - "04924", - "04925", - "04926", - "04927", - "04928", - "04929", - "04931", - "04932", - "04933", - "04934", - "04935", - "04936", - "04938", - "04939", - "04941", - "04942", - "04943", - "04944", - "04945", - "04946", - "04947", - "04948", - "04950", - "04951", - "04952", - "04953", - "04954", - "04955", - "04956", - "04957", - "04958", - "04959", - "04961", - "04962", - "04963", - "04964", - "04965", - "04966", - "04967", - "04968", - "04971", - "04972", - "04973", - "04974", - "04975", - "04976", - "04977", - "05021", - "05022", - "05023", - "05024", - "05025", - "05026", - "05027", - "05028", - "05031", - "05032", - "05033", - "05034", - "05035", - "05036", - "05037", - "05041", - "05042", - "05043", - "05044", - "05045", - "05051", - "05052", - "05053", - "05054", - "05055", - "05056", - "05060", - "05062", - "05063", - "05064", - "05065", - "05066", - "05067", - "05068", - "05069", - "05071", - "05072", - "05073", - "05074", - "05082", - "05083", - "05084", - "05085", - "05086", - "05101", - "05102", - "05103", - "05105", - "05108", - "05109", - "0511", - "05121", - "05123", - "05126", - "05127", - "05128", - "05129", - "05130", - "05131", - "05132", - "05135", - "05136", - "05137", - "05138", - "05139", - "05141", - "05142", - "05143", - "05144", - "05145", - "05146", - "05147", - "05148", - "05149", - "05151", - "05152", - "05153", - "05154", - "05155", - "05156", - "05157", - "05158", - "05159", - "05161", - "05162", - "05163", - "05164", - "05165", - "05166", - "05167", - "05168", - "05171", - "05172", - "05173", - "05174", - "05175", - "05176", - "05177", - "05181", - "05182", - "05183", - "05184", - "05185", - "05186", - "05187", - "05190", - "05191", - "05192", - "05193", - "05194", - "05195", - "05196", - "05197", - "05198", - "05199", - "05201", - "05202", - "05203", - "05204", - "05205", - "05206", - "05207", - "05208", - "05209", - "0521", - "05221", - "05222", - "05223", - "05224", - "05225", - "05226", - "05228", - "05231", - "05232", - "05233", - "05234", - "05235", - "05236", - "05237", - "05238", - "05241", - "05242", - "05244", - "05245", - "05246", - "05247", - "05248", - "05250", - "05251", - "05252", - "05253", - "05254", - "05255", - "05257", - "05258", - "05259", - "05261", - "05262", - "05263", - "05264", - "05265", - "05266", - "05271", - "05272", - "05273", - "05274", - "05275", - "05276", - "05277", - "05278", - "05281", - "05282", - "05283", - "05284", - "05285", - "05286", - "05292", - "05293", - "05294", - "05295", - "05300", - "05301", - "05302", - "05303", - "05304", - "05305", - "05306", - "05307", - "05308", - "05309", - "0531", - "05320", - "05321", - "05322", - "05323", - "05324", - "05325", - "05326", - "05327", - "05328", - "05329", - "05331", - "05332", - "05333", - "05334", - "05335", - "05336", - "05337", - "05339", - "05341", - "05344", - "05345", - "05346", - "05347", - "05351", - "05352", - "05353", - "05354", - "05355", - "05356", - "05357", - "05358", - "05361", - "05362", - "05363", - "05364", - "05365", - "05366", - "05367", - "05368", - "05371", - "05372", - "05373", - "05374", - "05375", - "05376", - "05377", - "05378", - "05379", - "05381", - "05382", - "05383", - "05384", - "05401", - "05402", - "05403", - "05404", - "05405", - "05406", - "05407", - "05409", - "0541", - "05421", - "05422", - "05423", - "05424", - "05425", - "05426", - "05427", - "05428", - "05429", - "05431", - "05432", - "05433", - "05434", - "05435", - "05436", - "05437", - "05438", - "05439", - "05441", - "05442", - "05443", - "05444", - "05445", - "05446", - "05447", - "05448", - "05451", - "05452", - "05453", - "05454", - "05455", - "05456", - "05457", - "05458", - "05459", - "05461", - "05462", - "05464", - "05465", - "05466", - "05467", - "05468", - "05471", - "05472", - "05473", - "05474", - "05475", - "05476", - "05481", - "05482", - "05483", - "05484", - "05485", - "05491", - "05492", - "05493", - "05494", - "05495", - "05502", - "05503", - "05504", - "05505", - "05506", - "05507", - "05508", - "05509", - "0551", - "05520", - "05521", - "05522", - "05523", - "05524", - "05525", - "05527", - "05528", - "05529", - "05531", - "05532", - "05533", - "05534", - "05535", - "05536", - "05541", - "05542", - "05543", - "05544", - "05545", - "05546", - "05551", - "05552", - "05553", - "05554", - "05555", - "05556", - "05561", - "05562", - "05563", - "05564", - "05565", - "05571", - "05572", - "05573", - "05574", - "05582", - "05583", - "05584", - "05585", - "05586", - "05592", - "05593", - "05594", - "05601", - "05602", - "05603", - "05604", - "05605", - "05606", - "05607", - "05608", - "05609", - "0561", - "05621", - "05622", - "05623", - "05624", - "05625", - "05626", - "05631", - "05632", - "05633", - "05634", - "05635", - "05636", - "05641", - "05642", - "05643", - "05644", - "05645", - "05646", - "05647", - "05648", - "05650", - "05651", - "05652", - "05653", - "05654", - "05655", - "05656", - "05657", - "05658", - "05659", - "05661", - "05662", - "05663", - "05664", - "05665", - "05671", - "05672", - "05673", - "05674", - "05675", - "05676", - "05677", - "05681", - "05682", - "05683", - "05684", - "05685", - "05686", - "05691", - "05692", - "05693", - "05694", - "05695", - "05696", - "05702", - "05703", - "05704", - "05705", - "05706", - "05707", - "0571", - "05721", - "05722", - "05723", - "05724", - "05725", - "05726", - "05731", - "05732", - "05733", - "05734", - "05741", - "05742", - "05743", - "05744", - "05745", - "05746", - "05751", - "05752", - "05753", - "05754", - "05755", - "05761", - "05763", - "05764", - "05765", - "05766", - "05767", - "05768", - "05769", - "05771", - "05772", - "05773", - "05774", - "05775", - "05776", - "05777", - "05802", - "05803", - "05804", - "05805", - "05806", - "05807", - "05808", - "0581", - "05820", - "05821", - "05822", - "05823", - "05824", - "05825", - "05826", - "05827", - "05828", - "05829", - "05831", - "05832", - "05833", - "05834", - "05835", - "05836", - "05837", - "05838", - "05839", - "05840", - "05841", - "05842", - "05843", - "05844", - "05845", - "05846", - "05848", - "05849", - "05850", - "05851", - "05852", - "05853", - "05854", - "05855", - "05857", - "05858", - "05859", - "05861", - "05862", - "05863", - "05864", - "05865", - "05872", - "05873", - "05874", - "05875", - "05882", - "05883", - "05901", - "05902", - "05903", - "05904", - "05905", - "05906", - "05907", - "05908", - "05909", - "0591", - "05921", - "05922", - "05923", - "05924", - "05925", - "05926", - "05931", - "05932", - "05933", - "05934", - "05935", - "05936", - "05937", - "05939", - "05941", - "05942", - "05943", - "05944", - "05945", - "05946", - "05947", - "05948", - "05951", - "05952", - "05953", - "05954", - "05955", - "05956", - "05957", - "05961", - "05962", - "05963", - "05964", - "05965", - "05966", - "05971", - "05973", - "05975", - "05976", - "05977", - "05978", - "06002", - "06003", - "06004", - "06007", - "06008", - "06020", - "06021", - "06022", - "06023", - "06024", - "06026", - "06027", - "06028", - "06029", - "06031", - "06032", - "06033", - "06034", - "06035", - "06036", - "06039", - "06041", - "06042", - "06043", - "06044", - "06045", - "06046", - "06047", - "06048", - "06049", - "06050", - "06051", - "06052", - "06053", - "06054", - "06055", - "06056", - "06057", - "06058", - "06059", - "06061", - "06062", - "06063", - "06066", - "06068", - "06071", - "06073", - "06074", - "06078", - "06081", - "06082", - "06083", - "06084", - "06085", - "06086", - "06087", - "06092", - "06093", - "06094", - "06095", - "06096", - "06101", - "06102", - "06103", - "06104", - "06105", - "06106", - "06107", - "06108", - "06109", - "0611", - "06120", - "06122", - "06123", - "06124", - "06126", - "06127", - "06128", - "06129", - "06130", - "06131", - "06132", - "06133", - "06134", - "06135", - "06136", - "06138", - "06139", - "06142", - "06144", - "06145", - "06146", - "06147", - "06150", - "06151", - "06152", - "06154", - "06155", - "06157", - "06158", - "06159", - "06161", - "06162", - "06163", - "06164", - "06165", - "06166", - "06167", - "06171", - "06172", - "06173", - "06174", - "06175", - "06181", - "06182", - "06183", - "06184", - "06185", - "06186", - "06187", - "06188", - "06190", - "06192", - "06195", - "06196", - "06198", - "06201", - "06202", - "06203", - "06204", - "06205", - "06206", - "06207", - "06209", - "0621", - "0621", - "06220", - "06221", - "06222", - "06223", - "06224", - "06226", - "06227", - "06228", - "06229", - "06231", - "06232", - "06233", - "06234", - "06235", - "06236", - "06237", - "06238", - "06239", - "06241", - "06242", - "06243", - "06244", - "06245", - "06246", - "06247", - "06249", - "06251", - "06252", - "06253", - "06254", - "06255", - "06256", - "06257", - "06258", - "06261", - "06262", - "06263", - "06264", - "06265", - "06266", - "06267", - "06268", - "06269", - "06271", - "06272", - "06274", - "06275", - "06276", - "06281", - "06282", - "06283", - "06284", - "06285", - "06286", - "06287", - "06291", - "06292", - "06293", - "06294", - "06295", - "06296", - "06297", - "06298", - "06301", - "06302", - "06303", - "06304", - "06305", - "06306", - "06307", - "06308", - "0631", - "06321", - "06322", - "06323", - "06324", - "06325", - "06326", - "06327", - "06328", - "06329", - "06331", - "06332", - "06333", - "06334", - "06335", - "06336", - "06337", - "06338", - "06339", - "06340", - "06341", - "06342", - "06343", - "06344", - "06345", - "06346", - "06347", - "06348", - "06349", - "06351", - "06352", - "06353", - "06355", - "06356", - "06357", - "06358", - "06359", - "06361", - "06362", - "06363", - "06364", - "06371", - "06372", - "06373", - "06374", - "06375", - "06381", - "06382", - "06383", - "06384", - "06385", - "06386", - "06387", - "06391", - "06392", - "06393", - "06394", - "06395", - "06396", - "06397", - "06398", - "06400", - "06401", - "06402", - "06403", - "06404", - "06405", - "06406", - "06407", - "06408", - "06409", - "0641", - "06420", - "06421", - "06422", - "06423", - "06424", - "06425", - "06426", - "06427", - "06428", - "06429", - "06430", - "06431", - "06432", - "06433", - "06434", - "06435", - "06436", - "06438", - "06439", - "06440", - "06441", - "06442", - "06443", - "06444", - "06445", - "06446", - "06447", - "06449", - "06451", - "06452", - "06453", - "06454", - "06455", - "06456", - "06457", - "06458", - "06461", - "06462", - "06464", - "06465", - "06466", - "06467", - "06468", - "06471", - "06472", - "06473", - "06474", - "06475", - "06476", - "06477", - "06478", - "06479", - "06482", - "06483", - "06484", - "06485", - "06486", - "06500", - "06501", - "06502", - "06503", - "06504", - "06505", - "06506", - "06507", - "06508", - "06509", - "0651", - "06522", - "06523", - "06524", - "06525", - "06526", - "06527", - "06531", - "06532", - "06533", - "06534", - "06535", - "06536", - "06541", - "06542", - "06543", - "06544", - "06545", - "06550", - "06551", - "06552", - "06553", - "06554", - "06555", - "06556", - "06557", - "06558", - "06559", - "06561", - "06562", - "06563", - "06564", - "06565", - "06566", - "06567", - "06568", - "06569", - "06571", - "06572", - "06573", - "06574", - "06575", - "06578", - "06580", - "06581", - "06582", - "06583", - "06584", - "06585", - "06586", - "06587", - "06588", - "06589", - "06591", - "06592", - "06593", - "06594", - "06595", - "06596", - "06597", - "06599", - "0661", - "06620", - "06621", - "06622", - "06623", - "06624", - "06625", - "06626", - "06627", - "06628", - "06629", - "06630", - "06631", - "06633", - "06634", - "06635", - "06636", - "06637", - "06638", - "06639", - "06641", - "06642", - "06643", - "06644", - "06645", - "06646", - "06647", - "06648", - "06650", - "06651", - "06652", - "06653", - "06654", - "06655", - "06656", - "06657", - "06658", - "06659", - "06660", - "06661", - "06663", - "06664", - "06665", - "06666", - "06667", - "06668", - "06669", - "06670", - "06672", - "06673", - "06674", - "06675", - "06676", - "06677", - "06678", - "06681", - "06682", - "06683", - "06684", - "06691", - "06692", - "06693", - "06694", - "06695", - "06696", - "06697", - "06698", - "06701", - "06703", - "06704", - "06706", - "06707", - "06708", - "06709", - "0671", - "06721", - "06722", - "06723", - "06724", - "06725", - "06726", - "06727", - "06728", - "06731", - "06732", - "06733", - "06734", - "06735", - "06736", - "06737", - "06741", - "06742", - "06743", - "06744", - "06745", - "06746", - "06747", - "06751", - "06752", - "06753", - "06754", - "06755", - "06756", - "06757", - "06758", - "06761", - "06762", - "06763", - "06764", - "06765", - "06766", - "06771", - "06772", - "06773", - "06774", - "06775", - "06776", - "06781", - "06782", - "06783", - "06784", - "06785", - "06786", - "06787", - "06788", - "06789", - "06802", - "06803", - "06804", - "06805", - "06806", - "06809", - "0681", - "06821", - "06824", - "06825", - "06826", - "06827", - "06831", - "06832", - "06833", - "06834", - "06835", - "06836", - "06837", - "06838", - "06841", - "06842", - "06843", - "06844", - "06848", - "06849", - "06851", - "06852", - "06853", - "06854", - "06855", - "06856", - "06857", - "06858", - "06861", - "06864", - "06865", - "06866", - "06867", - "06868", - "06869", - "06871", - "06872", - "06873", - "06874", - "06875", - "06876", - "06881", - "06887", - "06888", - "06893", - "06894", - "06897", - "06898", - "069", - "069", - "07021", - "07022", - "07023", - "07024", - "07025", - "07026", - "07031", - "07032", - "07033", - "07034", - "07041", - "07042", - "07043", - "07044", - "07045", - "07046", - "07051", - "07052", - "07053", - "07054", - "07055", - "07056", - "07062", - "07063", - "07066", - "07071", - "07072", - "07073", - "07081", - "07082", - "07083", - "07084", - "07085", - "0711", - "0711", - "07121", - "07122", - "07123", - "07124", - "07125", - "07126", - "07127", - "07128", - "07129", - "07130", - "07131", - "07132", - "07133", - "07134", - "07135", - "07136", - "07138", - "07139", - "07141", - "07142", - "07143", - "07144", - "07145", - "07146", - "07147", - "07148", - "07150", - "07151", - "07152", - "07153", - "07153", - "07154", - "07156", - "07157", - "07158", - "07159", - "07161", - "07162", - "07163", - "07164", - "07165", - "07166", - "07171", - "07172", - "07173", - "07174", - "07175", - "07176", - "07181", - "07182", - "07183", - "07184", - "07191", - "07192", - "07193", - "07194", - "07195", - "07202", - "07203", - "07204", - "0721", - "07220", - "07221", - "07222", - "07223", - "07224", - "07225", - "07226", - "07227", - "07228", - "07229", - "07231", - "07232", - "07233", - "07234", - "07235", - "07236", - "07237", - "07240", - "07242", - "07243", - "07244", - "07245", - "07246", - "07247", - "07248", - "07249", - "07250", - "07251", - "07252", - "07253", - "07254", - "07255", - "07256", - "07257", - "07258", - "07259", - "07260", - "07261", - "07262", - "07263", - "07264", - "07265", - "07266", - "07267", - "07268", - "07269", - "07271", - "07272", - "07273", - "07274", - "07275", - "07276", - "07277", - "07300", - "07302", - "07303", - "07304", - "07305", - "07306", - "07307", - "07308", - "07309", - "0731", - "07321", - "07322", - "07323", - "07324", - "07325", - "07326", - "07327", - "07328", - "07329", - "07331", - "07332", - "07333", - "07334", - "07335", - "07336", - "07337", - "07340", - "07343", - "07344", - "07345", - "07346", - "07347", - "07348", - "07351", - "07352", - "07353", - "07354", - "07355", - "07356", - "07357", - "07358", - "07361", - "07362", - "07363", - "07364", - "07365", - "07366", - "07367", - "07371", - "07373", - "07374", - "07375", - "07376", - "07381", - "07382", - "07383", - "07384", - "07385", - "07386", - "07387", - "07388", - "07389", - "07391", - "07392", - "07393", - "07394", - "07395", - "07402", - "07403", - "07404", - "0741", - "07420", - "07422", - "07423", - "07424", - "07425", - "07426", - "07427", - "07428", - "07429", - "07431", - "07432", - "07433", - "07434", - "07435", - "07436", - "07440", - "07441", - "07442", - "07443", - "07444", - "07445", - "07446", - "07447", - "07448", - "07449", - "07451", - "07452", - "07453,", - "07454", - "07455", - "07456", - "07457", - "07458", - "07459", - "07461", - "07462", - "07463", - "07464", - "07465", - "07466", - "07467", - "07471", - "07472", - "07473", - "07474", - "07475", - "07476", - "07477", - "07478", - "07482", - "07483", - "07484", - "07485", - "07486", - "07502", - "07503", - "07504", - "07505", - "07506", - "0751", - "07520", - "07522", - "07524", - "07525", - "07527", - "07528", - "07529", - "07531", - "07532", - "07533", - "07534", - "07541", - "07542", - "07543", - "07544", - "07545", - "07546", - "07551", - "07552", - "07553", - "07554", - "07555", - "07556", - "07557", - "07558", - "07561", - "07562", - "07563", - "07564", - "07565", - "07566", - "07567", - "07568", - "07569", - "07570", - "07571", - "07572", - "07573", - "07574", - "07575", - "07576", - "07577", - "07578", - "07579", - "07581", - "07582", - "07583", - "07584", - "07585", - "07586", - "07587", - "07602", - "0761", - "07620", - "07621", - "07622", - "07623", - "07624", - "07625", - "07626", - "07627", - "07628", - "07629", - "07631", - "07632", - "07633", - "07634", - "07635", - "07636", - "07641", - "07642", - "07643", - "07644", - "07645", - "07646", - "07651", - "07652", - "07653", - "07654", - "07655", - "07656", - "07657", - "07660", - "07661", - "07662", - "07663", - "07664", - "07665", - "07666", - "07667", - "07668", - "07669", - "07671", - "07672", - "07673", - "07674", - "07675", - "07676", - "07681", - "07682", - "07683", - "07684", - "07685", - "07702", - "07703", - "07704", - "07705", - "07706", - "07707", - "07708", - "07709", - "0771", - "07720", - "07721", - "07722", - "07723", - "07724", - "07725", - "07726", - "07727", - "07728", - "07729", - "07731", - "07732", - "07733", - "07734", - "07735", - "07736", - "07738", - "07739", - "07741", - "07742", - "07743", - "07744", - "07745", - "07746", - "07747", - "07748", - "07751", - "07753", - "07754", - "07755", - "07761", - "07762", - "07763", - "07764", - "07765", - "07771", - "07773", - "07774", - "07775", - "07777", - "07802", - "07803", - "07804", - "07805", - "07806", - "07807", - "07808", - "0781", - "07821", - "07822", - "07823", - "07824", - "07825", - "07826", - "07831", - "07832", - "07833", - "07834", - "07835", - "07836", - "07837", - "07838", - "07839", - "07841", - "07842", - "07843", - "07844", - "07851", - "07852", - "07853", - "07854", - "07903", - "07904", - "07905", - "07906", - "07907", - "0791", - "07930", - "07931", - "07932", - "07933", - "07934", - "07935", - "07936", - "07937", - "07938", - "07939", - "07940", - "07941", - "07942", - "07943", - "07944", - "07945", - "07946", - "07947", - "07948", - "07949", - "07950", - "07951", - "07952", - "07953", - "07954", - "07955", - "07957", - "07958", - "07959", - "07961", - "07962", - "07963", - "07964", - "07965", - "07966", - "07967", - "07971", - "07972", - "07973", - "07974", - "07975", - "07976", - "07977", - "08020", - "08021", - "08022", - "08023", - "08024", - "08025", - "08026", - "08027", - "08028", - "08029", - "08031", - "08032", - "08033", - "08034", - "08035", - "08036", - "08038", - "08039", - "08041", - "08042", - "08043", - "08045", - "08046", - "08051", - "08052", - "08053", - "08054", - "08055", - "08056", - "08057", - "08061", - "08062", - "08063", - "08064", - "08065", - "08066", - "08067", - "08071", - "08072", - "08073", - "08074", - "08075", - "08076", - "08081", - "08082", - "08083", - "08084", - "08085", - "08086", - "08091", - "08092", - "08093", - "08094", - "08095", - "08102", - "08104", - "08105", - "08106", - "0811", - "08121", - "08122", - "08123", - "08124", - "08131", - "08133", - "08134", - "08135", - "08136", - "08137", - "08138", - "08139", - "08141", - "08142", - "08143", - "08144", - "08145", - "08146", - "08151", - "08152", - "08153", - "08157", - "08158", - "08161", - "08165", - "08166", - "08167", - "08168", - "08170", - "08171", - "08176", - "08177", - "08178", - "08179", - "08191", - "08192", - "08193", - "08194", - "08195", - "08196", - "08202", - "08203", - "08204", - "08205", - "08206", - "08207", - "08208", - "0821", - "08221", - "08222", - "08223", - "08224", - "08225", - "08226", - "08230", - "08231", - "08232", - "08233", - "08234", - "08236", - "08237", - "08238", - "08239", - "08241", - "08243", - "08245", - "08246", - "08247", - "08248", - "08249", - "08250", - "08251", - "08252", - "08253", - "08254", - "08257", - "08258", - "08259", - "08261", - "08262", - "08263", - "08265", - "08266", - "08267", - "08268", - "08269", - "08271", - "08272", - "08273", - "08274", - "08276", - "08281", - "08282", - "08283", - "08284", - "08285", - "08291", - "08292", - "08293", - "08294", - "08295", - "08296", - "08302", - "08303", - "08304", - "08306", - "0831", - "08320", - "08321", - "08322", - "08323", - "08324", - "08325", - "08326", - "08327", - "08328", - "08329", - "08330", - "08331", - "08332", - "08333", - "08334", - "08335", - "08336", - "08337", - "08338", - "08340", - "08341", - "08342", - "08343", - "08344", - "08345", - "08346", - "08347", - "08348", - "08349", - "08361", - "08362", - "08363", - "08364", - "08365", - "08366", - "08367", - "08368", - "08369", - "08370", - "08372", - "08373", - "08374", - "08375", - "08376", - "08377", - "08378", - "08379", - "08380", - "08381", - "08382", - "08383", - "08384", - "08385", - "08386", - "08387", - "08388", - "08389", - "08392", - "08393", - "08394", - "08395", - "08402", - "08403", - "08404", - "08405", - "08406", - "08407", - "0841", - "08421", - "08422", - "08423", - "08424", - "08426", - "08427", - "08431", - "08432", - "08433", - "08434", - "08435", - "08441", - "08442", - "08443", - "08444", - "08445", - "08446", - "08450", - "08452", - "08453", - "08454", - "08456", - "08457", - "08458", - "08459", - "08460", - "08461", - "08462", - "08463", - "08464", - "08465", - "08466", - "08467", - "08468", - "08469", - "08501", - "08502", - "08503", - "08504", - "08505", - "08506", - "08507", - "08509", - "0851", - "08531", - "08532", - "08533", - "08534", - "08535", - "08536", - "08537", - "08538", - "08541", - "08542", - "08543", - "08544", - "08545", - "08546", - "08547", - "08548", - "08549", - "08550", - "08551", - "08552", - "08553", - "08554", - "08555", - "08556", - "08557", - "08558", - "08561", - "08562", - "08563", - "08564", - "08565", - "08571", - "08572", - "08573", - "08574", - "08581", - "08582", - "08583", - "08584", - "08585", - "08586", - "08591", - "08592", - "08593", - "0861", - "08621", - "08622", - "08623", - "08624", - "08628", - "08629", - "08630", - "08631", - "08633", - "08634", - "08635", - "08636", - "08637", - "08638", - "08639", - "08640", - "08641", - "08642", - "08649", - "08650", - "08651", - "08652", - "08654", - "08656", - "08657", - "08661", - "08662", - "08663", - "08664", - "08665", - "08666", - "08667", - "08669", - "08670", - "08671", - "08677", - "08678", - "08679", - "08681", - "08682", - "08683", - "08684", - "08685", - "08686", - "08687", - "08702", - "08703", - "08704", - "08705", - "08706", - "08707", - "08708", - "08709", - "0871", - "08721", - "08722", - "08723", - "08724", - "08725", - "08726", - "08727", - "08728", - "08731", - "08732", - "08733", - "08734", - "08735", - "08741", - "08742", - "08743", - "08744", - "08745", - "08751", - "08752", - "08753", - "08754", - "08756", - "08761", - "08762", - "08764", - "08765", - "08766", - "08771", - "08772", - "08773", - "08774", - "08781", - "08782", - "08783", - "08784", - "08785", - "08801", - "08802", - "08803", - "08805", - "08806", - "08807", - "08808", - "08809", - "0881", - "08821", - "08822", - "08823", - "08824", - "08825", - "08841", - "08845", - "08846", - "08847", - "08851", - "08856", - "08857", - "08858", - "08860", - "08861", - "08862", - "08867", - "08868", - "08869", - "089", - "0906", - "09070", - "09071", - "09072", - "09073", - "09074", - "09075", - "09076", - "09077", - "09078", - "09080", - "09081", - "09082", - "09083", - "09084", - "09085", - "09086", - "09087", - "09088", - "09089", - "09090", - "09091", - "09092", - "09093", - "09094", - "09097", - "09099", - "09101", - "09102", - "09103", - "09104", - "09105", - "09106", - "09107", - "0911", - "0911", - "09120", - "09122", - "09123", - "09126", - "09127", - "09128", - "09129", - "09131", - "09132", - "09133", - "09134", - "09135", - "09141", - "09142", - "09143", - "09144", - "09145", - "09146", - "09147", - "09148", - "09149", - "09151", - "09152", - "09153", - "09154", - "09155", - "09156", - "09157", - "09158", - "09161", - "09162", - "09163", - "09164", - "09165", - "09166", - "09167", - "09170", - "09171", - "09172", - "09173", - "09174", - "09175", - "09176", - "09177", - "09178", - "09179", - "09180", - "09181", - "09182", - "09183", - "09184", - "09185", - "09186", - "09187", - "09188", - "09189", - "09190", - "09191", - "09192", - "09193", - "09194", - "09195", - "09196", - "09197", - "09198", - "09199", - "09201", - "09202", - "09203", - "09204", - "09205", - "09206", - "09207", - "09208", - "09209", - "0921", - "09220", - "09221", - "09222", - "09223", - "09225", - "09227", - "09228", - "09229", - "09231", - "09232", - "09233", - "09234", - "09235", - "09236", - "09238", - "09241", - "09242", - "09243", - "09244", - "09245", - "09246", - "09251", - "09252", - "09253", - "09254", - "09255", - "09256", - "09257", - "09260", - "09261", - "09262", - "09263", - "09264", - "09265", - "09266", - "09267", - "09268", - "09269", - "09270", - "09271", - "09272", - "09273", - "09274", - "09275", - "09276", - "09277", - "09278", - "09279", - "09280", - "09281", - "09282", - "09283", - "09284", - "09285", - "09286", - "09287", - "09288", - "09289", - "09292", - "09293", - "09294", - "09295", - "09302", - "09303", - "09305", - "09306", - "09307", - "0931", - "09321", - "09323", - "09324", - "09325", - "09326", - "09331", - "09332", - "09333", - "09334", - "09335", - "09336", - "09337", - "09338", - "09339", - "09340", - "09341", - "09342", - "09343", - "09344", - "09345", - "09346", - "09347", - "09348", - "09349", - "09350", - "09351", - "09352", - "09353", - "09354", - "09355", - "09356", - "09357", - "09358", - "09359", - "09360", - "09363", - "09364", - "09365", - "09366", - "09367", - "09369", - "09371", - "09372", - "09373", - "09374", - "09375", - "09376", - "09377", - "09378", - "09381", - "09382", - "09383", - "09384", - "09385", - "09386", - "09391", - "09392", - "09393", - "09394", - "09395", - "09396", - "09397", - "09398", - "09401", - "09402", - "09403", - "09404", - "09405", - "09406", - "09407", - "09408", - "09409", - "0941", - "09420", - "09421", - "09422", - "09423", - "09424", - "09426", - "09427", - "09428", - "09429", - "09431", - "09433", - "09434", - "09435", - "09436", - "09438", - "09439", - "09441", - "09442", - "09443", - "09444", - "09445", - "09446", - "09447", - "09448", - "09451", - "09452", - "09453", - "09454", - "09461", - "09462", - "09463", - "09464", - "09465", - "09466", - "09467", - "09468", - "09469", - "09471", - "09472", - "09473", - "09474", - "09480", - "09481", - "09482", - "09484", - "09491", - "09492", - "09493", - "09495", - "09497", - "09498", - "09499", - "09502", - "09503", - "09504", - "09505", - "0951", - "09521", - "09522", - "09523", - "09524", - "09525", - "09526", - "09527", - "09528", - "09529", - "09531", - "09532", - "09533", - "09534", - "09535", - "09536", - "09542", - "09543", - "09544", - "09545", - "09546", - "09547", - "09548", - "09549", - "09551", - "09552", - "09553", - "09554", - "09555", - "09556", - "09560", - "09561", - "09562", - "09563", - "09564", - "09565", - "09566", - "09567", - "09568", - "09569", - "09571", - "09572", - "09573", - "09574", - "09575", - "09576", - "09602", - "09603", - "09604", - "09605", - "09606", - "09607", - "09608", - "0961", - "09621", - "09622", - "09624", - "09625", - "09626", - "09627", - "09628", - "09631", - "09632", - "09633", - "09634", - "09635", - "09636", - "09637", - "09638", - "09639", - "09641", - "09642", - "09643", - "09644", - "09645", - "09646", - "09647", - "09648", - "09651", - "09652", - "09653", - "09654", - "09655", - "09656", - "09657", - "09658", - "09659", - "09661", - "09662", - "09663", - "09664", - "09665", - "09666", - "09671", - "09672", - "09673", - "09674", - "09675", - "09676", - "09677", - "09681", - "09682", - "09683", - "09701", - "09704", - "09708", - "0971", - "09720", - "09721", - "09722", - "09723", - "09724", - "09725", - "09726", - "09727", - "09728", - "09729", - "09732", - "09733", - "09734", - "09735", - "09736", - "09737", - "09738", - "09741", - "09742", - "09744", - "09745", - "09746", - "09747", - "09748", - "09749", - "09761", - "09762", - "09763", - "09764", - "09765", - "09766", - "09771", - "09772", - "09773", - "09774", - "09775", - "09776", - "09777", - "09778", - "09779", - "09802", - "09803", - "09804", - "09805", - "0981", - "09820", - "09822", - "09823", - "09824", - "09825", - "09826", - "09827", - "09828", - "09829", - "09831", - "09832", - "09833", - "09834", - "09835", - "09836", - "09837", - "09841", - "09842", - "09843", - "09844", - "09845", - "09846", - "09847", - "09848", - "09851", - "09852", - "09853", - "09854", - "09855", - "09856", - "09857", - "09861", - "09865", - "09867", - "09868", - "09869", - "09871", - "09872", - "09873", - "09874", - "09875", - "09876", - "09901", - "09903", - "09904", - "09905", - "09906", - "09907", - "09908", - "0991", - "09920", - "09921", - "09922", - "09923", - "09924", - "09925", - "09926", - "09927", - "09928", - "09929", - "09931", - "09932", - "09933", - "09935", - "09936", - "09937", - "09938", - "09941", - "09942", - "09943", - "09944", - "09945", - "09946", - "09947", - "09948", - "09951", - "09952", - "09953", - "09954", - "09955", - "09956", - "09961", - "09962", - "09963", - "09964", - "09965", - "09966", - "09971", - "09972", - "09973", - "09974", - "09975", - "09976", - "09977", - "09978" - }; - - public static readonly string[] MobileNetworks = - { - "01510", - "01511", - "01512", - "01513", - "01514", - "01515", - "01516", - "01517", - "01518", - "01519", - "0160", - "0170", - "0171", - "0175", - "01520", - "01521", - "01522", - "01523", - "01524", - "01525", - "01526", - "01527", - "01528", - "01529", - "0162", - "0172", - "0173", - "0174", - "01570", - "01571", - "01572", - "01573", - "01574", - "01575", - "01576", - "01577", - "01578", - "01579", - "0163", - "0177", - "0178", - "01590", - "01591", - "01592", - "01593", - "01594", - "01595", - "01596", - "01597", - "01598", - "01599", - "0176", - "0179" - }; - - public static string RandomPostalCode() - { - return PostalCodes.Random(); - } - - public static string RandomHouseNumber() - { - var next = TestRandom.Next(100); - var nr = TestRandom.Next(100); - if (next < 10) return $"{nr} - {nr + TestRandom.Next(1, 5)}"; - - if (next < 30) return nr.ToString(CultureInfo.InvariantCulture) + "abcd".Random(); - - return nr.ToString(CultureInfo.InvariantCulture); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/TestAddress.cs b/src/abstractions/Backend.Fx/RandomData/TestAddress.cs deleted file mode 100644 index 735e3f9e..00000000 --- a/src/abstractions/Backend.Fx/RandomData/TestAddress.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; - -namespace Backend.Fx.RandomData -{ - public class TestAddress : ValueObject - { - public TestAddress(string street, string number, string postalCode, string city, string country) - { - Street = street; - Number = number; - PostalCode = postalCode; - City = city; - Country = country; - } - - public string Street { get; } - - public string Number { get; } - - public string PostalCode { get; } - - public string City { get; } - - public string Country { get; } - - protected override IEnumerable GetEqualityComponents() - { - yield return Street; - yield return Number; - yield return PostalCode; - yield return City; - yield return Country; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs deleted file mode 100644 index 6028f793..00000000 --- a/src/abstractions/Backend.Fx/RandomData/TestAddressGenerator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Linq; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public class TestAddressGenerator : Generator - { - public static TestAddress Generate() - { - return new TestAddressGenerator().First(); - } - - - protected override TestAddress Next() - { - return new TestAddress( - Names.Streets.Random(), - Numbers.RandomHouseNumber(), - Numbers.RandomPostalCode(), - Names.Cities.Random(), - Names.Countries.Random()); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/TestChemical.cs b/src/abstractions/Backend.Fx/RandomData/TestChemical.cs deleted file mode 100644 index bd617b47..00000000 --- a/src/abstractions/Backend.Fx/RandomData/TestChemical.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System; -using System.Linq; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public class TestChemical - { - public TestChemical(string name, string description, string alternativeNames, string formula, decimal molecularWeight, string casRegistryNumber, string molFile) - { - Name = name; - Description = description; - AlternativeNames = alternativeNames; - Formula = formula; - MolecularWeight = molecularWeight; - CasRegistryNumber = casRegistryNumber; - MolFile = molFile; - } - - public string Name { get; } - public string Description { get; } - public string AlternativeNames { get; } - public string Formula { get; } - public decimal MolecularWeight { get; } - public string CasRegistryNumber { get; } - public string MolFile { get; } - - public string[] AlternativeNamesArray - { - get { return AlternativeNames?.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); } - } - - // data taken from http://webbook.nist.gov/chemistry/name-ser.html - public static TestChemical[] All { get; } = - { - //name, description, altName, formula, mol weight, casNo, mol file - new TestChemical("1,2-Dichloroethan", - "1,2-Dichlorethan (Ethylendichlorid, EDC) ist eine farblose, brennbare und giftige Flüssigkeit mit chloroformartigem Geruch. Diese chemische Verbindung gehört zu den Chlorkohlenwasserstoffen.", - "α,β-Dichloroethane; s-Dichloroethane; Brocide; Dutch liquid; Ethylene chloride; Ethylene dichloride; Freon 150; Glycol dichloride; 1,2-Bichloroethane; 1,2-Dichlorethane; 1,2-Dichloroethane; CH2ClCH2Cl; sym-Dichloroethane; Aethylenchlorid; Bichlorure D'ethylene; Borer sol; Chlorure D'ethylene; Cloruro di ethene; 1,2-DCE; Destruxol borer-sol; 1,2-Dichloorethaan; 1,2-Dichlor-aethan; Dichloremulsion; Di-chlor-mulsion; Dichloro-1,2-ethane; 1,2-Dicloroetano; Dutch oil; EDC; ENT 1,656; Ethane dichloride; Ethyleendichloride; 1,2-Ethylene dichloride; NCI-C00511; Rcra waste number U077; UN 1184; DCE; EDC (halocarbon); HCC 150; 1,2-dichloroethane (ethylene dichloride)", - "C2H4Cl2", 98.959m, "107-06-2", - "\n\n\n 8 7 0 0 0 1 V2000\n 2.3785 0.7551 0.8326 C 0 0 0 0 0 0 0 0 0\n 1.5134 1.8542 1.3880 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.1383 1.9817 Cl 0 0 0 0 0 0 0 0 0\n 3.8896 1.4714 0.2337 Cl 0 0 0 0 0 0 0 0 0\n 1.8822 0.2254 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.6233 0.0000 1.6007 H 0 0 0 0 0 0 0 0 0\n 1.2715 2.6111 0.6209 H 0 0 0 0 0 0 0 0 0\n 2.0083 2.3814 2.2230 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\nM END\n"), - new TestChemical("1-Buten", - "Butene (auch Butylene) sind eine Gruppe von vier isomeren Kohlenwasserstoffen mit der allgemeinen Summenformel C4H8, die über eine C–C-Doppelbindung verfügen. Sie zählen damit zu den Alkenen. Zwei der Isomere unterscheiden sich durch cis-trans-Isomerie. Butene sind unter Standardbedingungen farblose, brennbare Gase mit einer größeren Dichte als Luft. Unter Druck lassen sich die Isomere verflüssigen. Sie wirken in höheren Konzentrationen narkotisierend und erstickend. Mit Luft bilden sie explosive Gemische.", - "α-Butene; α-Butylene; But-1-ene; Butene-1; Ethylethylene; 1-Butylene; 1-C4H8", "C4H8", 56.1063m, "106-98-9", - "\n\n\n 12 11 0 0 0 1 V2000\n 1.3702 2.1763 1.6455 C 0 0 0 0 0 0 0 0 0\n 0.4451 2.8431 0.9657 C 0 0 0 0 0 0 0 0 0\n 1.9783 0.9053 1.1655 C 0 0 0 0 0 0 0 0 0\n 3.4886 0.9497 1.2530 C 0 0 0 0 0 0 0 0 0\n 1.7309 2.5402 2.6153 H 0 0 0 0 0 0 0 0 0\n 0.0000 3.7633 1.3313 H 0 0 0 0 0 0 0 0 0\n 0.0658 2.5199 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.5835 0.0745 1.7845 H 0 0 0 0 0 0 0 0 0\n 1.6643 0.6803 0.1265 H 0 0 0 0 0 0 0 0 0\n 3.9327 0.0000 0.9290 H 0 0 0 0 0 0 0 0 0\n 3.9065 1.7417 0.6183 H 0 0 0 0 0 0 0 0 0\n 3.8311 1.1398 2.2789 H 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0\n 1 3 1 0 0 0\n 1 5 1 0 0 0\n 2 6 1 0 0 0\n 2 7 1 0 0 0\n 3 4 1 0 0 0\n 3 8 1 0 0 0\n 3 9 1 0 0 0\n 4 10 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\nM END\n"), - new TestChemical("1-Hexen", - "Hexene (Betonung auf der zweiten Silbe) sind chemische Verbindungen aus der Gruppe der Alkene mit der Summenformel C6H12. Der Wortstamm Hex weist auf die sechs Kohlenstoffatome, die Endung en auf die Doppelbindung zwischen zwei der Kohlenstoffatome hin. Es existieren verschiedene Isomere, die sich in der Position der Doppelbindung und dem Vorhandensein bzw. der Lage einer Verzweigung der Kohlenstoffkette unterscheiden. Die in der Industrie am häufigsten eingesetzte Verbindung ist 1-Hexen, welche z. B. als Comonomer bei der Produktion von Polyethen eingesetzt wird.", - "Hexene-1; 1-n-Hexene; 1-C6H12; Butylethylene; Hexene; Hex-1-ene; UN 2370; Hexylene; Neodene 6 XHP; NSC 74121; Dialene 6", "C6H12", 84.1595m, - "592-41-6", - "\n\n\n 18 17 0 0 0 0 0 0 0 0 V2000\n -2.3439 -0.9761 0.0000 H 0000000000000000000\n -2.5829 -2.8063 0.0000 H 0000000000000000000\n -1.8943 -1.9654 0.0000 C 0000000000000000000\n -0.1940 -3.1757 0.0000 H 0000000000000000000\n -0.5736 -2.1523 0.0000 C 0000000000000000000\n 1.1457 -1.2481 0.8727 H 0000000000000000000\n 1.1457 -1.2481 -0.8727 H 0000000000000000000\n 0.4932 -1.0856 0.0000 C 0000000000000000000\n -0.6369 0.5346 -0.8794 H 0000000000000000000\n -0.6369 0.5346 0.8794 H 0000000000000000000\n 0.0000 0.3648 0.0000 C 0000000000000000000\n 1.7839 1.2154 0.8785 H 0000000000000000000\n 1.7839 1.2154 -0.8785 H 0000000000000000000\n 1.1455 1.3862 0.0000 C 0000000000000000000\n 0.0421 3.0486 -0.8848 H 0000000000000000000\n 0.0421 3.0486 0.8848 H 0000000000000000000\n 1.4941 3.5435 0.0000 H 0000000000000000000\n 0.6554 2.8379 0.0000 C 0000000000000000000\n 1 3 1 0 0 0\n 2 3 1 0 0 0\n 3 5 2 0 0 0\n 4 5 1 0 0 0\n 5 8 1 0 0 0\n 6 8 1 0 0 0\n 7 8 1 0 0 0\n 8 11 1 0 0 0\n 9 11 1 0 0 0\n 10 11 1 0 0 0\n 11 14 1 0 0 0\n 12 14 1 0 0 0\n 13 14 1 0 0 0\n 14 18 1 0 0 0\n 15 18 1 0 0 0\n 16 18 1 0 0 0\n 17 18 1 0 0 0\nM END\n"), - new TestChemical("2,3-Dimethylpentan", - "3-Methylhexan und 2,3-Dimethylpentan sind chirale Verbindungen, von denen zwei Enantiomere existieren; Chiralitätszentrum ist das C3, das als Substituenten Wasserstoff und je einen Methyl-, Ethyl- und Propyl- bzw. Isopropylrest trägt. Sie sind die einfachsten chiralen Alkane.", - "2,3-Dimethylpentane; 3,4-Dimethylpentane", "C7H16", 100.2019m, "565-59-3", - "\n\n\n 23 22 0 0 0 1 V2000\n 4.3751 2.6128 2.7529 C 0 0 0 0 0 0 0 0 0\n 3.2736 1.9976 1.8651 C 0 0 0 0 0 0 0 0 0\n 1.9284 2.6959 2.0896 C 0 0 0 0 0 0 0 0 0\n 3.1364 0.5066 2.1417 C 0 0 0 0 0 0 0 0 0\n 4.5445 4.1009 2.4849 C 0 0 0 0 0 0 0 0 0\n 5.7094 1.9144 2.5314 C 0 0 0 0 0 0 0 0 0\n 0.9524 2.4445 0.9624 C 0 0 0 0 0 0 0 0 0\n 4.0701 2.4735 3.8192 H 0 0 0 0 0 0 0 0 0\n 3.5772 2.1391 0.7982 H 0 0 0 0 0 0 0 0 0\n 1.4902 2.3691 3.0539 H 0 0 0 0 0 0 0 0 0\n 2.0935 3.7916 2.1977 H 0 0 0 0 0 0 0 0 0\n 2.7404 0.3139 3.1481 H 0 0 0 0 0 0 0 0 0\n 2.4601 0.0256 1.4233 H 0 0 0 0 0 0 0 0 0\n 4.1132 0.0000 2.0752 H 0 0 0 0 0 0 0 0 0\n 5.2661 4.5496 3.1789 H 0 0 0 0 0 0 0 0 0\n 4.9040 4.2910 1.4645 H 0 0 0 0 0 0 0 0 0\n 3.5891 4.6380 2.5995 H 0 0 0 0 0 0 0 0 0\n 6.1048 2.1059 1.5247 H 0 0 0 0 0 0 0 0 0\n 6.4620 2.2562 3.2527 H 0 0 0 0 0 0 0 0 0\n 5.6092 0.8222 2.6385 H 0 0 0 0 0 0 0 0 0\n 0.7285 1.3752 0.8458 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.9597 1.1415 H 0 0 0 0 0 0 0 0 0\n 1.3427 2.8017 0.0000 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 4 1 0 0 0\n 2 9 1 0 0 0\n 3 7 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\nM END\n"), - new TestChemical("Essigsäure", - "Essigsäure (systematisch Ethansäure, lateinisch acidum aceticum) ist eine farblose, flüssige, ätzende und typisch riechende Carbonsäure der Zusammensetzung C2H4O2 (Halbstrukturformel CH3COOH). Als Lebensmittelzusatzstoff trägt sie die E-Nummer E 260. Wässrige Lösungen der Essigsäure werden trivial nur Essig und reine Essigsäure Eisessig genannt. Die Salze und Ester der Essigsäure heißen Acetate oder (systematisch) Ethanoate", - "Ethanoic acid; Ethylic acid; Glacial acetic acid; Methanecarboxylic acid; Vinegar acid; CH3COOH; Acetasol; Acide acetique; Acido acetico; Azijnzuur; Essigsaeure; Octowy kwas; Acetic acid, glacial; Kyselina octova; UN 2789; Aci-jel; Shotgun; Ethanoic acid monomer; NSC 132953", - "C2H4O2", 60.052m, "64-19-7", - "\n\n\n 8 7 0 0 0 0 0 0 0 0999 V2000\n 0.7724 0.9670 1.0069 C 0 0 0 0 0\n 2.0576 1.7541 0.9321 C 0 0 0 0 0\n 3.0833 0.9883 0.4751 O 0 0 0 0 0\n 2.1974 2.9214 1.2200 O 0 0 0 0 0\n 0.0114 1.5612 1.5141 H 0 0 0 0 0\n 0.4326 0.7231 -0.0064 H 0 0 0 0 0\n 0.9331 0.0221 1.5356 H 0 0 0 0 0\n 3.8606 1.5761 0.4447 H 0 0 0 0 0\n 6 1 1 0 0 0\n 2 1 1 0 0 0\n 1 5 1 0 0 0\n 1 7 1 0 0 0\n 3 2 1 0 0 0\n 2 4 2 0 0 0\n 8 3 1 0 0 0\nM END\n"), - new TestChemical( - "Aceton", - "Aceton [at͡səˈtoːn] (auch: Azeton) ist der Trivialname für die organisch-chemische Verbindung Propanon bzw. Dimethylketon. Aceton ist eine farblose Flüssigkeit und findet Verwendung als polares, aprotisches Lösungsmittel und als Ausgangsstoff für viele Synthesen der organischen Chemie. Es ist mit seinem Strukturmerkmal der Carbonylgruppe (>C=O), die zwei Methylgruppen trägt, das einfachste Keton.", - "2-Propanone; β-Ketopropane; Dimethyl ketone; Dimethylformaldehyde; Methyl ketone; Propanone; Pyroacetic ether; (CH3)2CO; Dimethylketal; Ketone propane; Ketone, dimethyl-; Chevron acetone; Rcra waste number U002; UN 1090; Sasetone; Propan-2-one; NSC 135802", - "C3H6O", 58.0791m, "67-64-1", - "\n\n\n 10 9 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.1857 C 0000000000000000000\n 0.0000 0.0000 1.4013 O 0000000000000000000\n 0.0000 1.2929 -0.6150 C 0000000000000000000\n 0.0000 -1.2929 -0.6150 C 0000000000000000000\n 0.0000 2.1487 0.0628 H 0000000000000000000\n 0.0000 -2.1487 0.0628 H 0000000000000000000\n 0.8810 1.3407 -1.2674 H 0000000000000000000\n -0.8810 1.3407 -1.2674 H 0000000000000000000\n -0.8810 -1.3407 -1.2674 H 0000000000000000000\n 0.8810 -1.3407 -1.2674 H 0000000000000000000\n 1 2 2 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 3 5 1 0 0 0\n 3 7 1 0 0 0\n 3 8 1 0 0 0\n 4 6 1 0 0 0\n 4 9 1 0 0 0\n 4 10 1 0 0 0\nM END\n"), - new TestChemical("Acetonitril", "Acetonitril ist ein organisches Lösungsmittel und gehört zur Stoffgruppe der Nitrile.", - "Cyanomethane; Ethanenitrile; Ethyl nitrile; Methane, cyano-; Methanecarbonitrile; Methyl cyanide; CH3CN; Acetonitril; Cyanure de methyl; USAF EK-488; Methylkyanid; NA 1648; NCI-C60822; Rcra waste number U003; UN 1648; Ethanonitrile", - "C2H3N", 41.0519m, "75-05-8", - "\n\n\n 6 5 0 0 0 1 V2000\n 0.4219 0.8958 0.5193 C 0 0 0 0 0 0 0 0 0\n 1.8612 0.9422 0.4897 C 0 0 0 0 0 0 0 0 0\n 3.0198 0.9796 0.4659 N 0 0 0 0 0 0 0 0 0\n 0.0000 1.7811 0.0257 H 0 0 0 0 0 0 0 0 0\n 0.0568 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0607 0.8694 1.5558 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 3 0 0 0\nM END\n"), - new TestChemical( - "Benzen", - "Benzol (nach der IUPAC Benzen) ist eine flüssige organische Verbindung mit einem charakteristischen aromatischen Geruch. Die Verbindung mit der Summenformel C6H6 ist ein aromatischer Kohlenwasserstoff und das einfachste und zugleich klassische Beispiel für die Aromatizität bestimmter Verbindungen. Benzol ist mischbar mit fast allen organischen Solventien, jedoch kaum mit Wasser. Als Lösungsmittel hat Benzol seine Bedeutung verloren, da es krebserregend ist. Als mutagenes Klastogen wirken Benzol bzw. dessen Metabolite als Gift, welches Chromosomenaberrationen hervorrufen kann.", - "[6]Annulene; Benzol; Benzole; Coal naphtha; Cyclohexatriene; Phenyl hydride; Pyrobenzol; Pyrobenzole; Benzolene; Bicarburet of hydrogen; Carbon oil; Mineral naphtha; Motor benzol; Benzeen; Benzen; Benzin; Benzine; Benzolo; Fenzen; NCI-C55276; Phene; Rcra waste number U019; UN 1114; NSC 67315; 1,3,5-Cyclohexatriene", - "C6H6", 78.1118m, "71-43-2", - "\n\n\n 12 12 0 0 0 1 V2000\n 3.2883 3.3891 0.2345 C 0 0 0 0 0 0 0 0 0\n 1.9047 3.5333 0.2237 C 0 0 0 0 0 0 0 0 0\n 3.8560 2.1213 0.1612 C 0 0 0 0 0 0 0 0 0\n 1.0888 2.4099 0.1396 C 0 0 0 0 0 0 0 0 0\n 3.0401 0.9977 0.0771 C 0 0 0 0 0 0 0 0 0\n 1.6565 1.1421 0.0663 C 0 0 0 0 0 0 0 0 0\n 3.9303 4.2734 0.3007 H 0 0 0 0 0 0 0 0 0\n 1.4582 4.5312 0.2815 H 0 0 0 0 0 0 0 0 0\n 4.9448 2.0077 0.1699 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.5234 0.1311 H 0 0 0 0 0 0 0 0 0\n 3.4870 0.0000 0.0197 H 0 0 0 0 0 0 0 0 0\n 1.0145 0.2578 0.0000 H 0 0 0 0 0 0 0 0 0\n 2 1 2 0 0 0\n 1 3 1 0 0 0\n 1 7 1 0 0 0\n 4 2 1 0 0 0\n 2 8 1 0 0 0\n 3 5 2 0 0 0\n 3 9 1 0 0 0\n 6 4 2 0 0 0\n 4 10 1 0 0 0\n 5 6 1 0 0 0\n 5 11 1 0 0 0\n 6 12 1 0 0 0\nM END\n"), - new TestChemical("Kohlentetrachlorid", - "Kohlenstoffgruppe oder Kohlenstoff-Silicium-Gruppe bezeichnet die 4. Hauptgruppe („Tetrele“) (nach neuer Nummerierung der IUPAC Gruppe 14) des Periodensystems. Sie umfasst die Elemente Kohlenstoff (C), Silicium (Si), Germanium (Ge), Zinn (Sn) und Blei (Pb). Auch ein radioaktives Element, das Flerovium (Fl), ist vertreten.", - "Methane, tetrachloro-; Benzinoform; Carbon chloride (CCl4); Carbona; Fasciolin; Flukoids; Freon 10; Necatorina; Perchloromethane; Tetrachlorocarbon; Tetrachloromethane; Tetrafinol; Tetraform; Tetrasol; Univerm; Vermoestricid; CCl4; Benzenoform; Carbon tet; Methane tetrachloride; Czterochlorek wegla; ENT 4,705; Halon 1040; Necatorine; R 10; Tetrachloorkoolstof; Tetrachloormetaan; Tetrachlorkohlenstoff, tetra; Tetrachlormethan; Tetrachlorure de carbone; Tetraclorometano; Tetracloruro di carbonio; Chlorid uhlicity; ENT 27164; Rcra waste number U211; UN 1846; Katharin; Seretin; Thawpit; NSC 97063; R 10 (Refrigerant)", - "CCl4", 153.823m, "56-23-5", - "\n\n\n 5 4 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.0000 C 0000000000000000000\n 1.0350 1.0350 1.0350 Cl 0000000000000000000\n -1.0350 -1.0350 1.0350 Cl 0000000000000000000\n -1.0350 1.0350 -1.0350 Cl 0000000000000000000\n 1.0350 -1.0350 -1.0350 Cl 0000000000000000000\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), - new TestChemical("Chloroform", "Chloroform (systematische Bezeichnung Trichlormethan) ist ein chlorierter Kohlenwasserstoff mit der Summenformel CHCl3.", - "Chloroform; Freon 20; Methane, trichloro-; R 20; Trichloroform; CHCl3; Formyl trichloride; Methane trichloride; Methenyl trichloride; Methyl trichloride; Chloroforme; Cloroformio; NCI-C02686; R 20 (refrigerant); Trichloormethaan; Trichlormethan; Triclorometano; Rcra waste number U044; UN 1888; NSC 77361; F 20", - "CHCl3", 119.378m, "67-66-3", - "\n\n\n 5 4 0 0 0 0 0 0 0 0 V2000\n 0.0000 0.0000 0.4548 C 0000000000000000000\n 0.0000 0.0000 1.5402 H 0000000000000000000\n 0.0000 1.7050 -0.0837 Cl 0000000000000000000\n 1.4766 -0.8525 -0.0837 Cl 0000000000000000000\n -1.4766 -0.8525 -0.0837 Cl 0000000000000000000\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), - new TestChemical("Cyclohexan", - "Cyclohexan (auch Hexahydrobenzol, Hexamethylen, Naphthen) ist eine farblose Flüssigkeit. Es ist ein Cycloalkan mit der Summenformel C6H12, das im Erdöl vorkommt und als Lösungsmittel und Grundstoff in der Synthese genutzt wird.", - "Benzene, hexahydro-; Hexahydrobenzene; Hexamethylene; Hexanaphthene; Cicloesano; Cykloheksan; Rcra waste number U056; UN 1145; NSC 406835", "C6H12", - 84.1595m, "110-82-7", - "\n\n\n 18 18 0 0 0 0 0 0 0 0 V2000\n 0.0000 -1.4672 0.2293 C 0000000000000000000\n -1.2706 0.7336 0.2293 C 0000000000000000000\n 1.2706 0.7336 0.2293 C 0000000000000000000\n 0.0000 1.4672 -0.2293 C 0000000000000000000\n -1.2706 -0.7336 -0.2293 C 0000000000000000000\n 1.2706 -0.7336 -0.2293 C 0000000000000000000\n 0.0000 -1.5355 1.3276 H 0000000000000000000\n -1.3298 0.7677 1.3276 H 0000000000000000000\n 1.3298 0.7677 1.3276 H 0000000000000000000\n 0.0000 1.5355 -1.3276 H 0000000000000000000\n -1.3298 -0.7677 -1.3276 H 0000000000000000000\n 1.3298 -0.7677 -1.3276 H 0000000000000000000\n 0.0000 -2.4985 -0.1466 H 0000000000000000000\n -2.1638 1.2493 -0.1466 H 0000000000000000000\n 2.1638 1.2493 -0.1466 H 0000000000000000000\n 0.0000 2.4985 0.1466 H 0000000000000000000\n -2.1638 -1.2493 0.1466 H 0000000000000000000\n 2.1638 -1.2493 0.1466 H 0000000000000000000\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 1 13 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 2 8 1 0 0 0\n 2 14 1 0 0 0\n 3 4 1 0 0 0\n 3 6 1 0 0 0\n 3 9 1 0 0 0\n 3 15 1 0 0 0\n 4 10 1 0 0 0\n 4 16 1 0 0 0\n 5 11 1 0 0 0\n 5 17 1 0 0 0\n 6 12 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), - new TestChemical("Cyclopenten", - "Cyclopenten ist eine organische Verbindung mit der Summenformel C5H8. Sie besteht aus einem fünfgliedrigen, ungesättigten Ring, welcher eine Doppelbindung aufweist. In der homologen Reihe der Cycloalkene steht Cyclopenten zwischen Cyclobuten und Cyclohexen. Formal handelt es sich um ein einfach hydriertes Cyclopentadien beziehungsweise ein einfach dehydriertes Cyclopentan. Cyclopenten besitzt nur wenige Anwendungen.", - "", "C5H8", 68.1170m, "142-29-0", - "\n\n\n 13 13 0 0 0 1 V2000\n 0.7026 2.1487 1.2803 C 0 0 0 0 0 0 0 0 0\n 1.9143 3.0520 1.0028 C 0 0 0 0 0 0 0 0 0\n 3.1024 2.1512 1.1173 C 0 0 0 0 0 0 0 0 0\n 2.7505 0.8940 1.4098 C 0 0 0 0 0 0 0 0 0\n 1.2685 0.7440 1.5398 C 0 0 0 0 0 0 0 0 0\n 0.1217 2.5129 2.1461 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.1470 0.4281 H 0 0 0 0 0 0 0 0 0\n 1.8684 3.5149 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.9848 3.8877 1.7230 H 0 0 0 0 0 0 0 0 0\n 4.1143 2.5269 0.9699 H 0 0 0 0 0 0 0 0 0\n 3.4198 0.0455 1.5471 H 0 0 0 0 0 0 0 0 0\n 0.8848 0.0000 0.8178 H 0 0 0 0 0 0 0 0 0\n 0.9989 0.3651 2.5427 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 5 1 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 4 2 0 0 0\n 3 10 1 0 0 0\n 4 5 1 0 0 0\n 4 11 1 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\nM END\n"), - new TestChemical("Diethylether", - "Diethylether ist der wichtigste Vertreter der organisch-chemischen Verbindungsklasse der Ether und wird deshalb häufig auch einfach als Ether (standardsprachlich und in der älteren wissenschaftlichen Literatur Äther) bezeichnet. Aufgrund der Herstellung aus Ethanol und Schwefelsäure war die historische Bezeichnung Schwefeläther, obwohl Diethylether keinen Schwefel enthält.", - "Ethane, 1,1'-oxybis-; Anaesthetic ether; Anesthesia ether; Anesthetic ether; Diethyl ether; Diethyl oxide; Ethoxyethane; Pronarcol; Solvent ether; 1,1'-Oxybisethane; (C2H5)2O; Aether; Diaethylaether; Dwuetylowy eter; Etere etilico; Ether ethylique; Ether, ethyl; Ethyl ether, tech.; Ethyl oxide; Oxyde d'ethyle; Rcra waste number U117; UN 1155; 3-Oxapentane; Ether; Ethyl ether anhydrous A.C.S.; Sulfuric ether; NSC 100036", - "C4H10O", 74.1216m, "60-29-7", - "\n\n\n 15 14 0 0 0 1 V2000\n 0.9744 2.7710 2.3574 C 0 0 0 0 0 0 0 0 0\n 0.9176 4.1589 1.7420 C 0 0 0 0 0 0 0 0 0\n 1.8106 1.8731 1.6596 O 0 0 0 0 0 0 0 0 0\n 3.2030 2.0811 1.8614 C 0 0 0 0 0 0 0 0 0\n 3.9340 1.0107 1.0734 C 0 0 0 0 0 0 0 0 0\n 0.0000 2.2572 2.2827 H 0 0 0 0 0 0 0 0 0\n 1.2519 2.8230 3.4284 H 0 0 0 0 0 0 0 0 0\n 0.1388 4.7597 2.2281 H 0 0 0 0 0 0 0 0 0\n 0.6868 4.1218 0.6694 H 0 0 0 0 0 0 0 0 0\n 1.8666 4.7007 1.8520 H 0 0 0 0 0 0 0 0 0\n 3.4420 2.0182 2.9412 H 0 0 0 0 0 0 0 0 0\n 3.4890 3.0953 1.5205 H 0 0 0 0 0 0 0 0 0\n 5.0187 1.1245 1.1931 H 0 0 0 0 0 0 0 0 0\n 3.7104 1.0671 0.0000 H 0 0 0 0 0 0 0 0 0\n 3.6667 0.0000 1.4086 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 4 1 0 0 0\n 4 5 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 5 15 1 0 0 0\nM END\n"), - new TestChemical("Ethanol", - "Ethanol (häufige Trivialnamen: Äthanol, Ethylalkohol, Alkohol) ist ein aliphatischer, einwertiger Alkohol mit der Summenformel C2H6O. Die reine Substanz ist eine bei Raumtemperatur farblose, leicht entzündliche Flüssigkeit mit einem brennenden Geschmack und einem charakteristischen, würzigen (süßlichen) Geruch. Die als Lebergift eingestufte Droge wird bei der Herstellung von Genussmitteln und alkoholischen Getränken wie Wein, Bier und Spirituosen aus kohlehydrathaltigem Material durch eine von Hefen ausgelöste Gärung in relativ großen Mengen produziert.", - "Ethyl alcohol; Alcohol; Alcohol anhydrous; Algrain; Anhydrol; Denatured ethanol; Ethyl hydrate; Ethyl hydroxide; Jaysol; Jaysol S; Methylcarbinol; SD Alchol 23-hydrogen; Tecsol; C2H5OH; Absolute ethanol; Cologne spirit; Fermentation alcohol; Grain alcohol; Molasses alcohol; Potato alcohol; Aethanol; Aethylalkohol; Alcohol, dehydrated; Alcool ethylique; Alcool etilico; Alkohol; Cologne spirits; Denatured alcohol CD-10; Denatured alcohol CD-5; Denatured alcohol CD-5a; Denatured alcohol SD-1; Denatured alcohol SD-13a; Denatured alcohol SD-17; Denatured alcohol SD-23a; Denatured alcohol SD-28; Denatured alcohol SD-3a; Denatured alcohol SD-30; Denatured alcohol SD-39b; Denatured alcohol SD-39c; Denatured alcohol SD-40m; Etanolo; Ethanol 200 proof; Ethyl alc; Etylowy alkohol; EtOH; NCI-C03134; Spirits of wine; Spirt; Alkoholu etylowego; Ethyl alcohol anhydrous; SD alcohol 23-hydrogen; UN 1170; Tecsol C; Alcare Hand Degermer; Absolute alcohol; Denatured alcohol; Ethanol, silent spirit; Ethylol; Punctilious ethyl alcohol; SD 3A", - "C2H6O", 46.0684m, "64-17-5", - "\n\n\n 9 8 0 0 0 0 0 0 0 0999 V2000\n 1.0195 0.8856 0.9752 C 0 0 0 0 0\n 1.8780 1.9882 1.5739 C 0 0 0 0 0\n 3.1989 1.4758 1.7291 O 0 0 0 0 0\n -0.0045 1.2392 0.8098 H 0 0 0 0 0\n 0.9875 0.0188 1.6438 H 0 0 0 0 0\n 1.4360 0.5612 0.0153 H 0 0 0 0 0\n 1.8717 2.8699 0.9114 H 0 0 0 0 0\n 1.4594 2.3055 2.5439 H 0 0 0 0 0\n 3.7472 2.1776 2.1115 H 0 0 0 0 0\n 6 1 1 0 0 0\n 4 1 1 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 7 2 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\nM END\n"), - new TestChemical("Essigsäureethylester", - "Essigsäureethylester, auch Ethylacetat oder Essigester, ist eine chemische Verbindung aus der Gruppe der Carbonsäureester. Es ist der Ester von Essigsäure und Ethanol. Die farblose Flüssigkeit ist ein charakteristisch nach Klebstoff riechendes Lösungsmittel, das in der chemischen Industrie und in Laboratorien oft verwendet wird.", - "Acetic acid, ethyl ester; Acetic ether; Acetidin; Acetoxyethane; Ethyl acetic ester; Ethyl ethanoate; Vinegar naphtha; CH3COOC2H5; Aethylacetat; Essigester; Ethyle (acetate d'); Etile (acetato di); Ethylacetaat; Ethylester kyseliny octove; Rcra waste number U112; UN 1173; Ethyl ester of acetic acid; 1-Acetoxyethane; NSC 70930; ac. acetic ethyl ester", - "C4H8O2", 88.1051m, "141-78-6", - "\n\n\n 14 13 0 0 0 0 0 0 0 0999 V2000\n 3.4731 2.2227 2.4832 O 0 0 0 0 0\n 2.1749 2.8339 2.4803 C 0 0 0 0 0\n 2.2477 4.1592 1.7469 C 0 0 0 0 0\n 3.7187 1.3350 1.4827 C 0 0 0 0 0\n 5.1346 0.8483 1.5545 C 0 0 0 0 0\n 2.9285 1.0057 0.6092 O 0 0 0 0 0\n 1.4145 2.1783 2.0421 H 0 0 0 0 0\n 1.8932 3.0019 3.5247 H 0 0 0 0 0\n 1.2769 4.6623 1.7528 H 0 0 0 0 0\n 2.9878 4.8162 2.2162 H 0 0 0 0 0\n 2.5643 4.0145 0.7089 H 0 0 0 0 0\n 5.1634 -0.2191 1.3199 H 0 0 0 0 0\n 5.7451 1.4089 0.8424 H 0 0 0 0 0\n 5.5400 0.9802 2.5620 H 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 5 1 0 0 0\n 4 6 2 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\nM END\n"), - new TestChemical("Ethylpropylether", "Ethylpropylether", - "Ether, ethyl propyl; Ethyl n-propyl ether; Ethyl propyl ether; Propyl ethyl ether; 1-Ethoxypropane; n-C3H7OC2H5; UN 2615", "C5H12O", 88.1482m, - "628-32-0", - "\n\n\n 18 17 0 0 0 1 V2000\n 3.1079 1.7452 1.0645 O 0 0 0 0 0 0 0 0 0\n 4.4441 1.2694 1.1114 C 0 0 0 0 0 0 0 0 0\n 5.3490 2.4893 1.2632 C 0 0 0 0 0 0 0 0 0\n 2.1504 0.7063 0.9251 C 0 0 0 0 0 0 0 0 0\n 6.8002 2.0698 1.3208 C 0 0 0 0 0 0 0 0 0\n 0.7827 1.3612 0.8832 C 0 0 0 0 0 0 0 0 0\n 4.6830 0.7077 0.1862 H 0 0 0 0 0 0 0 0 0\n 4.5718 0.5698 1.9616 H 0 0 0 0 0 0 0 0 0\n 5.0789 3.0544 2.1773 H 0 0 0 0 0 0 0 0 0\n 5.1881 3.1923 0.4219 H 0 0 0 0 0 0 0 0 0\n 2.2307 0.0000 1.7749 H 0 0 0 0 0 0 0 0 0\n 2.3473 0.1294 0.0000 H 0 0 0 0 0 0 0 0 0\n 7.4601 2.9403 1.4280 H 0 0 0 0 0 0 0 0 0\n 7.1058 1.5371 0.4105 H 0 0 0 0 0 0 0 0 0\n 6.9969 1.4029 2.1707 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.5997 0.7766 H 0 0 0 0 0 0 0 0 0\n 0.6863 2.0571 0.0395 H 0 0 0 0 0 0 0 0 0\n 0.5712 1.9289 1.7988 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 5 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 4 6 1 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 5 15 1 0 0 0\n 6 16 1 0 0 0\n 6 17 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), - new TestChemical("Essigsäureisopropylester", - "Essigsäureisopropylester ist eine chemische Verbindung aus der Gruppe der Carbonsäureester. Es ist eine farblose, leichtentzündliche und flüchtige Flüssigkeit.", - "Acetic acid, 1-methylethyl ester; Acetic acid, isopropyl ester; 2-Acetoxypropane; 2-Propyl acetate; CH3COOCH(CH3)2; Acetate d'isopropyle; Isopropile(acetato di); Isopropyl ethanoate; Isopropyl (acetate d'); Isopropylacetaat; Isopropylacetat; Isopropylester kyseliny octove; UN 1220; Isopropyl ester of acetic acid; sec-Propyl acetate; Acetic acid, 2-propyl ester; 1-Methylethyl acetate; NSC 9295", - "C5H10O2", 102.1317m, "108-21-4", - "\n\n\n 17 16 0 0 0 1 V2000\n 3.1938 1.0106 1.8174 O 0 0 0 0 0 0 0 0 0\n 2.0014 1.7632 2.1082 C 0 0 0 0 0 0 0 0 0\n 4.4164 1.5950 1.9641 C 0 0 0 0 0 0 0 0 0\n 0.9499 0.7230 2.4807 C 0 0 0 0 0 0 0 0 0\n 1.5706 2.5640 0.8846 C 0 0 0 0 0 0 0 0 0\n 5.5443 0.6574 1.6359 C 0 0 0 0 0 0 0 0 0\n 4.5054 2.7501 2.3330 O 0 0 0 0 0 0 0 0 0\n 2.1755 2.4482 2.9734 H 0 0 0 0 0 0 0 0 0\n 1.2515 0.1324 3.3552 H 0 0 0 0 0 0 0 0 0\n 0.7564 0.0192 1.6598 H 0 0 0 0 0 0 0 0 0\n 0.0000 1.2159 2.7240 H 0 0 0 0 0 0 0 0 0\n 1.4415 1.9261 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.6116 3.0626 1.0738 H 0 0 0 0 0 0 0 0 0\n 2.3036 3.3399 0.6292 H 0 0 0 0 0 0 0 0 0\n 5.3046 0.0000 0.7896 H 0 0 0 0 0 0 0 0 0\n 5.7670 0.0184 2.5009 H 0 0 0 0 0 0 0 0 0\n 6.4576 1.2122 1.3851 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 2 8 1 0 0 0\n 3 6 1 0 0 0\n 3 7 2 0 0 0\n 4 9 1 0 0 0\n 4 10 1 0 0 0\n 4 11 1 0 0 0\n 5 12 1 0 0 0\n 5 13 1 0 0 0\n 5 14 1 0 0 0\n 6 15 1 0 0 0\n 6 16 1 0 0 0\n 6 17 1 0 0 0\nM END\n"), - new TestChemical( - "Methan", - "Methan ist eine chemische Verbindung aus der Gruppe der Alkane mit der Summenformel CH4. Das farb- und geruchlose, brennbare Gas kommt in der Natur vor und ist ein Hauptbestandteil von Erdgas. Es dient als Heizgas und ist in der chemischen Industrie als Ausgangsprodukt für technische Synthesen von großer Bedeutung.", - "Marsh gas; Methyl hydride; CH4; Fire Damp; R 50; Biogas; R 50 (refrigerant)", "CH4", 16.0425m, "74-82-8", - "\n\n\n 5 4 0 0 0 1 V2000\n 1.0582 0.9353 0.8103 C 0 0 0 0 0 0 0 0 0\n 1.4145 1.5662 0.0000 H 0 0 0 0 0 0 0 0 0\n 1.2065 1.4452 1.7588 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.7294 0.6710 H 0 0 0 0 0 0 0 0 0\n 1.6121 0.0000 0.8114 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\nM END\n"), - new TestChemical("Methylalkohol", - "Methanol, auch Methylalkohol, ist eine organische chemische Verbindung mit der Summenformel CH4O (Halbstrukturformel: CH3OH) und der einfachste Vertreter aus der Stoffgruppe der Alkohole. Unter Normalbedingungen ist Methanol eine klare, farblose, entzündliche und leicht flüchtige Flüssigkeit mit alkoholischem Geruch.", - "Methanol; Carbinol; Methyl hydroxide; Methylol; Monohydroxymethane; Wood alcohol; CH3OH; Colonial spirit; Columbian spirit; Hydroxymethane; Wood naphtha; Alcool methylique; Alcool metilico; Columbian spirits; Metanolo; Methylalkohol; Metylowy alkohol; Pyroxylic spirit; Wood spirit; Rcra waste number U154; UN 1230; Pyro alcohol; Spirit of wood; Bieleski's solution; NSC 85232", - "CH4O", 32.0419m, "67-56-1", - "\n\n\n 6 5 0 0 0 1 V2000\n 0.2453 0.8386 1.6056 H 0 0 0 0 0 0 0 0 0\n 0.6776 0.9803 0.6074 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.5869 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.8133 0.0000 0.1338 H 0 0 0 0 0 0 0 0 0\n 1.8631 1.7142 0.6464 O 0 0 0 0 0 0 0 0 0\n 2.4856 1.2216 1.1660 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 2 3 1 0 0 0\n 2 4 1 0 0 0\n 2 5 1 0 0 0\n 5 6 1 0 0 0\nM END\n"), - new TestChemical("2-Butanone", - "Butanon ist neben Aceton eines der wichtigsten industriell genutzten Ketone. Es ist eine farblose, leicht bewegliche Flüssigkeit mit einem typischen Geruch und wird allgemein als Methylethylketon (MEK) bezeichnet.", - "Butan-2-one; Butanone; Ethyl methyl ketone; Ketone, methyl ethyl; Methyl ethyl ketone; MEK; C2H5COCH3; Acetone, methyl-; Aethylmethylketon; 3-Butanone; Butanone 2; Ethyl methyl cetone; Ethylmethylketon; Ketone, ethyl methyl; Meetco; Methyl acetone; Metiletilchetone; Metyloetyloketon; Rcra waste number U159; UN 1193; 2-Oxobutane; 2-Butanal; 2-butanone (MEK; methyl ethyl ketone); 2-butanone (MEK)", - "C4H8O", 72.1057m, "78-93-3", - "\n\n\n 13 12 0 0 0 1 V2000\n 1.7893 1.4200 2.8269 C 0 0 0 0 0 0 0 0 0\n 0.8451 2.5827 2.6164 C 0 0 0 0 0 0 0 0 0\n 2.3705 0.9490 1.5071 C 0 0 0 0 0 0 0 0 0\n 3.5557 1.7224 0.9969 C 0 0 0 0 0 0 0 0 0\n 1.9075 0.0000 0.9038 O 0 0 0 0 0 0 0 0 0\n 2.6155 1.7113 3.5049 H 0 0 0 0 0 0 0 0 0\n 1.2589 0.5906 3.3355 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.3072 1.9722 H 0 0 0 0 0 0 0 0 0\n 0.4300 2.9289 3.5716 H 0 0 0 0 0 0 0 0 0\n 1.3499 3.4390 2.1488 H 0 0 0 0 0 0 0 0 0\n 3.8802 1.3974 0.0000 H 0 0 0 0 0 0 0 0 0\n 3.3200 2.7937 0.9410 H 0 0 0 0 0 0 0 0 0\n 4.4074 1.6050 1.6799 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 4 1 0 0 0\n 3 5 2 0 0 0\n 4 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\nM END\n"), - new TestChemical( - "Butan", - "Die Butane sind eine Stoffgruppe innerhalb der Alkane, die die Summenformel C4H10 aufweisen. Sie besteht aus den beiden Vertretern n-Butan und iso-Butan, die zueinander isomer sind. Beide Butane sind farblose, brennbare, leicht zu verflüssigende Gase („Flüssiggase“), die sich kaum in Wasser, aber gut in Ethanol und Ether lösen.", - "n-Butane; Diethyl; Freon 600; Liquefied petroleum gas; LPG; n-C4H10; Butanen; Butani; Methylethylmethane; UN 1011; A 21; HC 600; HC 600 (hydrocarbon); R 600; R 600 (alkane)", - "C4H10", 58.1222m, "106-97-8", - "\n\n\n 14 13 0 0 0 1 V2000\n 3.5864 1.1360 0.9321 C 0 0 0 0 0 0 0 0 0\n 2.5594 0.8276 1.9979 C 0 0 0 0 0 0 0 0 0\n 1.7180 2.0488 2.3336 C 0 0 0 0 0 0 0 0 0\n 0.6912 1.7404 3.3995 C 0 0 0 0 0 0 0 0 0\n 3.1136 1.4730 0.0000 H 0 0 0 0 0 0 0 0 0\n 4.1901 0.2515 0.6922 H 0 0 0 0 0 0 0 0 0\n 4.2769 1.9280 1.2514 H 0 0 0 0 0 0 0 0 0\n 3.0625 0.4552 2.9124 H 0 0 0 0 0 0 0 0 0\n 1.9030 0.0000 1.6631 H 0 0 0 0 0 0 0 0 0\n 1.2148 2.4207 1.4190 H 0 0 0 0 0 0 0 0 0\n 2.3747 2.8766 2.6678 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.9495 3.0791 H 0 0 0 0 0 0 0 0 0\n 0.0880 2.6250 3.6404 H 0 0 0 0 0 0 0 0 0\n 1.1635 1.4017 4.3313 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 4 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\nM END\n"), - new TestChemical("Butanol", - "1-Butanol (auch n-Butanol oder nach IUPAC Butan-1-ol) ist eine chemische Verbindung aus der Gruppe der Alkanole. Der primäre Alkohol leitet sich vom aliphatischen Kohlenwasserstoff n-Butan ab.", - "Butyl alcohol; n-Butan-1-ol; n-Butanol; n-Butyl alcohol; Butyl hydroxide; CCS 203; Hemostyp; Methylolpropane; Propylcarbinol; n-C4H9OH; Butanol; Butan-1-ol; 1-Hydroxybutane; Alcool butylique; Butanolo; Butylowy alkohol; Butyric alcohol; Propylmethanol; Butanolen; 1-Butyl alcohol; Rcra waste number U031; Butanol-1; NSC 62782", - "C4H10O", 74.1216m, "71-36-3", - "\n\n\n 15 14 0 0 0 1 V2000\n 2.9651 2.0464 2.4042 C 0 0 0 0 0 0 0 0 0\n 2.3281 2.7103 1.1934 C 0 0 0 0 0 0 0 0 0\n 0.8652 2.3158 0.9860 C 0 0 0 0 0 0 0 0 0\n 3.3109 0.5952 2.1559 C 0 0 0 0 0 0 0 0 0\n 0.0000 2.7697 1.9922 O 0 0 0 0 0 0 0 0 0\n 3.8836 2.6000 2.6826 H 0 0 0 0 0 0 0 0 0\n 2.2886 2.1307 3.2779 H 0 0 0 0 0 0 0 0 0\n 2.8987 2.4514 0.2796 H 0 0 0 0 0 0 0 0 0\n 2.4074 3.8120 1.2923 H 0 0 0 0 0 0 0 0 0\n 0.5089 2.6739 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.7244 1.2206 1.0177 H 0 0 0 0 0 0 0 0 0\n 2.4210 0.0000 1.9086 H 0 0 0 0 0 0 0 0 0\n 3.7712 0.1403 3.0423 H 0 0 0 0 0 0 0 0 0\n 4.0193 0.4827 1.3240 H 0 0 0 0 0 0 0 0 0\n 0.1229 3.7075 2.0732 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 5 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\nM END\n"), - new TestChemical("Heptan", "Heptane sind zu den Alkanen zählende Kohlenwasserstoffe mit der Summenformel C7H16. Es existieren neun Konstitutionsisomere", - "n-Heptane; Dipropylmethane; Heptyl hydride; Skellysolve C; n-C7H16; Eptani; Heptan; Heptanen; Gettysolve-C; NSC 62784", "C7H16", 100.2019m, - "142-82-5", - "\n\n\n 23 22 0 0 0 1 V2000\n 4.9119 1.1117 1.6160 C 0 0 0 0 0 0 0 0 0\n 3.6978 1.8855 1.1285 C 0 0 0 0 0 0 0 0 0\n 2.4040 1.2047 1.5468 C 0 0 0 0 0 0 0 0 0\n 6.2065 1.7895 1.1965 C 0 0 0 0 0 0 0 0 0\n 1.1708 1.9589 1.0725 C 0 0 0 0 0 0 0 0 0\n 7.4159 1.0214 1.6802 C 0 0 0 0 0 0 0 0 0\n 0.9041 3.2087 1.8814 C 0 0 0 0 0 0 0 0 0\n 4.8843 0.0753 1.2230 H 0 0 0 0 0 0 0 0 0\n 4.8794 1.0110 2.7196 H 0 0 0 0 0 0 0 0 0\n 3.7266 2.9218 1.5233 H 0 0 0 0 0 0 0 0 0\n 3.7322 1.9883 0.0250 H 0 0 0 0 0 0 0 0 0\n 2.3839 0.1736 1.1402 H 0 0 0 0 0 0 0 0 0\n 2.3759 1.0904 2.6492 H 0 0 0 0 0 0 0 0 0\n 6.2340 2.8252 1.5899 H 0 0 0 0 0 0 0 0 0\n 6.2388 1.8907 0.0934 H 0 0 0 0 0 0 0 0 0\n 1.2758 2.2178 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.2900 1.2893 1.1316 H 0 0 0 0 0 0 0 0 0\n 7.4323 0.9394 2.7752 H 0 0 0 0 0 0 0 0 0\n 8.3484 1.5122 1.3742 H 0 0 0 0 0 0 0 0 0\n 7.4348 0.0000 1.2773 H 0 0 0 0 0 0 0 0 0\n 1.7349 3.9244 1.8126 H 0 0 0 0 0 0 0 0 0\n 0.7607 2.9796 2.9459 H 0 0 0 0 0 0 0 0 0\n 0.0000 3.7234 1.5317 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 8 1 0 0 0\n 1 9 1 0 0 0\n 2 3 1 0 0 0\n 2 10 1 0 0 0\n 2 11 1 0 0 0\n 3 5 1 0 0 0\n 3 12 1 0 0 0\n 3 13 1 0 0 0\n 4 6 1 0 0 0\n 4 14 1 0 0 0\n 4 15 1 0 0 0\n 5 7 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\nM END\n"), - new TestChemical( - "Hexan", - "n-Hexan ist eine den Alkanen (gesättigte Kohlenwasserstoffe) zugehörige chemische Verbindung. Es ist eine farblose Flüssigkeit mit der Summenformel C6H14. Es ist das unverzweigte Isomer der fünf Hexanisomeren.", - "n-Hexane; Skellysolve B; n-C6H14; Esani; Heksan; Hexanen; Hexyl hydride; Gettysolve-B; NCI-C60571; NSC 68472", "C6H14", 86.1754m, "110-54-3", - "\n\n\n 20 19 0 0 0 1 V2000\n 3.0831 1.0831 1.9494 C 0 0 0 0 0 0 0 0 0\n 2.9735 1.4738 3.4178 C 0 0 0 0 0 0 0 0 0\n 3.7734 2.7246 3.7510 C 0 0 0 0 0 0 0 0 0\n 2.1289 1.8691 1.0623 C 0 0 0 0 0 0 0 0 0\n 5.2567 2.5130 3.5423 C 0 0 0 0 0 0 0 0 0\n 0.6830 1.5862 1.4060 C 0 0 0 0 0 0 0 0 0\n 2.8829 0.0000 1.8355 H 0 0 0 0 0 0 0 0 0\n 4.1330 1.2389 1.6114 H 0 0 0 0 0 0 0 0 0\n 1.9001 1.6293 3.6713 H 0 0 0 0 0 0 0 0 0\n 3.3194 0.6360 4.0538 H 0 0 0 0 0 0 0 0 0\n 3.4222 3.5715 3.1275 H 0 0 0 0 0 0 0 0 0\n 3.5761 3.0236 4.7988 H 0 0 0 0 0 0 0 0 0\n 2.3340 2.9542 1.1601 H 0 0 0 0 0 0 0 0 0\n 2.3218 1.6234 0.0000 H 0 0 0 0 0 0 0 0 0\n 5.8188 3.4487 3.6460 H 0 0 0 0 0 0 0 0 0\n 5.6708 1.7956 4.2625 H 0 0 0 0 0 0 0 0 0\n 5.4489 2.1109 2.5328 H 0 0 0 0 0 0 0 0 0\n 0.5108 1.7246 2.4871 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.2530 0.8669 H 0 0 0 0 0 0 0 0 0\n 0.4010 0.5540 1.1613 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 7 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 5 1 0 0 0\n 3 11 1 0 0 0\n 3 12 1 0 0 0\n 4 6 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\nM END\n"), - new TestChemical( - "Octan", - "n-Octan ist eine farblose Flüssigkeit, die zu den Alkanen zählt. In der Chemie wird es entsprechend den aktuellen Nomenklaturregeln als n-Octan geschrieben, in Deutschland wird jedoch oft – gerade im Zusammenhang mit der Oktanzahl – die veraltete Schreibweise Oktan bevorzugt. Es handelt sich um den unverzweigten Vertreter der 18 Isomere der Octane.", - "n-Octane; n-C8H18; Oktan; Oktanen; Ottani; UN 1262", "C8H18", 114.2285m, "111-65-9", - "\n\n\n 26 25 0 0 0 1 V2000\n 3.8144 2.3469 1.6808 C 0 0 0 0 0 0 0 0 0\n 2.8536 2.9952 2.6656 C 0 0 0 0 0 0 0 0 0\n 1.4042 2.8035 2.2422 C 0 0 0 0 0 0 0 0 0\n 5.2606 2.6768 2.0091 C 0 0 0 0 0 0 0 0 0\n 1.0820 3.5021 0.9282 C 0 0 0 0 0 0 0 0 0\n 6.2381 2.0228 1.0442 C 0 0 0 0 0 0 0 0 0\n 0.8478 4.9859 1.0983 C 0 0 0 0 0 0 0 0 0\n 6.4020 0.5402 1.2955 C 0 0 0 0 0 0 0 0 0\n 3.5588 2.6881 0.6508 H 0 0 0 0 0 0 0 0 0\n 3.6652 1.2480 1.6733 H 0 0 0 0 0 0 0 0 0\n 3.0793 4.0774 2.7579 H 0 0 0 0 0 0 0 0 0\n 3.0069 2.5713 3.6775 H 0 0 0 0 0 0 0 0 0\n 0.7310 3.1711 3.0415 H 0 0 0 0 0 0 0 0 0\n 1.1916 1.7202 2.1456 H 0 0 0 0 0 0 0 0 0\n 5.4911 2.3686 3.0487 H 0 0 0 0 0 0 0 0 0\n 5.4015 3.7762 1.9864 H 0 0 0 0 0 0 0 0 0\n 0.1878 3.0372 0.4697 H 0 0 0 0 0 0 0 0 0\n 1.9182 3.3248 0.2134 H 0 0 0 0 0 0 0 0 0\n 7.2242 2.5203 1.1318 H 0 0 0 0 0 0 0 0 0\n 5.9096 2.1958 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.6299 5.4668 0.1362 H 0 0 0 0 0 0 0 0 0\n 0.0000 5.1885 1.7663 H 0 0 0 0 0 0 0 0 0\n 1.7255 5.4904 1.5249 H 0 0 0 0 0 0 0 0 0\n 5.4519 0.0000 1.1826 H 0 0 0 0 0 0 0 0 0\n 7.1172 0.0943 0.5924 H 0 0 0 0 0 0 0 0 0\n 6.7707 0.3401 2.3104 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 9 1 0 0 0\n 1 10 1 0 0 0\n 2 3 1 0 0 0\n 2 11 1 0 0 0\n 2 12 1 0 0 0\n 3 5 1 0 0 0\n 3 13 1 0 0 0\n 3 14 1 0 0 0\n 4 6 1 0 0 0\n 4 15 1 0 0 0\n 4 16 1 0 0 0\n 5 7 1 0 0 0\n 5 17 1 0 0 0\n 5 18 1 0 0 0\n 6 8 1 0 0 0\n 6 19 1 0 0 0\n 6 20 1 0 0 0\n 7 21 1 0 0 0\n 7 22 1 0 0 0\n 7 23 1 0 0 0\n 8 24 1 0 0 0\n 8 25 1 0 0 0\n 8 26 1 0 0 0\nM END\n"), - new TestChemical( - "Pentan", - "Pentane sind Kohlenwasserstoffe mit der Summenformel C5H12 und zählen zu den Alkanen. Es existieren drei Konstitutionsisomere: n-Pentan, Isopentan und Neopentan.", - "n-Pentane; Skellysolve A; n-C5H12; Pentan; Pentanen; Pentani; Amyl hydride; NSC 72415", "C5H12", 72.1488m, "109-66-0", - "\n\n\n 17 16 0 0 0 1 V2000\n 3.7280 2.4135 2.8751 C 0 0 0 0 0 0 0 0 0\n 2.5997 1.4666 2.4988 C 0 0 0 0 0 0 0 0 0\n 1.8538 1.9510 1.2659 C 0 0 0 0 0 0 0 0 0\n 4.4698 1.9317 4.1013 C 0 0 0 0 0 0 0 0 0\n 0.7349 1.0066 0.8889 C 0 0 0 0 0 0 0 0 0\n 3.3234 3.4299 3.0526 H 0 0 0 0 0 0 0 0 0\n 4.4308 2.5184 2.0246 H 0 0 0 0 0 0 0 0 0\n 1.8961 1.3604 3.3490 H 0 0 0 0 0 0 0 0 0\n 3.0039 0.4497 2.3206 H 0 0 0 0 0 0 0 0 0\n 2.5589 2.0599 0.4178 H 0 0 0 0 0 0 0 0 0\n 1.4479 2.9664 1.4462 H 0 0 0 0 0 0 0 0 0\n 5.2821 2.6183 4.3713 H 0 0 0 0 0 0 0 0 0\n 3.8049 1.8514 4.9716 H 0 0 0 0 0 0 0 0 0\n 4.9169 0.9415 3.9412 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.9059 1.6988 H 0 0 0 0 0 0 0 0 0\n 1.1125 0.0000 0.6654 H 0 0 0 0 0 0 0 0 0\n 0.1970 1.3600 0.0000 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 6 1 0 0 0\n 1 7 1 0 0 0\n 2 3 1 0 0 0\n 2 8 1 0 0 0\n 2 9 1 0 0 0\n 3 5 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\nM END\n"), - new TestChemical("1-Pentanol", - "1-Pentanol (veraltet: Amylalkohol) ist eine organische chemische Verbindung und gehört zu den Alkoholen. 1-Pentanol ist Bestandteil der Fuselöle.", - "Pentyl alcohol; n-Amyl alcohol; n-Butylcarbinol; n-Pentan-1-ol; n-Pentanol; n-Pentyl alcohol; Amyl alcohol; Amylol; Pentanol; 1-Pentyl alcohol; n-C5H11OH; Pentan-1-ol; Pentanol-1; Pentasol; n-Amylalkohol; Alcool amylique; Amyl alcohol, n-; Amyl alcohol, normal; Primary amyl alcohol; UN 1105; 1-Pentol; Primary-N-amyl alcohol; Butyl carbinol; NSC 5707", - "C5H12O", 88.1482m, "71-41-0", - "\n\n\n 18 17 0 0 0 1 V2000\n 2.0264 2.2581 1.9056 C 0 0 0 0 0 0 0 0 0\n 3.2462 1.6488 1.2359 C 0 0 0 0 0 0 0 0 0\n 4.4464 1.6425 2.1693 C 0 0 0 0 0 0 0 0 0\n 0.8116 2.2939 0.9786 C 0 0 0 0 0 0 0 0 0\n 5.6613 1.0380 1.5026 C 0 0 0 0 0 0 0 0 0\n 0.3196 1.0290 0.6256 O 0 0 0 0 0 0 0 0 0\n 2.2542 3.2923 2.2314 H 0 0 0 0 0 0 0 0 0\n 1.7856 1.7003 2.8338 H 0 0 0 0 0 0 0 0 0\n 3.0191 0.6161 0.9030 H 0 0 0 0 0 0 0 0 0\n 3.4892 2.2109 0.3117 H 0 0 0 0 0 0 0 0 0\n 4.6718 2.6757 2.5011 H 0 0 0 0 0 0 0 0 0\n 4.2030 1.0807 3.0931 H 0 0 0 0 0 0 0 0 0\n 1.0538 2.7449 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0000 2.8939 1.4351 H 0 0 0 0 0 0 0 0 0\n 5.4791 0.0000 1.1934 H 0 0 0 0 0 0 0 0 0\n 5.9510 1.5990 0.6041 H 0 0 0 0 0 0 0 0 0\n 6.5258 1.0323 2.1788 H 0 0 0 0 0 0 0 0 0\n 0.1634 0.5491 1.4297 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 7 1 0 0 0\n 1 8 1 0 0 0\n 2 3 1 0 0 0\n 2 9 1 0 0 0\n 2 10 1 0 0 0\n 3 5 1 0 0 0\n 3 11 1 0 0 0\n 3 12 1 0 0 0\n 4 6 1 0 0 0\n 4 13 1 0 0 0\n 4 14 1 0 0 0\n 5 15 1 0 0 0\n 5 16 1 0 0 0\n 5 17 1 0 0 0\n 6 18 1 0 0 0\nM END\n"), - new TestChemical("1-Propanol", - "Propanole sind Alkohole mit drei Kohlenstoffatomen und einer Hydroxygruppe (–OH). Sie haben die allgemeine Summenformel C3H8O und eine molare Masse von 60,10 g/mol. Es gibt nur zwei Isomere.", - "Propyl alcohol; n-Propan-1-ol; n-Propanol; n-Propyl alcohol; Ethylcarbinol; Optal; Osmosol extra; Propanol; Propylic alcohol; 1-Propyl alcohol; n-C3H7OH; 1-Hydroxypropane; Propanol-1; Propan-1-ol; n-Propyl alkohol; Alcool propilico; Alcool propylique; Propanole; Propanolen; Propanoli; Propylowy alkohol; UN 1274; Propylan-propyl alcohol; NSC 30300; Alcohol, propyl", - "C3H8O", 60.0950m, "71-23-8", - "\n\n\n 12 11 0 0 0 1 V2000\n 0.7713 1.5705 1.3838 C 0 0 0 0 0 0 0 0 0\n 2.1696 1.0226 1.0958 C 0 0 0 0 0 0 0 0 0\n 3.2631 1.9141 1.6398 C 0 0 0 0 0 0 0 0 0\n 0.3563 1.3950 2.7118 O 0 0 0 0 0 0 0 0 0\n 0.7013 2.6399 1.1017 H 0 0 0 0 0 0 0 0 0\n 0.0000 1.0213 0.8161 H 0 0 0 0 0 0 0 0 0\n 2.2872 0.9154 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.2716 0.0000 1.5099 H 0 0 0 0 0 0 0 0 0\n 3.2248 1.9921 2.7352 H 0 0 0 0 0 0 0 0 0\n 3.1932 2.9340 1.2381 H 0 0 0 0 0 0 0 0 0\n 4.2564 1.5270 1.3793 H 0 0 0 0 0 0 0 0 0\n 1.0138 1.7939 3.2691 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\n 4 12 1 0 0 0\nM END\n"), - new TestChemical( - "Phenol", - "Phenol (nach IUPAC: Benzenol, veraltet: Karbolsäure oder kurz Karbol) ist eine aromatische, organische Verbindung und besteht aus einer Phenylgruppe (–C6H5), an die eine Hydroxygruppe (–OH) gebunden ist. Der farblose, kristalline Feststoff ist eine wichtige Industriechemikalie und dient als Zwischenprodukt besonders zur Herstellung diverser Kunststoffe.", - "Carbolic acid; Baker's P and S Liquid and Ointment; Benzenol; Hydroxybenzene; Izal; Monohydroxybenzene; Monophenol; Oxybenzene; Phenic acid; Phenyl alcohol; Phenyl hydrate; Phenyl hydroxide; Phenylic acid; Phenylic alcohol; PhOH; Benzene, hydroxy-; Acide carbolique; Baker's P & S liquid & Ointment; Fenol; Fenolo; NCI-C50124; Paoscle; Phenole; Carbolsaure; NA 2821; Phenol alcohol; Phenol, molten; Rcra waste number U188; UN 1671; UN 2312; UN 2821; Phenic alcohol; NSC 36808", - "C6H6O", 94.1112m, "108-95-2", - "\n\n\n 13 13 0 0 0 1 V2000\n 0.3792 2.3991 0.0767 O 0 0 0 0 0 0 0 0 0\n 1.7410 2.2635 0.0604 C 0 0 0 0 0 0 0 0 0\n 2.4822 3.4537 0.0746 C 0 0 0 0 0 0 0 0 0\n 2.3800 1.0169 0.0335 C 0 0 0 0 0 0 0 0 0\n 3.8685 3.3780 0.0622 C 0 0 0 0 0 0 0 0 0\n 3.7689 0.9695 0.0214 C 0 0 0 0 0 0 0 0 0\n 4.5128 2.1443 0.0358 C 0 0 0 0 0 0 0 0 0\n 0.0000 1.5292 0.0598 H 0 0 0 0 0 0 0 0 0\n 1.9740 4.4239 0.0950 H 0 0 0 0 0 0 0 0 0\n 1.8001 0.0870 0.0221 H 0 0 0 0 0 0 0 0 0\n 4.4597 4.2998 0.0734 H 0 0 0 0 0 0 0 0 0\n 4.2770 0.0000 0.0000 H 0 0 0 0 0 0 0 0 0\n 5.6063 2.0990 0.0278 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 8 1 0 0 0\n 2 3 2 0 0 0\n 4 2 1 0 0 0\n 3 5 1 0 0 0\n 3 9 1 0 0 0\n 6 4 2 0 0 0\n 4 10 1 0 0 0\n 5 7 2 0 0 0\n 5 11 1 0 0 0\n 7 6 1 0 0 0\n 6 12 1 0 0 0\n 7 13 1 0 0 0\nM END\n"), - new TestChemical( - "Propan", "Propan ist ein farbloses brennbares Gas und gehört zu den Kohlenwasserstoffen. Es steht in der homologen Reihe der Alkane an dritter Stelle.", - "n-Propane; Dimethylmethane; Freon 290; Liquefied petroleum gas; LPG; Propyl hydride; R 290; C3H8; UN 1978; A-108; Hydrocarbon propellant A-108; HC 290", "C3H8", - 44.0956m, "74-98-6", - "\n\n\n 11 10 0 0 0 1 V2000\n 3.3461 1.6436 1.3326 C 0 0 0 0 0 0 0 0 0\n 2.0042 1.0740 0.9307 C 0 0 0 0 0 0 0 0 0\n 0.9734 1.2486 2.0232 C 0 0 0 0 0 0 0 0 0\n 3.7393 1.1540 2.2333 H 0 0 0 0 0 0 0 0 0\n 3.2805 2.7182 1.5491 H 0 0 0 0 0 0 0 0 0\n 4.0907 1.5138 0.5370 H 0 0 0 0 0 0 0 0 0\n 1.6524 1.5612 0.0000 H 0 0 0 0 0 0 0 0 0\n 2.1104 0.0000 0.6811 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.8391 1.7246 H 0 0 0 0 0 0 0 0 0\n 0.8224 2.3078 2.2707 H 0 0 0 0 0 0 0 0 0\n 1.2746 0.7385 2.9479 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 4 1 0 0 0\n 1 5 1 0 0 0\n 1 6 1 0 0 0\n 2 3 1 0 0 0\n 2 7 1 0 0 0\n 2 8 1 0 0 0\n 3 9 1 0 0 0\n 3 10 1 0 0 0\n 3 11 1 0 0 0\nM END\n"), - new TestChemical("p-Xylen", "", - "Benzene, 1,4-dimethyl-; p-Dimethylbenzene; p-Xylol; 1,4-Dimethylbenzene; 1,4-Xylene; p-Methyltoluene; para-Xylene; Chromar; Scintillar; 4-Methyltoluene; NSC 72419; 1,4-dimethyl-benzene ( p-xylene)", - "C8H10", 106.1650m, "106-42-3", - "\n\n\n 18 18 0 0 0 1 V2000\n 1.0336 0.8636 0.7240 C 0 0 0 0 0 0 0 0 0\n 1.8222 1.2612 1.9184 C 0 0 0 0 0 0 0 0 0\n 3.2175 1.2383 1.8736 C 0 0 0 0 0 0 0 0 0\n 1.1814 1.6695 3.0884 C 0 0 0 0 0 0 0 0 0\n 3.9602 1.6220 2.9826 C 0 0 0 0 0 0 0 0 0\n 1.9245 2.0535 4.1976 C 0 0 0 0 0 0 0 0 0\n 3.3192 2.0345 4.1521 C 0 0 0 0 0 0 0 0 0\n 4.1123 2.4606 5.3336 C 0 0 0 0 0 0 0 0 0\n 0.9917 1.6889 0.0000 H 0 0 0 0 0 0 0 0 0\n 0.0000 0.5972 0.9816 H 0 0 0 0 0 0 0 0 0\n 1.4818 0.0000 0.2149 H 0 0 0 0 0 0 0 0 0\n 3.7265 0.9144 0.9590 H 0 0 0 0 0 0 0 0 0\n 0.0866 1.6867 3.1337 H 0 0 0 0 0 0 0 0 0\n 5.0549 1.5997 2.9402 H 0 0 0 0 0 0 0 0 0\n 1.4139 2.3716 5.1134 H 0 0 0 0 0 0 0 0 0\n 5.0993 1.9796 5.3595 H 0 0 0 0 0 0 0 0 0\n 3.6010 2.2196 6.2750 H 0 0 0 0 0 0 0 0 0\n 4.2743 3.5470 5.3142 H 0 0 0 0 0 0 0 0 0\n 1 2 1 0 0 0\n 1 9 1 0 0 0\n 1 10 1 0 0 0\n 1 11 1 0 0 0\n 3 2 2 0 0 0\n 2 4 1 0 0 0\n 5 3 1 0 0 0\n 3 12 1 0 0 0\n 4 6 2 0 0 0\n 4 13 1 0 0 0\n 7 5 2 0 0 0\n 5 14 1 0 0 0\n 6 7 1 0 0 0\n 6 15 1 0 0 0\n 7 8 1 0 0 0\n 8 16 1 0 0 0\n 8 17 1 0 0 0\n 8 18 1 0 0 0\nM END\n"), - new TestChemical( - "Toluol", - "Toluol, Trivialname nach IUPAC auch Toluen, Methylbenzol, Phenylmethan, nach IUPAC-Nomenklatur Methylbenzen genannt, ist eine farblose, charakteristisch riechende, flüchtige Flüssigkeit, die in vielen ihrer Eigenschaften dem Benzol ähnelt. Toluol ist ein aromatischer Kohlenwasserstoff, häufig ersetzt es als Lösungsmittel das giftige Benzol. Es ist unter anderem auch im Benzin enthalten.", - "Benzene, methyl; Methacide; Methylbenzene; Methylbenzol; Phenylmethane; Antisal 1a; Toluol; Methane, phenyl-; NCI-C07272; Tolueen; Toluen; Toluolo; Rcra waste number U220; Tolu-sol; UN 1294; Dracyl; Monomethyl benzene; CP 25; NSC 406333; methylbenzene (toluene)", - "C7H8", 92.1384m, "108-88-3", - "\n\n\n 7 7 0 0 0 1 V2000\n 1.4722 1.7260 0.0000 C 0 0 0 0 0 0 0 0 0\n 0.9645 0.8630 0.0000 C 0 0 0 0 0 0 0 0 0\n 2.4874 1.7260 0.0000 C 0 0 0 0 0 0 0 0 0\n 2.9951 0.8630 0.0000 C 0 0 0 0 0 0 0 0 0\n 2.4874 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0\n 1.4722 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0\n 0.0000 0.8630 0.0000 C 0 0 0 0 0 0 0 0 0\n 1 2 2 0 0 0\n 3 1 1 0 0 0\n 2 6 1 0 0 0\n 2 7 1 0 0 0\n 4 3 2 0 0 0\n 5 4 1 0 0 0\n 6 5 2 0 0 0\nM END\n"), - new TestChemical( - "Wasser", - "Wasser (H2O) ist eine chemische Verbindung aus den Elementen Sauerstoff (O) und Wasserstoff (H). Wasser ist als Flüssigkeit durchsichtig, weitgehend farb-, geruch- und geschmacklos. Wasser ist die einzige chemische Verbindung auf der Erde, die in der Natur als Flüssigkeit, als Festkörper und als Gas vorkommt. Die Bezeichnung Wasser wird dabei für den flüssigen Aggregatzustand verwendet. Im festen Zustand spricht man von Eis, im gasförmigen Zustand von Wasserdampf. Wasser ist Grundlage des Lebens auf der Erde.", - "Water vapor; Distilled water; Ice; H2O; Dihydrogen oxide; steam; Tritiotope", "H2O", 18.0153m, "7732-18-5", - "\n\n\n 3 2 0 0 0 0 0 0 0 0999 V2000\n -0.2308 -0.3260 0.0000 O 0 0 0 0 0\n 0.7373 -0.2766 0.0000 H 0 0 0 0 0\n -0.5064 0.6026 0.0000 H 0 0 0 0 0\n 1 2 1 0 0 0\n 1 3 1 0 0 0\nM END\n") - }; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/TestPerson.cs b/src/abstractions/Backend.Fx/RandomData/TestPerson.cs deleted file mode 100644 index ed253807..00000000 --- a/src/abstractions/Backend.Fx/RandomData/TestPerson.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Backend.Fx.BuildingBlocks; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public class TestPerson : ValueObject - { - public enum Genders - { - Male, - Female - } - - private static readonly Dictionary InvalidCharacterReplacements = new Dictionary - { - {"À", "A"}, - {"Á", "A"}, - {"Â", "A"}, - {"Ã", "A"}, - {"Ä", "A"}, - {"Å", "A"}, - {"Æ", "A"}, - {"Ç", "C"}, - {"È", "E"}, - {"É", "E"}, - {"Ê", "E"}, - {"Ë", "E"}, - {"Ì", "I"}, - {"Í", "I"}, - {"Î", "I"}, - {"Ï", "I"}, - {"Ð", "D"}, - {"Ñ", "N"}, - {"Ò", "O"}, - {"Ó", "O"}, - {"Ô", "O"}, - {"Õ", "O"}, - {"Ö", "O"}, - {"×", "x"}, - {"Ø", "O"}, - {"Ù", "U"}, - {"Ú", "U"}, - {"Û", "U"}, - {"Ü", "U"}, - {"Ý", "Y"}, - {"Þ", "p"}, - {"ß", "ss"}, - {"à", "a"}, - {"á", "a"}, - {"â", "a"}, - {"ã", "a"}, - {"ä", "a"}, - {"å", "a"}, - {"æ", "a"}, - {"ç", "c"}, - {"è", "e"}, - {"é", "e"}, - {"ê", "e"}, - {"ë", "e"}, - {"ì", "i"}, - {"í", "i"}, - {"î", "i"}, - {"ï", "i"}, - {"ð", "o"}, - {"ñ", "n"}, - {"ò", "o"}, - {"ó", "o"}, - {"ô", "o"}, - {"õ", "o"}, - {"ö", "o"}, - {"÷", ""}, - {"ø", "o"}, - {"ù", ""}, - {"ú", "u"}, - {"û", "u"}, - {"ü", "u"}, - {"ý", "y"}, - {"þ", "p"}, - {"ÿ", "y"} - }; - - private readonly string _email; - - public TestPerson(string firstName, string middleName, string lastName, string title, Genders gender, DateTime dateOfBirth, string email = null) - { - _email = email; - FirstName = firstName; - MiddleName = middleName; - LastName = lastName; - Title = title; - Gender = gender; - DateOfBirth = dateOfBirth; - } - - public DateTime DateOfBirth { get; } - - public string FirstName { get; } - - public Genders Gender { get; } - - public string LastName { get; } - - public int Age - { - get - { - DateTime today = DateTime.Today; - var age = today.Year - DateOfBirth.Year; - if (DateOfBirth > today.AddYears(-age)) age--; - - return age; - } - } - - public string MiddleName { get; } - - public string Title { get; } - - public string Email => _email ?? $"{UserName}@no-email.not"; - - public string UserName => SanitizeForUserName(FirstName) + "." + SanitizeForUserName(LastName); - - private static string SanitizeForUserName(string s) - { - s = new string(s.Where(char.IsLetterOrDigit).ToArray()); - foreach (var invalidCharacterReplacement in InvalidCharacterReplacements) s = s.Replace(invalidCharacterReplacement.Key, invalidCharacterReplacement.Value); - return s; - } - - protected override IEnumerable GetEqualityComponents() - { - yield return DateOfBirth; - yield return FirstName; - yield return Gender; - yield return LastName; - yield return MiddleName; - yield return Title; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs b/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs deleted file mode 100644 index 9f1af527..00000000 --- a/src/abstractions/Backend.Fx/RandomData/TestPersonGenerator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public class TestPersonGenerator : Generator - { - private const int Year = 365; - private readonly Random _random = TestRandom.Instance; - private readonly HashSet _uniqueNames = new HashSet(); - - public bool EnforceUniqueNames { get; set; } - public int FemalePercentage { get; set; } = 55; - public int MaximumAgeInDays { get; set; } = 80 * Year; - public int MinimumAgeInDays { get; set; } = 18 * Year; - - public static TestPerson Generate() - { - return new TestPersonGenerator().First(); - } - - protected override TestPerson Next() - { - var isFemale = _random.Next(1, 100) < FemalePercentage; - TestPerson generated; - do - { - generated = new TestPerson( - isFemale ? Names.Female.Random() : Names.Male.Random(), - _random.Next(100) < 30 - ? isFemale ? Names.Female.Random() : Names.Male.Random() - : "", - Names.Family.Random(), - _random.Next(100) < 20 ? "Dr." : "", - isFemale ? TestPerson.Genders.Female : TestPerson.Genders.Male, - DateTime.UtcNow.AddDays(-_random.Next(MinimumAgeInDays, MaximumAgeInDays)).Date); - } while (EnforceUniqueNames && _uniqueNames.Contains($"{generated.FirstName}{generated.LastName}")); - - if (EnforceUniqueNames) - { - _uniqueNames.Add($"{generated.FirstName}{generated.LastName}"); - } - - return generated; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs b/src/abstractions/Backend.Fx/RandomData/TestRandom.cs deleted file mode 100644 index 0e80d9af..00000000 --- a/src/abstractions/Backend.Fx/RandomData/TestRandom.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; - -namespace Backend.Fx.RandomData -{ - [PublicAPI] - public static class TestRandom - { - public static Random Instance { get; set; } = new Random(429756); - - public static IEnumerable Next(int amount, Func generate) - { - for (var i = 0; i < amount; i++) yield return generate(); - } - - public static int Next() - { - return Instance.Next(); - } - - public static int Next(int max) - { - return Instance.Next(max); - } - - public static int Next(int min, int max) - { - return Instance.Next(min, max); - } - - public static bool NextBool() - { - return Instance.Next(2) == 1; - } - - public static double NextDouble() - { - return Instance.NextDouble(); - } - - public static DateTime RandomDateTime(int rangeDays) - { - return rangeDays < 0 - ? DateTime.UtcNow.AddDays(-Next(-rangeDays)).AddSeconds(-Next(100000)) - : DateTime.UtcNow.AddDays(Next(rangeDays)).AddSeconds(-Next(100000)); - } - - public static decimal RandomDecimal(int min = 0, int max = 999999) - { - var abs = Next(min, max); - return abs + Math.Round(Next(100) / 100m, 2); - } - - [Obsolete("See Letters.RandomPassword()")] - public static string NextPassword(int length = 10) - { - const string letters = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"; - const string numbers = "23456789"; - const string specials = "§$%&#+*-<>"; - var password = new char[length]; - - for (var i = 0; i < password.Length; i++) - { - var rnd = Next(100); - password[i] = rnd < 60 - ? letters.Random() - : rnd < 90 - ? numbers.Random() - : specials.Random(); - } - - return new string(password); - } - - public static decimal NextDecimal(decimal minimum, decimal maximum) - { - return (decimal) Instance.NextDouble() * (maximum - minimum) + minimum; - } - - public static bool NextProbability(int p) - { - return Instance.Next(0, 100) < p; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs b/src/abstractions/Backend.Fx/Util/AsyncHelper.cs similarity index 99% rename from src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs rename to src/abstractions/Backend.Fx/Util/AsyncHelper.cs index 95270634..c89fef9b 100644 --- a/src/abstractions/Backend.Fx/Extensions/AsyncHelper.cs +++ b/src/abstractions/Backend.Fx/Util/AsyncHelper.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { [PublicAPI] public static class AsyncHelper diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs b/src/abstractions/Backend.Fx/Util/CurrentTHolder.cs similarity index 94% rename from src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs rename to src/abstractions/Backend.Fx/Util/CurrentTHolder.cs index 02baa49a..94c3c11c 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/CurrentTHolder.cs +++ b/src/abstractions/Backend.Fx/Util/CurrentTHolder.cs @@ -1,9 +1,8 @@ using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; -using ILogger = Microsoft.Extensions.Logging.ILogger; -namespace Backend.Fx.Patterns.DependencyInjection +namespace Backend.Fx.Util { /// /// Holds a current instance of T that might be replaced during the scope @@ -19,6 +18,7 @@ public interface ICurrentTHolder where T : class T ProvideInstance(); } + [PublicAPI] public abstract class CurrentTHolder : ICurrentTHolder where T : class { private static readonly ILogger Logger = Log.Create>(); diff --git a/src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs b/src/abstractions/Backend.Fx/Util/DateTimeEx.cs similarity index 95% rename from src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs rename to src/abstractions/Backend.Fx/Util/DateTimeEx.cs index 0d7ad38d..760f7c64 100644 --- a/src/abstractions/Backend.Fx/Extensions/DateTimeEx.cs +++ b/src/abstractions/Backend.Fx/Util/DateTimeEx.cs @@ -1,7 +1,7 @@ -namespace Backend.Fx.Extensions -{ - using System; +using System; +namespace Backend.Fx.Util +{ public static class DateTimeEx { /// diff --git a/src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs b/src/abstractions/Backend.Fx/Util/DelegateDisposable.cs similarity index 92% rename from src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs rename to src/abstractions/Backend.Fx/Util/DelegateDisposable.cs index 562b56da..83d10c1c 100644 --- a/src/abstractions/Backend.Fx/Extensions/DelegateDisposable.cs +++ b/src/abstractions/Backend.Fx/Util/DelegateDisposable.cs @@ -1,7 +1,7 @@ using System; using JetBrains.Annotations; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { public class DelegateDisposable : IDisposable { diff --git a/src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs b/src/abstractions/Backend.Fx/Util/EnumerableEx.cs similarity index 74% rename from src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs rename to src/abstractions/Backend.Fx/Util/EnumerableEx.cs index 97dfb7c6..4febeb26 100644 --- a/src/abstractions/Backend.Fx/Extensions/EnumerableEx.cs +++ b/src/abstractions/Backend.Fx/Util/EnumerableEx.cs @@ -1,8 +1,8 @@ -namespace Backend.Fx.Extensions -{ - using System; - using System.Collections.Generic; +using System; +using System.Collections.Generic; +namespace Backend.Fx.Util +{ public static class EnumerableEx { public static void ForAll(this IEnumerable enumerable, Action action) diff --git a/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs b/src/abstractions/Backend.Fx/Util/MultipleDisposable.cs similarity index 93% rename from src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs rename to src/abstractions/Backend.Fx/Util/MultipleDisposable.cs index c51bfc5d..493b9d55 100644 --- a/src/abstractions/Backend.Fx/Extensions/MultipleDisposable.cs +++ b/src/abstractions/Backend.Fx/Util/MultipleDisposable.cs @@ -1,7 +1,7 @@ using System; using JetBrains.Annotations; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { [PublicAPI] public class MultipleDisposable : IDisposable diff --git a/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs b/src/abstractions/Backend.Fx/Util/ReaderWriterLockSlimExtensions.cs similarity index 97% rename from src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs rename to src/abstractions/Backend.Fx/Util/ReaderWriterLockSlimExtensions.cs index 02100115..b1a5ce33 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReaderWriterLockSlimExtensions.cs +++ b/src/abstractions/Backend.Fx/Util/ReaderWriterLockSlimExtensions.cs @@ -2,7 +2,7 @@ using System.Threading; using JetBrains.Annotations; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { [PublicAPI] public static class ReaderWriterLockSlimExtensions diff --git a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs b/src/abstractions/Backend.Fx/Util/ReflectionEx.cs similarity index 98% rename from src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs rename to src/abstractions/Backend.Fx/Util/ReflectionEx.cs index b3d90984..4afa1eed 100644 --- a/src/abstractions/Backend.Fx/Extensions/ReflectionEx.cs +++ b/src/abstractions/Backend.Fx/Util/ReflectionEx.cs @@ -4,7 +4,7 @@ using System.Reflection; using JetBrains.Annotations; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { [PublicAPI] public static class ReflectionEx diff --git a/src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs b/src/abstractions/Backend.Fx/Util/StringEnumUtil.cs similarity index 95% rename from src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs rename to src/abstractions/Backend.Fx/Util/StringEnumUtil.cs index de594405..c01e4def 100644 --- a/src/abstractions/Backend.Fx/Extensions/StringEnumUtil.cs +++ b/src/abstractions/Backend.Fx/Util/StringEnumUtil.cs @@ -1,7 +1,7 @@ using System; using System.Linq; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { public static class StringEnumUtil { diff --git a/src/abstractions/Backend.Fx/Extensions/StringEx.cs b/src/abstractions/Backend.Fx/Util/StringEx.cs similarity index 95% rename from src/abstractions/Backend.Fx/Extensions/StringEx.cs rename to src/abstractions/Backend.Fx/Util/StringEx.cs index 18d7f27a..52517ba0 100644 --- a/src/abstractions/Backend.Fx/Extensions/StringEx.cs +++ b/src/abstractions/Backend.Fx/Util/StringEx.cs @@ -1,7 +1,7 @@ using System.Text.RegularExpressions; using JetBrains.Annotations; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { [PublicAPI] public static class StringEx diff --git a/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs b/src/abstractions/Backend.Fx/Util/TolerantDateTimeComparer.cs similarity index 67% rename from src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs rename to src/abstractions/Backend.Fx/Util/TolerantDateTimeComparer.cs index 65910aeb..e0085d46 100644 --- a/src/abstractions/Backend.Fx/Extensions/TolerantDateTimeComparer.cs +++ b/src/abstractions/Backend.Fx/Util/TolerantDateTimeComparer.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using NodaTime; -namespace Backend.Fx.Extensions +namespace Backend.Fx.Util { [PublicAPI] public class TolerantDateTimeOffsetComparer : IEqualityComparer @@ -53,4 +54,29 @@ public int GetHashCode(DateTime? obj) return obj?.GetHashCode() ?? 0; } } + + [PublicAPI] + public class TolerantInstantComparer : IEqualityComparer + { + private readonly Duration _epsilon; + + public TolerantInstantComparer(Duration epsilon) + { + _epsilon = epsilon; + } + + public bool Equals(Instant? x, Instant? y) + { + if (x == null && y == null) return true; + + if (x == null || y == null) return false; + + return Math.Abs((x.Value - y.Value).TotalMilliseconds) < _epsilon.TotalMilliseconds; + } + + public int GetHashCode(Instant? obj) + { + return obj?.GetHashCode() ?? 0; + } + } } \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs index 9a93bd61..3b285e68 100644 --- a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs +++ b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationHostedService.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs index 9a629d69..e2300af7 100644 --- a/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs +++ b/src/environments/Backend.Fx.AspNetCore/BackendFxApplicationStartup.cs @@ -2,7 +2,6 @@ using Backend.Fx.AspNetCore.MultiTenancy; using Backend.Fx.AspNetCore.Mvc; using Backend.Fx.AspNetCore.Mvc.Activators; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; diff --git a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs index e7656117..b4e22069 100644 --- a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs +++ b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreApplication.cs @@ -1,8 +1,8 @@ -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Extensions; namespace Backend.Fx.AspNetCore.Bootstrapping { - public class AspNetCoreApplication : BackendFxApplicationDecorator + public class AspNetCoreApplication : BackendFxApplicationExtension { public AspNetCoreApplication(IBackendFxApplication application) : base(application) { diff --git a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs index b59ad1e1..ff233944 100644 --- a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs +++ b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/AspNetCoreModule.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Reflection; -using Backend.Fx.Extensions; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Util; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; diff --git a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs index fdc4659f..f512d155 100644 --- a/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/Bootstrapping/WaitForBootMiddleware.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; diff --git a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs index 529953d2..24eeedf2 100644 --- a/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs +++ b/src/environments/Backend.Fx.AspNetCore/Mvc/Activators/BackendFxApplicationHubActivator.cs @@ -1,6 +1,5 @@ using System; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/Backend.Fx.Features.MultiTenancy.Admin.csproj b/src/features/Backend.Fx.Features.MultiTenancy.Admin/Backend.Fx.Features.MultiTenancy.Admin.csproj new file mode 100644 index 00000000..e811dd89 --- /dev/null +++ b/src/features/Backend.Fx.Features.MultiTenancy.Admin/Backend.Fx.Features.MultiTenancy.Admin.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + Backend.Fx.Features.TenantsAdmin + + + + + + + diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs b/src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs new file mode 100644 index 00000000..516b5c9b --- /dev/null +++ b/src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs @@ -0,0 +1,13 @@ +namespace Backend.Fx.Features.TenantsAdmin +{ + public interface ITenantRepository + { + void SaveTenant(Tenant tenant); + + Tenant[] GetTenants(); + + Tenant GetTenant(int tenantId); + + void DeleteTenant(int tenantId); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs b/src/features/Backend.Fx.Features.MultiTenancy.Admin/Tenant.cs similarity index 77% rename from src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs rename to src/features/Backend.Fx.Features.MultiTenancy.Admin/Tenant.cs index b226562f..9f240cff 100644 --- a/src/abstractions/Backend.Fx/Environment/MultiTenancy/Tenant.cs +++ b/src/features/Backend.Fx.Features.MultiTenancy.Admin/Tenant.cs @@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; -namespace Backend.Fx.Environment.MultiTenancy +namespace Backend.Fx.Features.TenantsAdmin { /// /// Represents a tenant in the application @@ -21,7 +21,7 @@ public Tenant([NotNull] string name, string description, bool isDemoTenant, stri Description = description; IsDemoTenant = isDemoTenant; Configuration = configuration; - State = TenantState.Active; + IsActive = true; } [Key] public int Id { get; set; } @@ -32,22 +32,11 @@ public Tenant([NotNull] string name, string description, bool isDemoTenant, stri public bool IsDemoTenant { get; set; } - public TenantState State { get; set; } + public bool IsActive { get; set; } /// /// optional: a generic field to store your arbitrary config data /// public string Configuration { get; set; } - - public TenantId GetTenantId() - { - return new TenantId(Id); - } - } - - public enum TenantState - { - Active = 2, - Inactive = -1 } } \ No newline at end of file diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs b/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs new file mode 100644 index 00000000..c88edfca --- /dev/null +++ b/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs @@ -0,0 +1,144 @@ +using System; +using System.Linq; +using Backend.Fx.Exceptions; +using Backend.Fx.Logging; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Features.TenantsAdmin +{ + /// + /// Encapsulates the management of tenants + /// + [PublicAPI] + public interface ITenantService + { + Tenant CreateTenant(string name, string description, bool isDemonstrationTenant, string configuration = null); + void ActivateTenant(int tenantId); + void DeactivateTenant(int tenantId); + void DeleteTenant(int tenantId); + Tenant UpdateTenant(int tenantId, string name, string description, string configuration); + + Tenant[] GetTenants(); + Tenant[] GetActiveTenants(); + Tenant[] GetActiveDemonstrationTenants(); + Tenant[] GetActiveProductionTenants(); + Tenant GetTenant(int tenantId); + } + + public class TenantService : ITenantService + { + private static readonly ILogger Logger = Log.Create(); + private readonly ITenantRepository _tenantRepository; + + public TenantService(ITenantRepository tenantRepository) + { + _tenantRepository = tenantRepository; + } + + public Tenant CreateTenant(string name, string description, bool isDemonstrationTenant, + string configuration = null) + { + Logger.LogInformation("Creating tenant: {Name}", name); + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + } + + if (_tenantRepository.GetTenants() + .Any(t => t.Name != null && t.Name.ToLowerInvariant() == name.ToLowerInvariant())) + { + throw new ArgumentException($"There is already a tenant named {name}"); + } + + var tenant = new Tenant(name, description, isDemonstrationTenant) { Configuration = configuration }; + _tenantRepository.SaveTenant(tenant); + + return tenant; + } + + public void ActivateTenant(int tenantId) + { + Logger.LogInformation("Activating tenant: {TenantId}", tenantId); + Tenant tenant = _tenantRepository.GetTenant(tenantId); + tenant.IsActive = true; + _tenantRepository.SaveTenant(tenant); + } + + public void DeactivateTenant(int tenantId) + { + Logger.LogInformation("Deactivating tenant: {TenantId}", tenantId); + Tenant tenant = _tenantRepository.GetTenant(tenantId); + tenant.IsActive = false; + _tenantRepository.SaveTenant(tenant); + } + + public void DeleteTenant(int tenantId) + { + Logger.LogInformation("Deleting tenant: {TenantId}", tenantId); + Tenant tenant = _tenantRepository.GetTenant(tenantId); + if (tenant.IsActive) + { + throw new UnprocessableException($"Attempt to delete active tenant[{tenantId}]") + .AddError("You cannot delete an active tenant. Please make sure to deactivate it first."); + } + + _tenantRepository.DeleteTenant(tenantId); + } + + public Tenant GetTenant(int tenantId) + { + return _tenantRepository.GetTenant(tenantId); + } + + public Tenant UpdateTenant(int tenantId, string name, string description, string configuration) + { + var tenant = _tenantRepository.GetTenant(tenantId); + tenant.Name = name; + tenant.Description = description; + tenant.Configuration = configuration; + _tenantRepository.SaveTenant(tenant); + return tenant; + } + + public Tenant[] GetTenants() + { + var tenants = _tenantRepository.GetTenants(); + Logger.LogTrace("TenantIds: {TenantIds}", string.Join(",", tenants.Select(t => t.ToString()))); + return tenants; + } + + public Tenant[] GetActiveTenants() + { + var activeTenants = _tenantRepository + .GetTenants() + .Where(t => t.IsActive) + .ToArray(); + Logger.LogTrace("Active TenantIds: {TenantIds}", string.Join(",", activeTenants.Select(t => t.ToString()))); + return activeTenants; + } + + public Tenant[] GetActiveDemonstrationTenants() + { + var activeDemonstrationTenants = _tenantRepository + .GetTenants() + .Where(t => t.IsActive && t.IsDemoTenant) + .ToArray(); + Logger.LogTrace("Active Demonstration TenantIds: {TenantIds}", + string.Join(",", activeDemonstrationTenants.Select(t => t.ToString()))); + return activeDemonstrationTenants; + } + + public Tenant[] GetActiveProductionTenants() + { + var activeProductionTenants = _tenantRepository + .GetTenants() + .Where(t => t.IsActive && !t.IsDemoTenant) + .ToArray(); + Logger.LogTrace("Active Production TenantIds: {TenantIds}", + string.Join(",", activeProductionTenants.Select(t => t.ToString()))); + return activeProductionTenants; + } + } +} \ No newline at end of file diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantsAdminApplication.cs b/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantsAdminApplication.cs new file mode 100644 index 00000000..9f297a5a --- /dev/null +++ b/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantsAdminApplication.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions; +using Backend.Fx.Extensions.DataGeneration; +using Backend.Fx.Features.MultiTenancy; +using Backend.Fx.Util; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.TenantsAdmin +{ + [PublicAPI] + public class TenantsAdminApplication : BackendFxApplicationExtension + { + private readonly IBackendFxApplication _backendFxApplication; + private readonly ITenantWideMutexManager _tenantWideMutexManager; + + public TenantsAdminApplication( + IBackendFxApplication backendFxApplication, + ITenantRepository tenantRepository, + ITenantWideMutexManager tenantWideMutexManager) : base(backendFxApplication) + { + _backendFxApplication = backendFxApplication; + _tenantWideMutexManager = tenantWideMutexManager; + TenantsService = new TenantService(tenantRepository); + } + + public TenantService TenantsService { get; } + + + public virtual async Task BootAsync() + { + var tenants = TenantsService.GetActiveTenants(); + await RunDataGenerators(tenants); + } + + private async Task RunDataGenerators(IEnumerable tenants) + { + var dataGeneratorTypes = Type.EmptyTypes; + await _backendFxApplication.Invoker.InvokeAsync(sp => + { + dataGeneratorTypes = sp + .GetServices() + .OrderBy(dg => dg.Priority) + .Select(dg => dg.GetType()) + .ToArray(); + return Task.CompletedTask; + }, new SystemIdentity()); + + foreach (var tenant in tenants) + { + var tenantId = new TenantId(tenant.Id); + + if (_tenantWideMutexManager.TryAcquire(tenantId, "DataGeneration", out var mutex)) + { + try + { + foreach (var dataGeneratorType in dataGeneratorTypes) + { + if (typeof(IProductiveDataGenerator).IsAssignableFrom(dataGeneratorType) + || typeof(IDemoDataGenerator).IsAssignableFrom(dataGeneratorType) && + tenant.IsDemoTenant) + { + await _backendFxApplication.Invoker.InvokeAsync(async sp => + { + sp.GetRequiredService>().ReplaceCurrent(tenantId); + var dataGenerator = (IDataGenerator)sp.GetRequiredService(dataGeneratorType); + await dataGenerator.GenerateAsync(); + }, new SystemIdentity()); + } + } + } + finally + { + mutex.Dispose(); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs index 00a725c3..bc6bc301 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/AggregateMapping.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs index 34a480c5..ea95024e 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs @@ -1,7 +1,7 @@ using System.Data; using System.Data.Common; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence.Bootstrapping diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs index ecf1f38b..10654592 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -3,12 +3,13 @@ using System.Data; using System.Linq; using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.ConfigurationSettings; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Domain; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.ConfigurationSettings; using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -103,7 +104,7 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( ServiceDescriptor.Scoped, PlainAggregateMapping>()); compositionRoot.Register( - ServiceDescriptor.Scoped, AllowAll>()); + ServiceDescriptor.Scoped, AllowAll>()); } // loop through aggregate root types to... diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs index 7d4fb82c..8ca408b2 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/DbContextExtensions.cs @@ -1,9 +1,8 @@ using System; using System.Linq; using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Extensions; using Backend.Fx.Logging; +using Backend.Fx.Util; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs index 5729d037..738165b8 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfFlush.cs @@ -3,17 +3,16 @@ using System.Linq; using System.Reflection; using System.Security.Principal; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Exceptions; -using Backend.Fx.Extensions; -using Backend.Fx.Features.Persistence; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.Logging; +using NodaTime; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore5Persistence @@ -54,7 +53,7 @@ private void UpdateTrackingProperties() { using (Logger.LogDebugDuration("Updating tracking properties of created and modified entities")) { - UpdateTrackingProperties(IdentityHolder.Current.Name, Clock.UtcNow); + UpdateTrackingProperties(IdentityHolder.Current.Name, Clock.GetCurrentInstant()); } } @@ -93,7 +92,7 @@ private void SaveChanges() } } - private void UpdateTrackingProperties(string identity, DateTime utcNow) + private void UpdateTrackingProperties(string identity, Instant instant) { identity ??= "anonymous"; var isTraceEnabled = Logger.IsEnabled(LogLevel.Trace); @@ -123,13 +122,13 @@ private void UpdateTrackingProperties(string identity, DateTime utcNow) if (entry.State == EntityState.Added) { - if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was created by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, utcNow); - entity.SetCreatedProperties(identity, utcNow); + if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was created by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, instant); + entity.SetCreatedProperties(identity, instant); } else if (entry.State == EntityState.Modified) { - if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was modified by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, utcNow); - entity.SetModifiedProperties(identity, utcNow); + if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was modified by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, instant); + entity.SetModifiedProperties(identity, instant); // this line causes the recent changes of tracking properties to be detected before flushing entry.State = EntityState.Modified; @@ -141,7 +140,7 @@ private void UpdateTrackingProperties(string identity, DateTime utcNow) throw; } }); - if (count > 0) Logger.LogDebug("Tracked {EntityCount} entities as created/changed on {UtcNow} by {Identity}", count, utcNow, identity); + if (count > 0) Logger.LogDebug("Tracked {EntityCount} entities as created/changed on {UtcNow} by {Identity}", count, instant, identity); } /// diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs index 7a95f2d0..be220127 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EfRepository.cs @@ -4,12 +4,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -23,18 +23,18 @@ namespace Backend.Fx.EfCore5Persistence public class EfRepository : Repository, IAsyncRepository where TAggregateRoot : AggregateRoot { private static readonly ILogger Logger = Log.Create>(); - private readonly IAggregateAuthorization _aggregateAuthorization; + private readonly IAuthorizationPolicy _authorizationPolicy; private readonly IAggregateMapping _aggregateMapping; private DbContext _dbContext; [SuppressMessage("ReSharper", "EF1001")] public EfRepository(DbContext dbContext, IAggregateMapping aggregateMapping, - ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) - : base(currentTenantIdHolder, aggregateAuthorization) + ICurrentTHolder currentTenantIdHolder, IAuthorizationPolicy authorizationPolicy) + : base(currentTenantIdHolder, authorizationPolicy) { _dbContext = dbContext; _aggregateMapping = aggregateMapping; - _aggregateAuthorization = aggregateAuthorization; + _authorizationPolicy = authorizationPolicy; // somewhat a hack: using the internal EF Core services against advice var localViewListener = dbContext.GetService(); @@ -109,7 +109,7 @@ private void AuthorizeChanges(InternalEntityEntry entry, EntityState previousSta if (previousState == EntityState.Unchanged && entry.EntityState == EntityState.Modified && entry.EntityType.ClrType == typeof(TAggregateRoot)) { var aggregateRoot = (TAggregateRoot) entry.Entity; - if (!_aggregateAuthorization.CanModify(aggregateRoot)) throw new ForbiddenException("Unauthorized attempt to modify {AggregateTypeName}[{aggregateRoot.Id}]") + if (!_authorizationPolicy.CanModify(aggregateRoot)) throw new ForbiddenException("Unauthorized attempt to modify {AggregateTypeName}[{aggregateRoot.Id}]") .AddError($"You are not allowed to modify {AggregateTypeName}[{aggregateRoot.Id}]"); } } diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs b/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs index 87e90311..b99ce87a 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/EntityQueryable.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs index 41dcefe7..4ce81b1b 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/IAggregateMapping.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs index eb87bd96..43d2c2ad 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Mssql/MsSqlSequence.cs @@ -1,7 +1,7 @@ using System; using System.Data; using Backend.Fx.EfCore5Persistence.Bootstrapping; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs index 023fa1aa..f01945be 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Oracle/OracleSequence.cs @@ -1,7 +1,7 @@ using System; using System.Data; using Backend.Fx.EfCore5Persistence.Bootstrapping; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs index 5e07ff6d..acb0452f 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/PlainAggregateMapping.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs index 2565ab57..8666e3a3 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore5Persistence/Postgres/PostgresSequence.cs @@ -1,7 +1,7 @@ using System; using System.Data; using Backend.Fx.EfCore5Persistence.Bootstrapping; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs index 46d130ff..7a771e23 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/AggregateMapping.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs index 63f05dc1..2460960c 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/DbContextTransactionOperationDecorator.cs @@ -1,7 +1,7 @@ using System.Data; using System.Data.Common; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence.Bootstrapping diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs index a66dd85c..a67c8ee2 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Bootstrapping/EfCorePersistenceModule.cs @@ -3,12 +3,13 @@ using System.Data; using System.Linq; using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.ConfigurationSettings; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Domain; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Features.Authorization; +using Backend.Fx.Features.ConfigurationSettings; using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -103,7 +104,7 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( ServiceDescriptor.Scoped, PlainAggregateMapping>()); compositionRoot.Register( - ServiceDescriptor.Scoped, AllowAll>()); + ServiceDescriptor.Scoped, AllowAll>()); } // loop through aggregate root types to... diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs b/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs index 0f4b0055..c983b5d0 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/DbContextExtensions.cs @@ -1,9 +1,8 @@ using System; using System.Linq; using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Extensions; using Backend.Fx.Logging; +using Backend.Fx.Util; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs index e372b0cc..aadee6be 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfFlush.cs @@ -3,17 +3,16 @@ using System.Linq; using System.Reflection; using System.Security.Principal; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Exceptions; -using Backend.Fx.Extensions; -using Backend.Fx.Features.Persistence; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.Logging; +using NodaTime; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.EfCore6Persistence @@ -54,7 +53,7 @@ private void UpdateTrackingProperties() { using (Logger.LogDebugDuration("Updating tracking properties of created and modified entities")) { - UpdateTrackingProperties(IdentityHolder.Current.Name, Clock.UtcNow); + UpdateTrackingProperties(IdentityHolder.Current.Name, Clock.GetCurrentInstant()); } } @@ -93,7 +92,7 @@ private void SaveChanges() } } - private void UpdateTrackingProperties(string identity, DateTime utcNow) + private void UpdateTrackingProperties(string identity, Instant instant) { identity ??= "anonymous"; var isTraceEnabled = Logger.IsEnabled(LogLevel.Trace); @@ -123,13 +122,13 @@ private void UpdateTrackingProperties(string identity, DateTime utcNow) if (entry.State == EntityState.Added) { - if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was created by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, utcNow); - entity.SetCreatedProperties(identity, utcNow); + if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was created by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, instant); + entity.SetCreatedProperties(identity, instant); } else if (entry.State == EntityState.Modified) { - if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was modified by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, utcNow); - entity.SetModifiedProperties(identity, utcNow); + if (isTraceEnabled) Logger.LogTrace("tracking that {EntityTypeName}[{Id}] was modified by {Identity} at {UtcNow}", entity.GetType().Name, entity.Id, identity, instant); + entity.SetModifiedProperties(identity, instant); // this line causes the recent changes of tracking properties to be detected before flushing entry.State = EntityState.Modified; @@ -141,7 +140,7 @@ private void UpdateTrackingProperties(string identity, DateTime utcNow) throw; } }); - if (count > 0) Logger.LogDebug("Tracked {EntityCount} entities as created/changed on {UtcNow} by {Identity}", count, utcNow, identity); + if (count > 0) Logger.LogDebug("Tracked {EntityCount} entities as created/changed on {UtcNow} by {Identity}", count, instant, identity); } /// diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs index cf42b9ec..6d599df5 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EfRepository.cs @@ -4,12 +4,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; @@ -22,18 +22,18 @@ namespace Backend.Fx.EfCore6Persistence public class EfRepository : Repository, IAsyncRepository where TAggregateRoot : AggregateRoot { private static readonly ILogger Logger = Log.Create>(); - private readonly IAggregateAuthorization _aggregateAuthorization; + private readonly IAuthorizationPolicy _authorizationPolicy; private readonly IAggregateMapping _aggregateMapping; private DbContext _dbContext; [SuppressMessage("ReSharper", "EF1001")] public EfRepository([CanBeNull] DbContext dbContext, IAggregateMapping aggregateMapping, - ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) - : base(currentTenantIdHolder, aggregateAuthorization) + ICurrentTHolder currentTenantIdHolder, IAuthorizationPolicy authorizationPolicy) + : base(currentTenantIdHolder, authorizationPolicy) { _dbContext = dbContext; _aggregateMapping = aggregateMapping; - _aggregateAuthorization = aggregateAuthorization; + _authorizationPolicy = authorizationPolicy; // somewhat a hack: using the internal EF Core services against advice var localViewListener = dbContext?.GetService(); @@ -119,7 +119,7 @@ private void AuthorizeChanges(InternalEntityEntry entry, EntityState previousSta if (previousState == EntityState.Unchanged && entry.EntityState == EntityState.Modified && entry.EntityType.ClrType == typeof(TAggregateRoot)) { var aggregateRoot = (TAggregateRoot) entry.Entity; - if (!_aggregateAuthorization.CanModify(aggregateRoot)) throw new ForbiddenException("Unauthorized attempt to modify {AggregateTypeName}[{aggregateRoot.Id}]") + if (!_authorizationPolicy.CanModify(aggregateRoot)) throw new ForbiddenException("Unauthorized attempt to modify {AggregateTypeName}[{aggregateRoot.Id}]") .AddError($"You are not allowed to modify {AggregateTypeName}[{aggregateRoot.Id}]"); } } diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs b/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs index c5d60b9b..b559283b 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/EntityQueryable.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs index 905220fe..66987c45 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/IAggregateMapping.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs index 789d79e7..f49588f4 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Mssql/MsSqlSequence.cs @@ -1,7 +1,7 @@ using System; using System.Data; using Backend.Fx.EfCore6Persistence.Bootstrapping; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs index a36115d9..ff471842 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Oracle/OracleSequence.cs @@ -1,7 +1,7 @@ using System; using System.Data; using Backend.Fx.EfCore6Persistence.Bootstrapping; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs b/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs index 3f198947..8c9c8c57 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/PlainAggregateMapping.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence diff --git a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs index a62350a0..a4e4384d 100644 --- a/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs +++ b/src/implementations/Backend.Fx.EfCore6Persistence/Postgres/PostgresSequence.cs @@ -1,7 +1,7 @@ using System; using System.Data; using Backend.Fx.EfCore6Persistence.Bootstrapping; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs index 77b6d80d..ea486c26 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryEntityIdGenerator.cs @@ -1,4 +1,5 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; +using Backend.Fx.Extensions.Persistence; using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs index 36bd4daa..16941d1b 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryFlush.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; namespace Backend.Fx.InMemoryPersistence { diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs index 1df0b0c5..d4ac62e9 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryPersistenceModule.cs @@ -1,11 +1,11 @@ using System; using System.Linq; using System.Reflection; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.ConfigurationSettings; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Domain; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Features.Authorization; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Features.ConfigurationSettings; using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.InMemoryPersistence @@ -34,7 +34,7 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( ServiceDescriptor.Scoped, InMemoryRepository>()); compositionRoot.Register( - ServiceDescriptor.Scoped, AllowAll>()); + ServiceDescriptor.Scoped, AllowAll>()); compositionRoot.Register( ServiceDescriptor.Singleton, InMemoryStore>()); } diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs index 9435fbcc..42abfd06 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryQueryable.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs index a53587ec..c3c243df 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryRepository.cs @@ -1,10 +1,9 @@ using System.Linq; -using Backend.Fx.BuildingBlocks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Authorization; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.RandomData; +using Backend.Fx.Util; using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence @@ -12,8 +11,8 @@ namespace Backend.Fx.InMemoryPersistence [PublicAPI] public class InMemoryRepository : Repository where T : AggregateRoot { - public InMemoryRepository(IInMemoryStore store, ICurrentTHolder currentTenantIdHolder, IAggregateAuthorization aggregateAuthorization) - : base(currentTenantIdHolder, aggregateAuthorization) + public InMemoryRepository(IInMemoryStore store, ICurrentTHolder currentTenantIdHolder, IAuthorizationPolicy authorizationPolicy) + : base(currentTenantIdHolder, authorizationPolicy) { Store = store; } diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs index 16fe65fc..11c28e12 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemorySequence.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; namespace Backend.Fx.InMemoryPersistence { diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs index 67a5d975..a35a9006 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryStore.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; namespace Backend.Fx.InMemoryPersistence { diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs index 8ef21b5c..c48de878 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryView.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; namespace Backend.Fx.InMemoryPersistence diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs index 625cb31e..3b27d75d 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Backend.Fx.DependencyInjection; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -33,6 +33,11 @@ public MicrosoftCompositionRoot() public override IServiceProvider ServiceProvider => _serviceProvider.Value; + public override bool HasRegistration() + { + return ServiceCollection.Any(sd => sd.ServiceType is T); + } + public override void Verify() { // ensure creation of lazy service provider, this will trigger the validation @@ -51,19 +56,19 @@ public override void Register(ServiceDescriptor serviceDescriptor) if (existingRegistration == null) { - serviceDescriptor.LogDetails(Logger, "Adding"); + LogAddRegistration(serviceDescriptor); ServiceCollection.Add(serviceDescriptor); } else { - serviceDescriptor.LogDetails(Logger, "Replacing"); + LogReplaceRegistration(serviceDescriptor); ServiceCollection.Replace(serviceDescriptor); } } public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) { - serviceDescriptor.LogDetails(Logger, "Adding decorator"); + LogAddDecoratorRegistration(serviceDescriptor); ServiceCollection.Decorate(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType); } @@ -73,13 +78,13 @@ public override void RegisterCollection(IEnumerable serviceDe if (serviceDescriptorArray.Length == 0) { - Logger.Warn("Skipping registration of empty collection"); + Logger.LogWarning("Skipping registration of empty collection"); return; } foreach (var serviceDescriptor in serviceDescriptorArray) { - serviceDescriptor.LogDetails(Logger, "Adding"); + LogAddRegistration(serviceDescriptor); ServiceCollection.Add(serviceDescriptor); } } diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs index 8a53318a..33c7e563 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftServiceProviderModule.cs @@ -1,5 +1,5 @@ +using Backend.Fx.DependencyInjection; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj b/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj index 393427ce..2c9990cf 100644 --- a/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj +++ b/src/implementations/Backend.Fx.RabbitMq/Backend.Fx.RabbitMq.csproj @@ -24,7 +24,6 @@ - diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs b/src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs similarity index 78% rename from src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs rename to src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs index c280e6e4..107d1557 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMQChannel.cs +++ b/src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs @@ -1,16 +1,13 @@ using System; using System.Collections.Generic; using System.Net.Sockets; -using System.Text; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Polly; using RabbitMQ.Client; using RabbitMQ.Client.Events; using RabbitMQ.Client.Exceptions; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.RabbitMq { @@ -18,7 +15,6 @@ public class RabbitMqChannel : IDisposable { private static readonly ILogger Logger = Log.Create(); private readonly string _exchangeName; - private readonly IMessageNameProvider _messageNameProvider; private readonly IConnectionFactory _connectionFactory; private readonly string _queueName; private readonly int _retryCount; @@ -30,13 +26,11 @@ public class RabbitMqChannel : IDisposable private bool _isDisposed; private IModel _channel; - public RabbitMqChannel(IMessageNameProvider messageNameProvider, - IConnectionFactory connectionFactory, - string exchangeName, - string queueName, - int retryCount) + public RabbitMqChannel(IConnectionFactory connectionFactory, + string exchangeName, + string queueName, + int retryCount) { - _messageNameProvider = messageNameProvider; _connectionFactory = connectionFactory; _exchangeName = exchangeName; _queueName = queueName; @@ -59,7 +53,7 @@ public void Dispose() } } - public void EnsureClosed() + private void EnsureClosed() { if (_consumer != null) { @@ -108,12 +102,12 @@ private bool Open() Logger.LogInformation("RabbitMQ Client is trying to connect"); Policy.Handle() - .Or() - .WaitAndRetry(_retryCount, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (ex, time) => { Logger.LogWarning(ex, "Connection not ready"); } - ) - .Execute(() => { _connection = _connectionFactory.CreateConnection(); }); + .Or() + .WaitAndRetry(_retryCount, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => { Logger.LogWarning(ex, "Connection not ready"); } + ) + .Execute(() => { _connection = _connectionFactory.CreateConnection(); }); if (_connection?.IsOpen == true) { @@ -121,7 +115,8 @@ private bool Open() _connection.CallbackException += OnCallbackException; _connection.ConnectionBlocked += OnConnectionBlocked; - Logger.LogInformation("Acquired a connection to RabbitMQ host {HostName} and is subscribed to failure events", + Logger.LogInformation( + "Acquired a connection to RabbitMQ host {HostName} and is subscribed to failure events", _connection.Endpoint.HostName); _channel = _connection.CreateModel(); @@ -150,13 +145,14 @@ private bool Open() } } - public void PublishEvent(IIntegrationEvent integrationEvent) + public void PublishMessage(SerializedMessage serializedMessage) { - var messageName = _messageNameProvider.GetMessageName(integrationEvent); - var message = JsonConvert.SerializeObject(integrationEvent); - var body = Encoding.UTF8.GetBytes(message); - - DoResilient(() => _channel.BasicPublish(_exchangeName, messageName, null, body)); + DoResilient( + () => _channel.BasicPublish( + _exchangeName, + serializedMessage.EventTypeName, + null, + serializedMessage.MessagePayload)); } public void Subscribe(string messageName) @@ -217,11 +213,11 @@ private void OnMessageReceived(object sender, BasicDeliverEventArgs basicDeliver private void DoResilient(Action action) { Policy.Handle() - .Or() - .WaitAndRetry(_retryCount, - retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), - (ex, time) => { Logger.LogWarning(ex, "Connection not ready"); }) - .Execute(action); + .Or() + .WaitAndRetry(_retryCount, + retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), + (ex, time) => { Logger.LogWarning(ex, "Connection not ready"); }) + .Execute(action); } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs b/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs index c8fac43d..15659198 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs +++ b/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs @@ -1,15 +1,9 @@ -using System; -using System.Text; -using System.Threading.Tasks; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.MessageBus; +using System.Threading.Tasks; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using RabbitMQ.Client; using RabbitMQ.Client.Events; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Backend.Fx.RabbitMq { @@ -18,9 +12,13 @@ public class RabbitMqMessageBus : MessageBus private static readonly ILogger Logger = Log.Create(); private readonly RabbitMqChannel _channel; - public RabbitMqMessageBus(IConnectionFactory connectionFactory, int retryCount, string exchangeName, string receiveQueueName) + public RabbitMqMessageBus( + IConnectionFactory connectionFactory, + int retryCount, + string exchangeName, + string receiveQueueName) { - _channel = new RabbitMqChannel(MessageNameProvider, connectionFactory, exchangeName, receiveQueueName, retryCount); + _channel = new RabbitMqChannel(connectionFactory, exchangeName, receiveQueueName, retryCount); } public override void Connect() @@ -32,13 +30,27 @@ public override void Connect() Logger.LogInformation("Channel to RabbitMQ opened"); } } + + protected override void SubscribeToEventMessage(string eventTypeName) + { + Logger.LogInformation("Subscribing to messages of {EventTypeName}", eventTypeName); + _channel.EnsureOpen(); + _channel.Subscribe(eventTypeName); + } + + protected override Task PublishMessageAsync(SerializedMessage serializedMessage) + { + _channel.EnsureOpen(); + _channel.PublishMessage(serializedMessage); + return Task.CompletedTask; + } - private void ChannelOnMessageReceived(object sender, BasicDeliverEventArgs args) + private async void ChannelOnMessageReceived(object sender, BasicDeliverEventArgs args) { Logger.LogDebug("RabbitMQ message with routing key {RoutingKey} received", args.RoutingKey); try { - Process(args.RoutingKey, new RabbitMqEventProcessingContext(args.Body)); + await ProcessAsync(new SerializedMessage(args.RoutingKey, args.Body)); _channel.Acknowledge(args.DeliveryTag); } catch @@ -48,27 +60,6 @@ private void ChannelOnMessageReceived(object sender, BasicDeliverEventArgs args) } } - protected override Task PublishOnMessageBus(IIntegrationEvent integrationEvent) - { - Logger.LogInformation("Publishing {MessageName}", MessageNameProvider.GetMessageName(integrationEvent)); - _channel.EnsureOpen(); - _channel.PublishEvent(integrationEvent); - return Task.CompletedTask; - } - - protected override void Subscribe(string messageName) - { - Logger.LogInformation("Subscribing to {MessageName}", messageName); - _channel.EnsureOpen(); - _channel.Subscribe(messageName); - } - - protected override void Unsubscribe(string messageName) - { - Logger.LogInformation("Unsubscribing from {MessageName}", messageName); - _channel.Unsubscribe(messageName); - } - protected override void Dispose(bool disposing) { if (disposing) @@ -82,31 +73,5 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - - private class RabbitMqEventProcessingContext : EventProcessingContext - { - private readonly string _jsonString; - - public RabbitMqEventProcessingContext(object rawReceivedMessage) - { - Logger.LogTrace("Deserializing a message of type {MessageType}", rawReceivedMessage?.GetType().Name ?? "???"); - if (!(rawReceivedMessage is byte[] rawEventPayloadBytes)) throw new InvalidOperationException("Raw event payload is not a binary JSON string"); - - _jsonString = Encoding.UTF8.GetString(rawEventPayloadBytes); - var eventStub = JsonConvert.DeserializeAnonymousType(_jsonString, new {tenantId = 0, correlationId = Guid.Empty}); - TenantId = new TenantId(eventStub.tenantId); - CorrelationId = eventStub.correlationId; - } - - public override TenantId TenantId { get; } - - public override dynamic DynamicEvent => JObject.Parse(_jsonString); - public override Guid CorrelationId { get; } - - public override IIntegrationEvent GetTypedEvent(Type eventType) - { - return (IIntegrationEvent) JsonConvert.DeserializeObject(_jsonString, eventType); - } - } } } \ No newline at end of file diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index c4dbd93a..ba5b4400 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Backend.Fx.DependencyInjection; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SimpleInjector; @@ -15,6 +16,7 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection /// /// Provides a reusable composition root assuming Simple Injector as container /// + [PublicAPI] public class SimpleInjectorCompositionRoot : CompositionRoot { private static readonly ILogger Logger = Log.Create(); @@ -86,7 +88,7 @@ public override void RegisterCollection(IEnumerable serviceDe if (serviceDescriptorArray.Length == 0) { - Logger.Warn("Skipping registration of empty collection"); + Logger.LogWarning("Skipping registration of empty collection"); return; } @@ -99,6 +101,11 @@ public override void RegisterCollection(IEnumerable serviceDe _serviceCollections.Add(serviceDescriptorArray); } + public override bool HasRegistration() + { + return _services.Any(sd => sd.ServiceType is T); + } + public override void Verify() { FillContainer(); @@ -131,21 +138,21 @@ private void FillContainer() Logger.LogInformation("Registering services with container"); foreach (var serviceDescriptor in _services) { - serviceDescriptor.LogDetails(Logger, "Adding"); + LogAddRegistration(serviceDescriptor); if (serviceDescriptor.ImplementationType != null) { Container.Register( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); + serviceDescriptor.Lifetime.MapLifestyle()); } else if (serviceDescriptor.ImplementationFactory != null) { Container.Register( serviceDescriptor.ServiceType, () => serviceDescriptor.ImplementationFactory(Container), - serviceDescriptor.Lifetime.Translate()); + serviceDescriptor.Lifetime.MapLifestyle()); } else if (serviceDescriptor.ImplementationInstance != null && serviceDescriptor.Lifetime == ServiceLifetime.Singleton) @@ -161,17 +168,17 @@ private void FillContainer() foreach (var serviceDescriptor in _decorators) { - serviceDescriptor.LogDetails(Logger, "Adding decorator"); + LogAddDecoratorRegistration(serviceDescriptor); Container.RegisterDecorator( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); + serviceDescriptor.Lifetime.MapLifestyle()); } foreach (var serviceDescriptors in _serviceCollections) { - Logger.Debug("Adding {Lifetime} collection registration: {ServiceType}: {ImplementationType}", + Logger.LogDebug("Adding {Lifetime} collection registration: {ServiceType}: {ImplementationType}", serviceDescriptors[0].Lifetime.ToString(), serviceDescriptors[0].ServiceType.Name, $"[{string.Join(",", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))}]"); @@ -181,7 +188,7 @@ private void FillContainer() Container.Collection.Append( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, - serviceDescriptor.Lifetime.Translate()); + serviceDescriptor.Lifetime.MapLifestyle()); } } } diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorMappings.cs similarity index 79% rename from src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs rename to src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorMappings.cs index f72e71bd..926efdd8 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/MicrosoftDependencyInjectionExtensions.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorMappings.cs @@ -4,9 +4,9 @@ namespace Backend.Fx.SimpleInjectorDependencyInjection { - public static class MicrosoftDependencyInjectionExtensions + public static class SimpleInjectorMappings { - public static Lifestyle Translate(this ServiceLifetime serviceLifetime) + public static Lifestyle MapLifestyle(this ServiceLifetime serviceLifetime) { switch (serviceLifetime) { diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs index 082c98fd..2db00e31 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorModule.cs @@ -1,5 +1,5 @@ +using Backend.Fx.DependencyInjection; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using SimpleInjector; diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs index 3e833eb7..d9bc6db7 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/Domain/ICalculationService.cs @@ -1,10 +1,10 @@ using System; using System.Security.Principal; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Exceptions; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; +using NodaTime; namespace Backend.Fx.AspNetCore.Tests.SampleApp.Domain { @@ -17,7 +17,7 @@ public interface ICalculationService public class CalculationResult { - public CalculationResult(DateTime timestamp, string executor, double result, int tenantId) + public CalculationResult(Instant timestamp, string executor, double result, int tenantId) { Timestamp = timestamp; Executor = executor; @@ -25,7 +25,7 @@ public CalculationResult(DateTime timestamp, string executor, double result, int TenantId = tenantId; } - public DateTime Timestamp { get; } + public Instant Timestamp { get; } public string Executor { get; } public double Result { get; } public int TenantId { get; } @@ -47,17 +47,17 @@ public CalculationService(IClock clock, ICurrentTHolder identityHolde public ICalculationService.CalculationResult Add(double arg1, double arg2) { - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 + arg2, _tenantIdHolder.Current.Value); + return new(_clock.GetCurrentInstant(), _identityHolder.Current.Name, arg1 + arg2, _tenantIdHolder.Current.Value); } public ICalculationService.CalculationResult Subtract(double arg1, double arg2) { - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 - arg2, _tenantIdHolder.Current.Value); + return new(_clock.GetCurrentInstant(), _identityHolder.Current.Name, arg1 - arg2, _tenantIdHolder.Current.Value); } public ICalculationService.CalculationResult Multiply(double arg1, double arg2) { - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 * arg2, _tenantIdHolder.Current.Value); + return new(_clock.GetCurrentInstant(), _identityHolder.Current.Name, arg1 * arg2, _tenantIdHolder.Current.Value); } public ICalculationService.CalculationResult Divide(double arg1, double arg2) @@ -69,7 +69,7 @@ public ICalculationService.CalculationResult Divide(double arg1, double arg2) } - return new(_clock.UtcNow, _identityHolder.Current.Name, arg1 / arg2, _tenantIdHolder.Current.Value); + return new(_clock.GetCurrentInstant(), _identityHolder.Current.Name, arg1 / arg2, _tenantIdHolder.Current.Value); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs index 227492f7..74e434cb 100644 --- a/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs +++ b/tests/Backend.Fx.AspNetCore.Tests/SampleApp/SampleApplicationHostedService.cs @@ -2,7 +2,6 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.SimpleInjectorDependencyInjection; namespace Backend.Fx.AspNetCore.Tests.SampleApp diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs index 2cd1b2e5..2cc5cac7 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -1,10 +1,10 @@ using System.Data; using System.Security.Principal; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Features.Persistence; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures { @@ -33,7 +33,7 @@ CurrentIdentityHolder CreateAsIdentity() return cih; } - clock ??= new WallClock(); + clock ??= SystemClock.Instance; operation ??= UseOperation(); operation.Begin(); diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 06176e35..4d0c5815 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -2,8 +2,8 @@ using System.Data; using System.Data.SqlClient; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 5c893b3c..7ca70836 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,8 +1,8 @@ using System.Data; using System.IO; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs index 0342c856..37821dd6 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/Fixtures/TestDbSession.cs @@ -2,9 +2,10 @@ using System.Data; using System.Security.Principal; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; +using Backend.Fx.Util; +using NodaTime; namespace Backend.Fx.EfCore5Persistence.Tests.Fixtures { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs index 110a9b90..44a6d2d6 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs index 7978a465..5dbf8f4b 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs @@ -1,4 +1,5 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.InMemoryPersistence; namespace Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index c25e3b9d..a43ebf84 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -1,9 +1,8 @@ using System.Data.Common; using Backend.Fx.EfCore5Persistence.Bootstrapping; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Data.Sqlite; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs index 3b3d9dfb..ee3f83da 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -2,13 +2,13 @@ using System.Data; using System.IO; using System.Threading.Tasks; -using Backend.Fx.BuildingBlocks; +using Backend.Fx.Domain; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Runtime; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs index 7f181ff2..5528f59a 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -2,15 +2,16 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using Backend.Fx.Domain; using Backend.Fx.EfCore5Persistence.Tests.Fixtures; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; using Backend.Fx.Features.Authorization; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Hacking; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using FakeItEasy; +using NodaTime; using SampleApp.Domain; using Xunit; using Xunit.Abstractions; @@ -31,8 +32,7 @@ public TheRepositoryOfComposedAggregate(ITestOutputHelper testOutputHelper) : ba private static int _nextId = 1; private readonly int _tenantId = _nextTenantId++; - private readonly IEqualityComparer _tolerantDateTimeComparer = - new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000)); + private readonly IEqualityComparer _tolerantDateTimeComparer = new TolerantInstantComparer(Duration.FromMilliseconds(5000)); private readonly IEntityIdGenerator _idGenerator = A.Fake(); private readonly DatabaseFixture _fixture; @@ -88,7 +88,7 @@ private int CreateBlogWithPost(IDbConnection dbConnection, int postCount = 1) // string strChangedOn = // dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); // DateTime changedOn = DateTime.Parse(strChangedOn); - // Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(100))); + // Assert.Equal(clock.GetCurrentInstant(), changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(100))); // } // } @@ -291,8 +291,8 @@ public void CanUpdate() [Fact] public void CanUpdateDependant() { - var clock = new AdjustableClock(new WallClock()); - clock.OverrideUtcNow(new DateTime(2020, 01, 20, 20, 30, 40)); + var clock = new AdjustableClock(SystemClock.Instance); + clock.OverrideUtcNow(Instant.FromUtc(2020, 01, 20, 20, 30, 40)); int id; using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) @@ -320,15 +320,15 @@ public void CanUpdateDependant() var strChangedOn = dbSession.DbConnection.ExecuteScalar($"SELECT changedon FROM Posts where id = {post.Id}"); DateTime changedOn = DateTime.Parse(strChangedOn); - Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); + Assert.Equal(clock.GetCurrentInstant(), Instant.FromDateTimeUtc(changedOn), new TolerantInstantComparer(Duration.FromMilliseconds(500))); } } [Fact] public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() { - var clock = new AdjustableClock(new WallClock()); - clock.OverrideUtcNow(new DateTime(2020, 01, 20, 20, 30, 40)); + var clock = new AdjustableClock(SystemClock.Instance); + clock.OverrideUtcNow(Instant.FromUtc(2020, 01, 20, 20, 30, 40)); int id; using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) @@ -336,7 +336,7 @@ public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() id = CreateBlogWithPost(dbSession.DbConnection, 10); } - DateTime expectedModifiedOn = clock.Advance(TimeSpan.FromHours(1)); + Instant expectedModifiedOn = clock.Advance(Duration.FromHours(1)); using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) { diff --git a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs index 62cc12f6..97ad1903 100644 --- a/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore5Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -2,11 +2,12 @@ using System.Threading.Tasks; using Backend.Fx.EfCore5Persistence.Tests.Fixtures; using Backend.Fx.EfCore5Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Authorization; using Backend.Fx.TestUtil; +using Backend.Fx.Util; +using NodaTime; using SampleApp.Domain; using Xunit; using Xunit.Abstractions; @@ -94,7 +95,7 @@ public void CanRead() Blogger bratislavMetulsky = repo.Single(444); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -103,7 +104,7 @@ public void CanRead() Assert.NotNull(bratislavMetulsky); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -133,7 +134,7 @@ public async Task CanReadAsync() Blogger bratislavMetulsky = await repo.SingleAsync(555); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -142,7 +143,7 @@ public async Task CanReadAsync() Assert.NotNull(bratislavMetulsky); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -181,7 +182,7 @@ public void CanUpdate() { var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blogger johnnyFlash = repo.Single(456); - Assert.Equal(DateTime.UtcNow, johnnyFlash.ChangedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000))); + Assert.Equal(SystemClock.Instance.GetCurrentInstant(), johnnyFlash.ChangedOn, new TolerantInstantComparer(Duration.FromMilliseconds(5000))); Assert.Equal(new SystemIdentity().Name, johnnyFlash.ChangedBy); Assert.Equal("Johnny", johnnyFlash.FirstName); Assert.Equal("Flash", johnnyFlash.LastName); diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj index 7830fb08..b3cac1c1 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Backend.Fx.EfCore6Persistence.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs index fb78a6db..19a6fa3b 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/DatabaseFixture.cs @@ -1,10 +1,10 @@ using System.Data; using System.Security.Principal; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Features.Persistence; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures { @@ -33,7 +33,7 @@ CurrentIdentityHolder CreateAsIdentity() return cih; } - clock ??= new WallClock(); + clock ??= SystemClock.Instance; operation ??= UseOperation(); operation.Begin(); diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs index 72892469..6f124a82 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqlServerDatabaseFixture.cs @@ -2,8 +2,8 @@ using System.Data; using System.Data.SqlClient; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs index 28e35a4d..c77cf993 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/SqliteDatabaseFixture.cs @@ -1,8 +1,8 @@ using System.Data; using System.IO; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -14,12 +14,12 @@ public class SqliteDatabaseFixture : DatabaseFixture protected override DbContextOptions GetDbContextOptionsForDbCreation() { - return new DbContextOptionsBuilder().UseSqlite(_connectionString).Options; + return new DbContextOptionsBuilder().UseSqlite(_connectionString, opt => opt.UseNodaTime()).Options; } public override DbContextOptionsBuilder GetDbContextOptionsBuilder(IDbConnection connection) { - return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection); + return new DbContextOptionsBuilder().UseSqlite((SqliteConnection) connection, opt => opt.UseNodaTime()); } public override DbConnectionOperationDecorator UseOperation() diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs index 4bc0ea5f..a9dd734f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/Fixtures/TestDbSession.cs @@ -2,9 +2,10 @@ using System.Data; using System.Security.Principal; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; +using Backend.Fx.Util; +using NodaTime; namespace Backend.Fx.EfCore6Persistence.Tests.Fixtures { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs index 1e6ee53f..147b867a 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbBootstrapper.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Microsoft.EntityFrameworkCore; namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence @@ -17,7 +17,7 @@ public void Dispose() public void EnsureDatabaseExistence() { var dbContext = new SampleAppDbContext( - new DbContextOptionsBuilder().UseSqlite(_connectionString).Options); + new DbContextOptionsBuilder().UseSqlite(_connectionString, opt => opt.UseNodaTime()).Options); dbContext.Database.EnsureCreated(); } } diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs index 4730fbfd..ad8fe32e 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppDbContextFactory.cs @@ -11,7 +11,7 @@ public class SampleAppDbContextFactory : IDesignTimeDbContextFactory().UseSqlite("DataSource=:memory:").Options); + return new SampleAppDbContext(new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:", opt => opt.UseNodaTime()).Options); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs index 36a34113..4b18d925 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Persistence/SampleAppIdGenerator.cs @@ -1,4 +1,5 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.InMemoryPersistence; namespace Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs index 1ae970c7..f9445e2f 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/SampleApp/Runtime/SampleAppBuilder.cs @@ -1,10 +1,10 @@ using System.Data.Common; using Backend.Fx.EfCore6Persistence.Bootstrapping; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; +using Backend.Fx.Features.DomainEvents; using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Data.Sqlite; @@ -27,13 +27,15 @@ public static IBackendFxApplication Build(CompositionRootType compositionRootTyp typeof(BlogMapping).Assembly, typeof(Blog).Assembly); + application = new DomainEventsApplication(application); + application = new PersistentApplication( new SampleAppDbBootstrapper(connectionString), A.Fake(), new EfCorePersistenceModule( dbConnectionFactory, A.Fake(), - (builder, connection) => builder.UseSqlite((DbConnection)connection), + (builder, connection) => builder.UseSqlite((DbConnection)connection, opt => opt.UseNodaTime()), false, application.Assemblies ), diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs index 2a3473fa..23da0c03 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheDbApplicationWithEfCore.cs @@ -2,13 +2,13 @@ using System.Data; using System.IO; using System.Threading.Tasks; -using Backend.Fx.BuildingBlocks; +using Backend.Fx.Domain; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Runtime; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Features.Persistence; using Backend.Fx.TestUtil; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs index 2014bf14..af55d4c4 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -2,15 +2,16 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using Backend.Fx.Domain; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; using Backend.Fx.Features.Authorization; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Hacking; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using FakeItEasy; +using NodaTime; using SampleApp.Domain; using Xunit; using Xunit.Abstractions; @@ -31,8 +32,7 @@ public TheRepositoryOfComposedAggregate(ITestOutputHelper testOutputHelper) : ba private static int _nextId = 1; private readonly int _tenantId = _nextTenantId++; - private readonly IEqualityComparer _tolerantDateTimeComparer = - new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000)); + private readonly IEqualityComparer _tolerantDateTimeComparer = new TolerantInstantComparer(Duration.FromMilliseconds(5000)); private readonly IEntityIdGenerator _idGenerator = A.Fake(); private readonly DatabaseFixture _fixture; @@ -88,7 +88,7 @@ private int CreateBlogWithPost(IDbConnection dbConnection, int postCount = 1) // string strChangedOn = // dbSession.DbConnection.ExecuteScalar($"SELECT ChangedOn FROM Posts where id = {post.Id}"); // DateTime changedOn = DateTime.Parse(strChangedOn); - // Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(100))); + // Assert.Equal(clock.GetCurrentInstant(), changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(100))); // } // } @@ -294,8 +294,8 @@ public void CanUpdate() [Fact] public void CanUpdateDependant() { - var clock = new AdjustableClock(new WallClock()); - clock.OverrideUtcNow(new DateTime(2020, 01, 20, 20, 30, 40)); + var clock = new AdjustableClock(SystemClock.Instance); + clock.OverrideUtcNow(Instant.FromUtc(2020, 01, 20, 20, 30, 40)); int id; using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) @@ -321,18 +321,18 @@ public void CanUpdateDependant() var name = dbSession.DbConnection.ExecuteScalar($"SELECT name FROM Posts where id = {post.Id}"); Assert.Equal("modified", name); - var strChangedOn = - dbSession.DbConnection.ExecuteScalar($"SELECT changedon FROM Posts where id = {post.Id}"); - DateTime changedOn = DateTime.Parse(strChangedOn); - Assert.Equal(clock.UtcNow, changedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(500))); + var changedOn = + dbSession.DbConnection.ExecuteScalar($"SELECT changedon FROM Posts where id = {post.Id}"); + //DateTime changedOn = DateTime.Parse(strChangedOn); + Assert.Equal(clock.GetCurrentInstant(), changedOn, new TolerantInstantComparer(Duration.FromMilliseconds(500))); } } [Fact] public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() { - var clock = new AdjustableClock(new WallClock()); - clock.OverrideUtcNow(new DateTime(2020, 01, 20, 20, 30, 40)); + var clock = new AdjustableClock(SystemClock.Instance); + clock.OverrideUtcNow(Instant.FromUtc(2020, 01, 20, 20, 30, 40)); int id; using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) @@ -340,7 +340,7 @@ public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() id = CreateBlogWithPost(dbSession.DbConnection, 10); } - DateTime expectedModifiedOn = clock.Advance(TimeSpan.FromHours(1)); + Instant expectedModifiedOn = clock.Advance(Duration.FromHours(1)); using (TestDbSession dbSession = _fixture.CreateTestDbSession(clock: clock)) { diff --git a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs index e322d394..476d2624 100644 --- a/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCore6Persistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -2,11 +2,12 @@ using System.Threading.Tasks; using Backend.Fx.EfCore6Persistence.Tests.Fixtures; using Backend.Fx.EfCore6Persistence.Tests.SampleApp.Persistence; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Extensions; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Authorization; using Backend.Fx.TestUtil; +using Backend.Fx.Util; +using NodaTime; using SampleApp.Domain; using Xunit; using Xunit.Abstractions; @@ -94,7 +95,7 @@ public void CanRead() Blogger bratislavMetulsky = repo.Single(444); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -103,7 +104,7 @@ public void CanRead() Assert.NotNull(bratislavMetulsky); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -133,7 +134,7 @@ public async Task CanReadAsync() Blogger bratislavMetulsky = await repo.SingleAsync(555); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -142,7 +143,7 @@ public async Task CanReadAsync() Assert.NotNull(bratislavMetulsky); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); Assert.Equal("the test", bratislavMetulsky.CreatedBy); - Assert.Equal(new DateTime(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); + Assert.Equal(Instant.FromUtc(2012, 05, 12, 23, 12, 09), bratislavMetulsky.CreatedOn); Assert.Equal("Bratislav", bratislavMetulsky.FirstName); Assert.Equal("Metulsky", bratislavMetulsky.LastName); Assert.Equal("whatever", bratislavMetulsky.Bio); @@ -181,7 +182,7 @@ public void CanUpdate() { var repo = new EfRepository(dbSession.DbContext, new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blogger johnnyFlash = repo.Single(456); - Assert.Equal(DateTime.UtcNow, johnnyFlash.ChangedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000))); + Assert.Equal(SystemClock.Instance.GetCurrentInstant(), johnnyFlash.ChangedOn, new TolerantInstantComparer(Duration.FromMilliseconds(5000))); Assert.Equal(new SystemIdentity().Name, johnnyFlash.ChangedBy); Assert.Equal("Johnny", johnnyFlash.FirstName); Assert.Equal("Flash", johnnyFlash.LastName); diff --git a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs index b8991a36..345e9db0 100644 --- a/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs +++ b/tests/Backend.Fx.RabbitMq.Tests/TheRabbitMqMessageBus.cs @@ -1,8 +1,8 @@ using System; using System.Diagnostics; using System.Threading; -using Backend.Fx.Features.MessageBus; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.TestUtil; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; @@ -57,11 +57,11 @@ public void CanBeUsedWithBackendFxApplication() receiver.Connect(); receiver.Subscribe(); - sender.Publish(new TestIntegrationEvent(1)); + sender.PublishAsync(new TestIntegrationEvent(1)); Assert.True(_received.WaitOne(Debugger.IsAttached ? int.MaxValue : 5000)); } - public class TestIntegrationEventHandler : IIntegrationMessageHandler + public class TestIntegrationEventHandler : IIntegrationEventHandler { private readonly ManualResetEvent _received; diff --git a/tests/Backend.Fx.TestUtil/CompositionRootType.cs b/tests/Backend.Fx.TestUtil/CompositionRootType.cs index beae5701..ec345850 100644 --- a/tests/Backend.Fx.TestUtil/CompositionRootType.cs +++ b/tests/Backend.Fx.TestUtil/CompositionRootType.cs @@ -1,6 +1,6 @@ using System; +using Backend.Fx.DependencyInjection; using Backend.Fx.MicrosoftDependencyInjection; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.SimpleInjectorDependencyInjection; namespace Backend.Fx.TestUtil diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs index 991733a1..4a7cab34 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; using Backend.Fx.RandomData; using Backend.Fx.TestUtil; using JetBrains.Annotations; +using NodaTime; using Xunit; using Xunit.Abstractions; @@ -44,7 +44,7 @@ public TestEntity(string name, TestAggregateRoot parent) [Fact] public void ChangedByPropertyIsChoppedAt100Chars() { - DateTime now = DateTime.UtcNow; + Instant now = SystemClock.Instance.GetCurrentInstant(); var sut = new TestAggregateRoot(_nextId++, "gaga"); var moreThanHundred = Letters.RandomLowerCase(110); sut.SetModifiedProperties(moreThanHundred, now); @@ -54,7 +54,7 @@ public void ChangedByPropertyIsChoppedAt100Chars() [Fact] public void ChangedByPropertyIsStoredCorrectly() { - DateTime now = DateTime.UtcNow; + Instant now = SystemClock.Instance.GetCurrentInstant(); var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetModifiedProperties("me", now); Assert.Equal("me", sut.ChangedBy); @@ -64,7 +64,7 @@ public void ChangedByPropertyIsStoredCorrectly() [Fact] public void ChangedOnPropertyIsStoredCorrectly() { - DateTime now = DateTime.UtcNow; + Instant now = SystemClock.Instance.GetCurrentInstant(); var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetModifiedProperties("me", now); Assert.Equal(now, sut.ChangedOn); @@ -74,7 +74,7 @@ public void ChangedOnPropertyIsStoredCorrectly() [Fact] public void CreatedByPropertyIsChoppedAt100Chars() { - DateTime now = DateTime.UtcNow; + Instant now = SystemClock.Instance.GetCurrentInstant(); var sut = new TestAggregateRoot(_nextId++, "gaga"); var moreThanHundred = Letters.RandomLowerCase(110); sut.SetCreatedProperties(moreThanHundred, now); @@ -84,7 +84,7 @@ public void CreatedByPropertyIsChoppedAt100Chars() [Fact] public void CreatedByPropertyIsStoredCorrectly() { - DateTime now = DateTime.UtcNow; + Instant now = SystemClock.Instance.GetCurrentInstant(); var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetCreatedProperties("me", now); Assert.Equal("me", sut.CreatedBy); @@ -94,7 +94,7 @@ public void CreatedByPropertyIsStoredCorrectly() [Fact] public void CreatedOnPropertyIsStoredCorrectly() { - DateTime now = DateTime.UtcNow; + Instant now = SystemClock.Instance.GetCurrentInstant(); var sut = new TestAggregateRoot(_nextId++, "gaga"); sut.SetCreatedProperties("me", now); Assert.Equal(now, sut.CreatedOn); @@ -106,7 +106,7 @@ public void ThrowsGivenEmptyChangedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetModifiedProperties("", DateTime.UtcNow)); + Assert.Throws(() => sut.SetModifiedProperties("", SystemClock.Instance.GetCurrentInstant())); } [Fact] @@ -114,7 +114,7 @@ public void ThrowsGivenEmptyCreatedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetCreatedProperties("", DateTime.UtcNow)); + Assert.Throws(() => sut.SetCreatedProperties("", SystemClock.Instance.GetCurrentInstant())); } [Fact] @@ -122,7 +122,7 @@ public void ThrowsGivenNullChangedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetModifiedProperties(null, DateTime.UtcNow)); + Assert.Throws(() => sut.SetModifiedProperties(null, SystemClock.Instance.GetCurrentInstant())); } [Fact] @@ -130,7 +130,7 @@ public void ThrowsGivenNullCreatedBy() { var sut = new TestAggregateRoot(_nextId++, "gaga"); // ReSharper disable once AssignNullToNotNullAttribute - Assert.Throws(() => sut.SetCreatedProperties(null, DateTime.UtcNow)); + Assert.Throws(() => sut.SetCreatedProperties(null, SystemClock.Instance.GetCurrentInstant())); } public TheAggregateRoot(ITestOutputHelper output) : base(output) diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs index 44e1c66a..11f4e0ac 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs @@ -20,7 +20,7 @@ public TheRepository(ITestOutputHelper output) : base(output) [Fact] public void AcceptsNullArrayToResolve() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -35,7 +35,7 @@ public void AcceptsNullArrayToResolve() [Fact] public void CanResolveListOfIds() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -84,7 +84,7 @@ public void ThrowsOnAttemptToDeleteNull() [Fact] public void DeletesItemFromMyTenant() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -106,7 +106,7 @@ public void DeletesItemFromMyTenant() [Fact] public void DoesNotReturnItemsFromOtherTenants() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -127,7 +127,7 @@ public void DoesNotReturnItemsFromOtherTenants() [Fact] public void MaintainsTenantIdOnAdd() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -145,7 +145,7 @@ public void MaintainsTenantIdOnAdd() [Fact] public void ProvidesCorrectAny() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -172,7 +172,7 @@ public void ProvidesCorrectAny() [Fact] public void ReturnsAll() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -198,7 +198,7 @@ public void ReturnsAll() [Fact] public void ReturnsByIdOnSingle() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -227,7 +227,7 @@ public void ReturnsByIdOnSingle() [Fact] public void ReturnsByIdOnSingleOrDefault() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -256,7 +256,7 @@ public void ReturnsByIdOnSingleOrDefault() [Fact] public void ReturnsEmptyWhenTenantIdHolderIsEmpty() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), authorization); @@ -271,7 +271,7 @@ public void ReturnsEmptyWhenTenantIdHolderIsEmpty() [Fact] public void ReturnsOnlyAuthorizedRecords() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q.Where(agg => agg.Id == 25 || agg.Id == 26)); @@ -301,7 +301,7 @@ public void ReturnsOnlyAuthorizedRecords() [Fact] public void ReturnsOnlyItemsFromMyTenant() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -338,7 +338,7 @@ public void ReturnsOnlyItemsFromMyTenant() [Fact] public void ThrowsOnAddWhenTenantIdIsEmpty() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), authorization); @@ -358,7 +358,7 @@ public void ThrowsOnAddWhenTenantIdIsEmpty() [Fact] public void ThrowsOnAddRangeWhenTenantIdIsEmpty() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(null), authorization); @@ -378,7 +378,7 @@ public void ThrowsOnAddRangeWhenTenantIdIsEmpty() [Fact] public void ThrowsOnAddWhenUnauthorized() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), authorization); @@ -393,7 +393,7 @@ public void ThrowsOnAddWhenUnauthorized() [Fact] public void ThrowsOnAddRangeWhenUnauthorized() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); var sut = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(234), authorization); @@ -408,7 +408,7 @@ public void ThrowsOnAddRangeWhenUnauthorized() [Fact] public void ThrowsOnDeleteWhenTenantDoesNotMatch() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -433,7 +433,7 @@ public void ThrowsOnDeleteWhenTenantDoesNotMatch() [Fact] public void ThrowsOnDeleteWhenTenantIdHolderIsEmpty() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -452,7 +452,7 @@ public void ThrowsOnDeleteWhenTenantIdHolderIsEmpty() [Fact] public void ThrowsOnDeleteWhenUnauthorized() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); @@ -472,7 +472,7 @@ public void ThrowsOnDeleteWhenUnauthorized() [Fact] public void ThrowsOnResolveWhenTenantDoesNotMatch() { - var authorization = A.Fake>(); + var authorization = A.Fake>(); A.CallTo(() => authorization.HasAccessExpression).Returns(agg => true); A.CallTo(() => authorization.Filter(A>._)) .ReturnsLazily((IQueryable q) => q); diff --git a/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs b/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs index 88cecfa6..7e04d745 100644 --- a/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs +++ b/tests/Backend.Fx.Tests/BuildingBlocks/TheValueObject.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs index a88c62ab..b283071e 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs @@ -1,7 +1,6 @@ using System; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.ConfigurationSettings; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; +using Backend.Fx.Features.ConfigurationSettings; using Backend.Fx.TestUtil; using JetBrains.Annotations; using Xunit; @@ -12,9 +11,9 @@ namespace Backend.Fx.Tests.ConfigurationSettings public class TheSetting : TestWithLogging { [UsedImplicitly] - public class TestSettingsService : SettingsService + public class TestSettingsCategory : SettingsCategory { - public TestSettingsService(IEntityIdGenerator idGenerator, IRepository settingRepository) + public TestSettingsCategory(IEntityIdGenerator idGenerator, IRepository settingRepository) : base("Test", idGenerator, settingRepository, new SettingSerializerFactory()) { } diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs index 917d5dca..f4bd58a2 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs @@ -1,5 +1,5 @@ using System; -using Backend.Fx.ConfigurationSettings; +using Backend.Fx.Features.ConfigurationSettings; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs index f2d9e177..3bda64fe 100644 --- a/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs +++ b/tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs @@ -1,9 +1,8 @@ using System.Linq; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.ConfigurationSettings; +using Backend.Fx.Domain; using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Features.Authorization; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Features.ConfigurationSettings; using Backend.Fx.Hacking; using Backend.Fx.InMemoryPersistence; using Backend.Fx.TestUtil; @@ -17,7 +16,7 @@ public class TheSettingsService : TestWithLogging { public TheSettingsService(ITestOutputHelper output): base(output) { - var settingAuthorization = A.Fake>(); + var settingAuthorization = A.Fake>(); A.CallTo(() => settingAuthorization.HasAccessExpression).Returns(setting => true); A.CallTo(() => settingAuthorization.Filter(A>._)).ReturnsLazily((IQueryable q) => q); A.CallTo(() => settingAuthorization.CanCreate(A._)).Returns(true); @@ -28,9 +27,9 @@ public TheSettingsService(ITestOutputHelper output): base(output) _settingRepository = new InMemoryRepository(new InMemoryStore(), CurrentTenantIdHolder.Create(999), settingAuthorization); } - public class MySettingsService : SettingsService + public class MySettingsCategory : SettingsCategory { - public MySettingsService(IEntityIdGenerator idGenerator, IRepository repo) + public MySettingsCategory(IEntityIdGenerator idGenerator, IRepository repo) : base("My", idGenerator, repo, new SettingSerializerFactory()) { } @@ -54,7 +53,7 @@ public string SmtpHost [Fact] public void ReadsNonExistingSettingAsDefaultFromRepository() { - var sut = new MySettingsService(_idGenerator, _settingRepository); + var sut = new MySettingsCategory(_idGenerator, _settingRepository); Assert.Null(sut.SmtpHost); } @@ -66,7 +65,7 @@ public void ReadsNullSettingFromRepository() _settingRepository.Add(setting); - var sut = new MySettingsService(_idGenerator, _settingRepository); + var sut = new MySettingsCategory(_idGenerator, _settingRepository); Assert.Null(sut.SmtpHost); } @@ -78,14 +77,14 @@ public void ReadsSettingFromRepository() _settingRepository.Add(setting); - var sut = new MySettingsService(_idGenerator, _settingRepository); + var sut = new MySettingsCategory(_idGenerator, _settingRepository); Assert.Equal(333, sut.SmtpPort); } [Fact] public void StoresSettingsInRepository() { - var sut = new MySettingsService(_idGenerator, _settingRepository) {SmtpPort = 333}; + var sut = new MySettingsCategory(_idGenerator, _settingRepository) {SmtpPort = 333}; Assert.Equal(333, sut.SmtpPort); var settings = _settingRepository.GetAll(); diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs b/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs index 4ba18001..77340cbd 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs +++ b/tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Authentication; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs b/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs index 44f1c8ee..7f4febb3 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs +++ b/tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Authentication; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs b/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs index b870b7f5..e7c5a547 100644 --- a/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs +++ b/tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Environment.Authentication; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs index 463719c0..8110e128 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs +++ b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs @@ -1,7 +1,8 @@ using System; using System.Threading; -using Backend.Fx.Environment.DateAndTime; +using Backend.Fx.Hacking; using Backend.Fx.TestUtil; +using NodaTime; using Xunit; using Xunit.Abstractions; @@ -13,12 +14,12 @@ class TheAdjustableClock : TestWithLogging [Fact] public void AllowsOverridingOfUtcNow() { - var overriddenUtcNow = new DateTime(2000, 1, 1, 12, 0, 0); - var sut = new AdjustableClock(new WallClock()); + var overriddenUtcNow = Instant.FromUtc(2000, 1, 1, 12, 0, 0); + var sut = new AdjustableClock(SystemClock.Instance); sut.OverrideUtcNow(overriddenUtcNow); - Assert.Equal(overriddenUtcNow, sut.UtcNow); + Assert.Equal(overriddenUtcNow, sut.GetCurrentInstant()); Thread.Sleep(100); - Assert.Equal(overriddenUtcNow, sut.UtcNow); + Assert.Equal(overriddenUtcNow, sut.GetCurrentInstant()); } public TheAdjustableClock(ITestOutputHelper output) : base(output) diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs index 4ef74b58..9f8778f4 100644 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs +++ b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs @@ -1,7 +1,7 @@ -using System; -using System.Threading; -using Backend.Fx.Environment.DateAndTime; +using System.Threading; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; +using NodaTime; using Xunit; using Xunit.Abstractions; @@ -13,11 +13,11 @@ public class TheFrozenClock : TestWithLogging public void IsFrozen() { - IClock sut = new FrozenClock(new WallClock()); - DateTime systemUtcNow = sut.UtcNow; + IClock sut = new FrozenClock(SystemClock.Instance); + Instant systemUtcNow = sut.GetCurrentInstant(); Thread.Sleep(100); - Assert.Equal(systemUtcNow, sut.UtcNow); - Assert.NotEqual(DateTime.UtcNow, sut.UtcNow); + Assert.Equal(systemUtcNow, sut.GetCurrentInstant()); + Assert.NotEqual(SystemClock.Instance.GetCurrentInstant(), sut.GetCurrentInstant()); } public TheFrozenClock(ITestOutputHelper output) : base(output) diff --git a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs b/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs deleted file mode 100644 index dc23ae47..00000000 --- a/tests/Backend.Fx.Tests/Environment/DateAndTime/TheWallClock.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Backend.Fx.Environment.DateAndTime; -using Backend.Fx.Extensions; -using Backend.Fx.TestUtil; -using Xunit; -using Xunit.Abstractions; - -namespace Backend.Fx.Tests.Environment.DateAndTime -{ - public class TheWallClock : TestWithLogging - { - private readonly IEqualityComparer _tolerantDateTimeComparer = new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(10)); - - [Fact] - public void IsTheSystemClock() - { - IClock sut = new WallClock(); - - Assert.Equal(DateTime.UtcNow, sut.UtcNow, _tolerantDateTimeComparer); - - Thread.Sleep(100); - - Assert.Equal(DateTime.UtcNow, sut.UtcNow, _tolerantDateTimeComparer); - } - - public TheWallClock(ITestOutputHelper output) : base(output) - { - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs index 97c8b55c..827a16ce 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Security.Principal; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs index 0b0a4a4f..bf7f771f 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs @@ -3,7 +3,6 @@ using Backend.Fx.InMemoryPersistence; using Backend.Fx.Logging; using Backend.Fx.MicrosoftDependencyInjection; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs index 715a0623..492a5c59 100644 --- a/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs +++ b/tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.InMemoryPersistence; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs b/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs index 5d9a9f03..711eac7b 100644 --- a/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs +++ b/tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs @@ -1,8 +1,8 @@ using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Features.Persistence; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; diff --git a/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs b/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs index b155ed57..1150c4fb 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs @@ -1,6 +1,6 @@ using System; -using Backend.Fx.Extensions; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs b/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs index 3ded35c7..d93d4d82 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -using Backend.Fx.Extensions; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs b/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs index a4a466b3..fe4cbae4 100644 --- a/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs +++ b/tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs @@ -1,6 +1,6 @@ using System; -using Backend.Fx.Extensions; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs index 8823bbb0..7ed4d93e 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.Tests.Patterns.Authorization { public class TheAllowAllImplementation : TestWithLogging { - private readonly IAggregateAuthorization _sut = new AllowAll(); + private readonly IAuthorizationPolicy _sut = new AllowAll(); private readonly TheAggregateRoot.TestAggregateRoot _testAggregateRoot = new TheAggregateRoot.TestAggregateRoot(1,"e"); [Fact] diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs index e66209e1..26e6d446 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs @@ -1,11 +1,10 @@ using System; using System.Linq.Expressions; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Authorization; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using Microsoft.Extensions.DependencyInjection; @@ -35,7 +34,7 @@ public async Task CanResolveAuthorizationTypes(CompositionRootType compositionRo sut.Invoker.Invoke(sp => { - var auth = sp.GetRequiredService>(); + var auth = sp.GetRequiredService>(); Assert.IsType(auth); }, new SystemIdentity(), diff --git a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs b/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs index 45da256e..a45d6e2a 100644 --- a/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs +++ b/tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.Tests.Patterns.Authorization { public class TheDenyAllImplementation : TestWithLogging { - private readonly IAggregateAuthorization _sut = new DenyAll(); + private readonly IAuthorizationPolicy _sut = new DenyAll(); private readonly TheAggregateRoot.TestAggregateRoot _testAggregateRoot = new TheAggregateRoot.TestAggregateRoot(1,"e"); diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs index 41ce18cb..cd6dbbed 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.DataGeneration; +using Backend.Fx.Extensions.DataGeneration; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs index b48fda6b..bccd697e 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; +using Backend.Fx.DependencyInjection; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.DataGeneration; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Hacking; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; @@ -40,7 +40,7 @@ public void DelegatesAllOtherCalls() app); // ReSharper disable UnusedVariable - IBackendFxApplicationAsyncInvoker ai = sut.AsyncInvoker; + IBackendFxApplicationInvoker ai = sut.AsyncInvoker; A.CallTo(() => app.AsyncInvoker).MustHaveHappenedOnceExactly(); ICompositionRoot cr = sut.CompositionRoot; diff --git a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs index b5c14c67..1b29a2df 100644 --- a/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.DataGeneration; +using Backend.Fx.Extensions.DataGeneration; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs index d37f466d..e085fa31 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs @@ -1,8 +1,11 @@ using System; using System.Security.Principal; +using Backend.Fx.DependencyInjection; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Util; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs index 182868a4..89e86dd5 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs @@ -2,13 +2,12 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Features.Authorization; using Backend.Fx.Features.DomainEvents; -using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Tests.Patterns.Authorization; using Backend.Fx.TestUtil; using FakeItEasy; @@ -36,7 +35,7 @@ public void ProvidesSpecificDecoratorWhenPresent() new SingleTenantApplication( A.Fake(), true, - new MessageBusApplication( + new MessageBusExtension( A.Fake(), new AuthorizingApplication( new BackendFxApplication( @@ -55,7 +54,7 @@ public void ProvidesNoDecoratorWhenNotPresent() new SingleTenantApplication( A.Fake(), true, - new MessageBusApplication( + new MessageBusExtension( A.Fake(), new BackendFxApplication( CompositionRootType.Microsoft.Create(), diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs index f608e6e2..99a289ba 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs @@ -1,9 +1,7 @@ using System; using System.Security.Principal; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; @@ -19,17 +17,17 @@ public TheBackendFxApplicationAsyncInvoker(ITestOutputHelper output): base(outpu _sut = new BackendFxApplicationInvoker(_fakes.CompositionRoot); } - private readonly IBackendFxApplicationAsyncInvoker _sut; + private readonly IBackendFxApplicationInvoker _sut; private readonly DiTestFakes _fakes; [Fact] public async Task BeginsNewScopeForEveryInvocation() { - await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity()); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity()); A.CallTo(() => _fakes.CompositionRoot.BeginScope()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.ServiceScope.Dispose()).MustHaveHappenedTwiceExactly(); } @@ -37,11 +35,11 @@ public async Task BeginsNewScopeForEveryInvocation() [Fact] public async Task BeginsNewOperationForEveryInvocation() { - await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity()); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedOnceExactly(); - await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111)); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity()); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedTwiceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustHaveHappenedTwiceExactly(); } @@ -50,7 +48,7 @@ public async Task BeginsNewOperationForEveryInvocation() public async Task DoesNotCatchFrameworkExceptions() { A.CallTo(() => _fakes.CompositionRoot.BeginScope()).Throws(); - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity())); A.CallTo(() => _fakes.Operation.Begin()).MustNotHaveHappened(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -58,7 +56,7 @@ public async Task DoesNotCatchFrameworkExceptions() [Fact] public async Task DoesNotCatchOperationExceptions() { - await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(_ => throw new SimulatedException(), new AnonymousIdentity(), new TenantId(111))); + await Assert.ThrowsAsync(async () => await _sut.InvokeAsync(_ => throw new SimulatedException(), new AnonymousIdentity())); A.CallTo(() => _fakes.Operation.Begin()).MustHaveHappenedOnceExactly(); A.CallTo(() => _fakes.Operation.Complete()).MustNotHaveHappened(); } @@ -67,7 +65,7 @@ public async Task DoesNotCatchOperationExceptions() public async Task MaintainsCorrelationIdOnInvocation() { var correlationId = Guid.NewGuid(); - await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), new TenantId(123), correlationId); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), correlationId); Assert.Equal(correlationId, _fakes.CurrentCorrelationHolder.Current.Id); } @@ -75,7 +73,7 @@ public async Task MaintainsCorrelationIdOnInvocation() public async Task MaintainsIdentityOnInvocation() { var identity = new GenericIdentity("me"); - await _sut.InvokeAsync(_ => Task.CompletedTask, identity, new TenantId(123)); + await _sut.InvokeAsync(_ => Task.CompletedTask, identity); A.CallTo(() => _fakes.IdentityHolder.ReplaceCurrent(A.That.IsEqualTo(identity))).MustHaveHappenedOnceExactly(); } @@ -83,7 +81,7 @@ public async Task MaintainsIdentityOnInvocation() public async Task MaintainsTenantIdOnInvocation() { var tenantId = new TenantId(123); - await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity(), tenantId); + await _sut.InvokeAsync(_ => Task.CompletedTask, new AnonymousIdentity()); A.CallTo(() => _fakes.TenantIdHolder.ReplaceCurrent(A.That.IsEqualTo(tenantId))).MustHaveHappenedOnceExactly(); } @@ -95,7 +93,7 @@ await _sut.InvokeAsync(sp => { provided = sp; return Task.CompletedTask; - }, new AnonymousIdentity(), new TenantId(111)); + }, new AnonymousIdentity()); Assert.StrictEqual(_fakes.ServiceProvider, provided); } } diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs index ef7d07cc..14057a0f 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs @@ -1,8 +1,7 @@ using System; using System.Security.Principal; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs index 02532331..30218209 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs @@ -1,5 +1,6 @@ using System; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs index b2e1866b..ed281798 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs @@ -1,6 +1,7 @@ using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs index ad382572..a214c546 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs @@ -1,16 +1,16 @@ using System.Security.Principal; using System.Threading.Tasks; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Environment.Authentication; -using Backend.Fx.Environment.DateAndTime; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Features.Authorization; using Backend.Fx.Features.DomainEvents; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using FakeItEasy; using Microsoft.Extensions.DependencyInjection; +using NodaTime; using Xunit; namespace Backend.Fx.Tests.Patterns.DependencyInjection @@ -32,7 +32,7 @@ public async Task CanResolveInfrastructureServices(CompositionRootType compositi sp => { var clock = sp.GetRequiredService(); - Assert.IsType(clock); + Assert.IsType(clock); var correlationHolder = sp.GetRequiredService>(); Assert.IsType(correlationHolder); @@ -63,7 +63,7 @@ public async Task CanResolveAggregateAuthorization(CompositionRootType compositi sut.Invoker.Invoke( sp => { - var authorization = sp.GetRequiredService>(); + var authorization = sp.GetRequiredService>(); Assert.IsType>(authorization); }, new SystemIdentity(), diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs index 43299186..92c69842 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs @@ -1,5 +1,5 @@ using System; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; diff --git a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs index 09268153..a88b16c0 100644 --- a/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs +++ b/tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs @@ -3,10 +3,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Environment.Authentication; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.MessageBus; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; @@ -22,7 +21,7 @@ public TheSequentializingBackendFxApplicationInvoker(ITestOutputHelper output): _output = output; var fakes = new DiTestFakes(); _invoker = new BackendFxApplicationInvoker(fakes.CompositionRoot); - _decoratedInvoker = new SequentializingBackendFxApplicationInvoker(_invoker); + _decoratedInvoker = new IntegrationEventHandlingInvoker(_invoker); } private readonly int _actionDuration = 100; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs index 206fe9da..3a264a20 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; using JetBrains.Annotations; namespace Backend.Fx.Tests.Patterns.EventAggregation.Domain diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingEventHandler.cs similarity index 72% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs rename to tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingEventHandler.cs index ee2977a6..7e88f475 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingEventHandler.cs @@ -1,15 +1,15 @@ using System.Diagnostics; using System.Threading; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; using Xunit; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { - public class BlockingMessageHandler : IIntegrationMessageHandler + public class BlockingEventHandler : IIntegrationEventHandler { private readonly ManualResetEvent _manualResetEvent; - public BlockingMessageHandler(ManualResetEvent manualResetEvent) + public BlockingEventHandler(ManualResetEvent manualResetEvent) { _manualResetEvent = manualResetEvent; } diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicEventHandler.cs new file mode 100644 index 00000000..2db6314c --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicEventHandler.cs @@ -0,0 +1,19 @@ +using Backend.Fx.Extensions.MessageBus; + +namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration +{ + public class DynamicEventHandler : IIntegrationEventHandler<> + { + private readonly IIntegrationEventHandler<> _integrationEventHandlerImplementation; + + public DynamicEventHandler(IIntegrationEventHandler<> integrationEventHandlerImplementation) + { + _integrationEventHandlerImplementation = integrationEventHandlerImplementation; + } + + public void Handle(dynamic eventData) + { + _integrationEventHandlerImplementation.Handle(eventData); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs deleted file mode 100644 index 808d5cd7..00000000 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicMessageHandler.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Backend.Fx.Features.MessageBus; - -namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration -{ - public class DynamicMessageHandler : IIntegrationMessageHandler - { - private readonly IIntegrationMessageHandler _integrationMessageHandlerImplementation; - - public DynamicMessageHandler(IIntegrationMessageHandler integrationMessageHandlerImplementation) - { - _integrationMessageHandlerImplementation = integrationMessageHandlerImplementation; - } - - public void Handle(dynamic eventData) - { - _integrationMessageHandlerImplementation.Handle(eventData); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningEventHandler.cs new file mode 100644 index 00000000..ad28f6bc --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningEventHandler.cs @@ -0,0 +1,21 @@ +using System.Threading; +using Backend.Fx.Extensions.MessageBus; + +namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration +{ + public class LongRunningEventHandler : IIntegrationEventHandler + { + private readonly IIntegrationEventHandler _handler; + + public LongRunningEventHandler(IIntegrationEventHandler handler) + { + _handler = handler; + } + + public void Handle(TestIntegrationEvent eventData) + { + Thread.Sleep(1000); + _handler.Handle(eventData); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs deleted file mode 100644 index 33df7219..00000000 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningMessageHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading; -using Backend.Fx.Features.MessageBus; - -namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration -{ - public class LongRunningMessageHandler : IIntegrationMessageHandler - { - private readonly IIntegrationMessageHandler _handler; - - public LongRunningMessageHandler(IIntegrationMessageHandler handler) - { - _handler = handler; - } - - public void Handle(TestIntegrationEvent eventData) - { - Thread.Sleep(1000); - _handler.Handle(eventData); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs index b0d5d79d..68c716c9 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs index 4196e61a..9835632c 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs @@ -1,5 +1,5 @@ using System.Threading; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs index 3fc1f043..51761dc2 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Extensions.MessageBus.InProc; using Backend.Fx.TestUtil; using Xunit; using Xunit.Abstractions; @@ -12,15 +13,15 @@ public class TheInMemoryMessageBusChannel : TestWithLogging [Fact] public async Task HandlesEventsAsynchronously() { - var channel = new InMemoryMessageBusChannel(); - var messageBus = new InMemoryMessageBus(channel); + var channel = new InProcMessageBusChannel(); + var messageBus = new InProcMessageBus(channel); messageBus.Connect(); messageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); var handled = new ManualResetEvent(false); - messageBus.Subscribe(new BlockingMessageHandler(handled)); + messageBus.Subscribe(new BlockingEventHandler(handled)); - await messageBus.Publish(new TestIntegrationEvent(0, string.Empty)); + await messageBus.PublishAsync(new TestIntegrationEvent(0, string.Empty)); var finishHandleTask = channel.FinishHandlingAllMessagesAsync(); Assert.Contains(finishHandleTask.Status, new[] {TaskStatus.WaitingForActivation, TaskStatus.Running}); @@ -32,21 +33,21 @@ public async Task HandlesEventsAsynchronously() [Fact] public async Task InvokesAllApplicationHandlers() { - var channel = new InMemoryMessageBusChannel(); + var channel = new InProcMessageBusChannel(); - var messageBus = new InMemoryMessageBus(channel); + var messageBus = new InProcMessageBus(channel); var eventHandled = false; messageBus.Connect(); messageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); - messageBus.Subscribe(new DelegateIntegrationMessageHandler(_ => eventHandled = true)); + messageBus.Subscribe(new DelegateIntegrationEventHandler(_ => eventHandled = true)); - var anotherMessageBus = new InMemoryMessageBus(channel); + var anotherMessageBus = new InProcMessageBus(channel); var anotherEventHandled = false; anotherMessageBus.Connect(); anotherMessageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); - messageBus.Subscribe(new DelegateIntegrationMessageHandler(_ => anotherEventHandled = true)); + messageBus.Subscribe(new DelegateIntegrationEventHandler(_ => anotherEventHandled = true)); - await messageBus.Publish(new TestIntegrationEvent(0, string.Empty)); + await messageBus.PublishAsync(new TestIntegrationEvent(0, string.Empty)); await channel.FinishHandlingAllMessagesAsync(); Assert.True(eventHandled); @@ -55,7 +56,7 @@ public async Task InvokesAllApplicationHandlers() eventHandled = false; anotherEventHandled = false; - await anotherMessageBus.Publish(new TestIntegrationEvent(0, string.Empty)); + await anotherMessageBus.PublishAsync(new TestIntegrationEvent(0, string.Empty)); await channel.FinishHandlingAllMessagesAsync(); Assert.True(eventHandled); @@ -65,22 +66,22 @@ public async Task InvokesAllApplicationHandlers() [Fact] public async Task DoesAwaitAllPendingMessages() { - var channel = new InMemoryMessageBusChannel(); - var messageBus = new InMemoryMessageBus(channel); + var channel = new InProcMessageBusChannel(); + var messageBus = new InProcMessageBus(channel); messageBus.Connect(); messageBus.ProvideInvoker(new TheMessageBus.TestInvoker()); var allMessagesAreHandled = false; - messageBus.Subscribe(new DelegateIntegrationMessageHandler(x => + messageBus.Subscribe(new DelegateIntegrationEventHandler(x => { if (x.StringParam == "first message") { - messageBus.Publish(new TestIntegrationEvent(0, "second message")); + messageBus.PublishAsync(new TestIntegrationEvent(0, "second message")); } else if (x.StringParam == "second message") { - messageBus.Publish(new TestIntegrationEvent(0, "third message")); + messageBus.PublishAsync(new TestIntegrationEvent(0, "third message")); } else if (x.StringParam == "third message") { @@ -90,7 +91,7 @@ public async Task DoesAwaitAllPendingMessages() // Publish the first message and await the result. // This should block until all three messages are processed not only the first one was. - await messageBus.Publish(new TestIntegrationEvent(0, "first message")); + await messageBus.PublishAsync(new TestIntegrationEvent(0, "first message")); await channel.FinishHandlingAllMessagesAsync(); Assert.True(allMessagesAreHandled); diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs index ca6179aa..a9f4828c 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs @@ -4,8 +4,9 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.MessageBus; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Extensions.MessageBus.InProc; using Backend.Fx.TestUtil; using FakeItEasy; using JetBrains.Annotations; @@ -25,16 +26,16 @@ public TheInMemoryMessageBus(ITestOutputHelper output): base(output) [Fact] public async Task HandlesEventsAsynchronously() { - Sut.Subscribe(); + Sut.Subscribe(); var sw = new Stopwatch(); sw.Start(); - await Sut.Publish(Invoker.IntegrationEvent); + await Sut.PublishAsync(Invoker.IntegrationEvent); Assert.True(sw.ElapsedMilliseconds < 900); Assert.True(Invoker.IntegrationEvent.TypedProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); Assert.True(sw.ElapsedMilliseconds > 1000); } - protected override IMessageBus Sut { get; } = new InMemoryMessageBus(); + protected override IMessageBus Sut { get; } = new InProcMessageBus(); } [UsedImplicitly] @@ -71,24 +72,24 @@ public TestInvoker() A.CallTo(() => TypedHandler.Handle(A._)).Invokes((TestIntegrationEvent _) => IntegrationEvent.TypedProcessed.Set()); A.CallTo(() => DynamicHandler.Handle(new object())).WithAnyArguments().Invokes((object _) => IntegrationEvent.DynamicProcessed.Set()); - A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(TypedMessageHandler)))) - .Returns(new TypedMessageHandler(TypedHandler)); + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(TypedEventHandler)))) + .Returns(new TypedEventHandler(TypedHandler)); - A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(LongRunningMessageHandler)))) - .Returns(new LongRunningMessageHandler(TypedHandler)); + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(LongRunningEventHandler)))) + .Returns(new LongRunningEventHandler(TypedHandler)); - A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(ThrowingTypedMessageHandler)))) - .Returns(new ThrowingTypedMessageHandler(TypedHandler)); + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(ThrowingTypedEventHandler)))) + .Returns(new ThrowingTypedEventHandler(TypedHandler)); - A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(DynamicMessageHandler)))) - .Returns(new DynamicMessageHandler(DynamicHandler)); + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(DynamicEventHandler)))) + .Returns(new DynamicEventHandler(DynamicHandler)); - A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(ThrowingDynamicMessageHandler)))) - .Returns(new ThrowingDynamicMessageHandler(DynamicHandler)); + A.CallTo(() => FakeServiceProvider.GetService(A.That.IsEqualTo(typeof(ThrowingDynamicEventHandler)))) + .Returns(new ThrowingDynamicEventHandler(DynamicHandler)); } - public IIntegrationMessageHandler TypedHandler { get; } = A.Fake>(); - public IIntegrationMessageHandler DynamicHandler { get; } = A.Fake(); + public IIntegrationEventHandler TypedHandler { get; } = A.Fake>(); + public IIntegrationEventHandler<> DynamicHandler { get; } = A.Fake>(); public IServiceProvider FakeServiceProvider { get; } = A.Fake(); public void Invoke(Action action, IIdentity identity, TenantId tenantId, Guid? correlationId = null) @@ -100,8 +101,8 @@ public void Invoke(Action action, IIdentity identity, TenantId [Fact] public async void CallsDynamicEventHandler() { - Sut.Subscribe(Sut.MessageNameProvider.GetMessageName()); - await Sut.Publish(Invoker.IntegrationEvent); + Sut.Subscribe(Sut.MessageNameProvider.GetMessageName()); + await Sut.PublishAsync(Invoker.IntegrationEvent); Assert.True(Invoker.IntegrationEvent.DynamicProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); A.CallTo(() => Invoker.TypedHandler.Handle(A._)).MustNotHaveHappened(); @@ -111,10 +112,10 @@ public async void CallsDynamicEventHandler() [Fact] public async void CallsMixedEventHandlers() { - Sut.Subscribe(Sut.MessageNameProvider.GetMessageName()); - Sut.Subscribe(); + Sut.Subscribe(Sut.MessageNameProvider.GetMessageName()); + Sut.Subscribe(); - await Sut.Publish(Invoker.IntegrationEvent); + await Sut.PublishAsync(Invoker.IntegrationEvent); Assert.True(Invoker.IntegrationEvent.TypedProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); Assert.True(Invoker.IntegrationEvent.DynamicProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); @@ -129,8 +130,8 @@ public async void CallsMixedEventHandlers() [Fact] public async Task CallsTypedEventHandler() { - Sut.Subscribe(); - await Sut.Publish(Invoker.IntegrationEvent); + Sut.Subscribe(); + await Sut.PublishAsync(Invoker.IntegrationEvent); Assert.True(Invoker.IntegrationEvent.TypedProcessed.Wait(Debugger.IsAttached ? int.MaxValue : 10000)); A.CallTo(() => Invoker.TypedHandler.Handle(A .That @@ -143,9 +144,9 @@ public async Task CallsTypedEventHandler() [Fact] public async Task DoesNotCallUnsubscribedTypedEventHandler() { - Sut.Subscribe(); - Sut.Unsubscribe(); - await Sut.Publish(Invoker.IntegrationEvent); + Sut.Subscribe(); + Sut.Unsubscribe(); + await Sut.PublishAsync(Invoker.IntegrationEvent); A.CallTo(() => Invoker.TypedHandler.Handle(A .That .Matches(evt => evt.IntParam == 34 && evt.StringParam == "gaga"))) @@ -155,9 +156,9 @@ public async Task DoesNotCallUnsubscribedTypedEventHandler() [Fact] public async void DoesNotCallUnsubscribedDynamicEventHandler() { - Sut.Subscribe(Sut.MessageNameProvider.GetMessageName()); - Sut.Unsubscribe(Sut.MessageNameProvider.GetMessageName()); - await Sut.Publish(Invoker.IntegrationEvent); + Sut.Subscribe(Sut.MessageNameProvider.GetMessageName()); + Sut.Unsubscribe(Sut.MessageNameProvider.GetMessageName()); + await Sut.PublishAsync(Invoker.IntegrationEvent); A.CallTo(() => Invoker.DynamicHandler.Handle(A._)).MustNotHaveHappened(); } @@ -165,10 +166,10 @@ public async void DoesNotCallUnsubscribedDynamicEventHandler() public async void DoesNotCallUnsubscribedDelegateEventHandler() { var handled = new ManualResetEvent(false); - var handler = new DelegateIntegrationMessageHandler(_ => handled.Set()); + var handler = new DelegateIntegrationEventHandler(_ => handled.Set()); Sut.Subscribe(handler); Sut.Unsubscribe(handler); - await Sut.Publish(Invoker.IntegrationEvent); + await Sut.PublishAsync(Invoker.IntegrationEvent); Assert.False(handled.WaitOne(Debugger.IsAttached ? int.MaxValue : 1000)); } @@ -182,9 +183,9 @@ public void CannCallConnectButItDoesNothing() public async void DelegateIntegrationMessageHandler() { var handled = new ManualResetEvent(false); - var handler = new DelegateIntegrationMessageHandler(_ => handled.Set()); + var handler = new DelegateIntegrationEventHandler(_ => handled.Set()); Sut.Subscribe(handler); - await Sut.Publish(Invoker.IntegrationEvent); + await Sut.PublishAsync(Invoker.IntegrationEvent); Assert.True(handled.WaitOne(Debugger.IsAttached ? int.MaxValue : 10000)); } } diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs index 696d46a2..c019b6ac 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Extensions.MessageBus.InProc; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; @@ -15,16 +15,16 @@ public class TheMessageBusApplication [InlineData(CompositionRootType.SimpleInjector)] public async Task Boots(CompositionRootType compositionRootType) { - var app = new MessageBusTestApplication(compositionRootType); + var app = new MessageBusTestExtension(compositionRootType); await app.BootAsync(); } } - public class MessageBusTestApplication : MessageBusApplication + public class MessageBusTestExtension : MessageBusExtension { - public MessageBusTestApplication(CompositionRootType compositionRootType) + public MessageBusTestExtension(CompositionRootType compositionRootType) : base( - new InMemoryMessageBus(), + new InProcMessageBus(), new BackendFxApplication( compositionRootType.Create(), A.Fake())) diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs index 93920d6c..b4d5edc7 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs @@ -1,7 +1,8 @@ using Backend.Fx.Environment.MultiTenancy; -using Backend.Fx.Features.MessageBus; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.MessageBus; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using FakeItEasy; using Xunit; using Xunit.Abstractions; @@ -42,7 +43,7 @@ public void DoesNotPublishOnBusWhenPublishing() { var testIntegrationEvent = new Domain.TestIntegrationEvent(44); _sut.Publish(testIntegrationEvent); - A.CallTo(()=>_messageBus.Publish(A._)).MustNotHaveHappened(); + A.CallTo(()=>_messageBus.PublishAsync(A._)).MustNotHaveHappened(); } [Fact] @@ -56,11 +57,11 @@ public void PublishesAllEventsOnRaise() _sut.Publish(ev2); _sut.Publish(ev3); _sut.Publish(ev4); - _sut.RaiseEvents(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev1))).MustHaveHappenedOnceExactly(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev2))).MustHaveHappenedOnceExactly(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev3))).MustHaveHappenedOnceExactly(); - A.CallTo(()=>_messageBus.Publish(A.That.IsEqualTo(ev4))).MustHaveHappenedOnceExactly(); + _sut.RaiseEventsAsync(); + A.CallTo(()=>_messageBus.PublishAsync(A.That.IsEqualTo(ev1))).MustHaveHappenedOnceExactly(); + A.CallTo(()=>_messageBus.PublishAsync(A.That.IsEqualTo(ev2))).MustHaveHappenedOnceExactly(); + A.CallTo(()=>_messageBus.PublishAsync(A.That.IsEqualTo(ev3))).MustHaveHappenedOnceExactly(); + A.CallTo(()=>_messageBus.PublishAsync(A.That.IsEqualTo(ev4))).MustHaveHappenedOnceExactly(); } } } \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs index 2d41160d..f59bacdd 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs @@ -1,14 +1,14 @@ using System; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { - public class ThrowingDynamicMessageHandler : IIntegrationMessageHandler + public class ThrowingDynamicEventHandler : IIntegrationEventHandler<> { public const string ExceptionMessage = "From ThrowingDynamicEventHandler"; - private readonly IIntegrationMessageHandler _handler; + private readonly IIntegrationEventHandler<> _handler; - public ThrowingDynamicMessageHandler(IIntegrationMessageHandler handler) + public ThrowingDynamicEventHandler(IIntegrationEventHandler<> handler) { _handler = handler; } diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedEventHandler.cs similarity index 56% rename from tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs rename to tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedEventHandler.cs index 6b7b145a..4fc13381 100644 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedMessageHandler.cs +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedEventHandler.cs @@ -1,14 +1,14 @@ using System; -using Backend.Fx.Features.MessageBus; +using Backend.Fx.Extensions.MessageBus; namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration { - public class ThrowingTypedMessageHandler : IIntegrationMessageHandler + public class ThrowingTypedEventHandler : IIntegrationEventHandler { public const string ExceptionMessage = "From ThrowingTypedEventHandler"; - private readonly IIntegrationMessageHandler _handler; + private readonly IIntegrationEventHandler _handler; - public ThrowingTypedMessageHandler(IIntegrationMessageHandler handler) + public ThrowingTypedEventHandler(IIntegrationEventHandler handler) { _handler = handler; } diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedEventHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedEventHandler.cs new file mode 100644 index 00000000..0f414b69 --- /dev/null +++ b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedEventHandler.cs @@ -0,0 +1,20 @@ +using Backend.Fx.Extensions.MessageBus; + +namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration +{ + public class TypedEventHandler : IIntegrationEventHandler + { + private readonly IIntegrationEventHandler _integrationEventHandlerImplementation; + + public TypedEventHandler(IIntegrationEventHandler integrationEventHandlerImplementation) + { + _integrationEventHandlerImplementation = integrationEventHandlerImplementation; + } + + public void Handle(TestIntegrationEvent eventData) + { + _integrationEventHandlerImplementation.Handle(eventData); + eventData.TypedProcessed.Set(); + } + } +} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs b/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs deleted file mode 100644 index 2d189cd6..00000000 --- a/tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedMessageHandler.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Backend.Fx.Features.MessageBus; - -namespace Backend.Fx.Tests.Patterns.EventAggregation.Integration -{ - public class TypedMessageHandler : IIntegrationMessageHandler - { - private readonly IIntegrationMessageHandler _integrationMessageHandlerImplementation; - - public TypedMessageHandler(IIntegrationMessageHandler integrationMessageHandlerImplementation) - { - _integrationMessageHandlerImplementation = integrationMessageHandlerImplementation; - } - - public void Handle(TestIntegrationEvent eventData) - { - _integrationMessageHandlerImplementation.Handle(eventData); - eventData.TypedProcessed.Set(); - } - } -} \ No newline at end of file diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs index a7530d22..662a737c 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheHiLoIdGenerator.cs @@ -1,5 +1,6 @@ using System.Linq; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.InMemoryPersistence; using Backend.Fx.TestUtil; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs index d916b033..f3ea3349 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceHiLoIdGenerator.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Features.Persistence; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs index 635e389a..31ee3b8d 100644 --- a/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs +++ b/tests/Backend.Fx.Tests/Patterns/IdGeneration/TheSequenceIdGenerator.cs @@ -1,5 +1,6 @@ using System.Linq; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.InMemoryPersistence; using Backend.Fx.TestUtil; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs index a3a3037d..26aa8bf1 100644 --- a/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs +++ b/tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs @@ -3,10 +3,11 @@ using System.Threading; using System.Threading.Tasks; using Backend.Fx.Environment.MultiTenancy; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.Features.Jobs; using Backend.Fx.Logging; -using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.TestUtil; +using Backend.Fx.Util; using FakeItEasy; using FluentScheduler; using Microsoft.Extensions.DependencyInjection; diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs b/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs index 261aabc0..906db408 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs @@ -1,7 +1,7 @@ using System; using System.Data; -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Extensions.Persistence; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs b/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs index 947d4d14..505a2013 100644 --- a/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs +++ b/tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs @@ -1,5 +1,4 @@ -using Backend.Fx.Features.Persistence; -using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.ExecutionPipeline; using Backend.Fx.TestUtil; using FakeItEasy; using Xunit; diff --git a/tests/SampleApp.Domain/Blog.cs b/tests/SampleApp.Domain/Blog.cs index b6214e41..785067a3 100644 --- a/tests/SampleApp.Domain/Blog.cs +++ b/tests/SampleApp.Domain/Blog.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; -using Backend.Fx.Features.Persistence; +using Backend.Fx.Domain; using JetBrains.Annotations; namespace SampleApp.Domain diff --git a/tests/SampleApp.Domain/BlogCreatedHandler.cs b/tests/SampleApp.Domain/BlogCreatedHandler.cs index ff9a2631..267db532 100644 --- a/tests/SampleApp.Domain/BlogCreatedHandler.cs +++ b/tests/SampleApp.Domain/BlogCreatedHandler.cs @@ -1,4 +1,3 @@ -using Backend.Fx.BuildingBlocks; using Backend.Fx.Features.DomainEvents; using Xunit; diff --git a/tests/SampleApp.Domain/Blogger.cs b/tests/SampleApp.Domain/Blogger.cs index b57e7ed6..85f707e2 100644 --- a/tests/SampleApp.Domain/Blogger.cs +++ b/tests/SampleApp.Domain/Blogger.cs @@ -1,5 +1,4 @@ -using Backend.Fx.BuildingBlocks; -using JetBrains.Annotations; +using JetBrains.Annotations; namespace SampleApp.Domain { diff --git a/tests/SampleApp.Domain/Post.cs b/tests/SampleApp.Domain/Post.cs index b7df2582..6e268a17 100644 --- a/tests/SampleApp.Domain/Post.cs +++ b/tests/SampleApp.Domain/Post.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Backend.Fx.BuildingBlocks; using JetBrains.Annotations; namespace SampleApp.Domain From be86e09247a626faad58fa1734804ad1a6fbdc9a Mon Sep 17 00:00:00 2001 From: Marc Wittke Date: Wed, 28 Sep 2022 13:16:51 -0300 Subject: [PATCH 31/51] tests(BackendFx): checkpoint --- Backend.Fx.sln | 10 - Backend.Fx.sln.DotSettings | 1 + src/abstractions/Backend.Fx/Backend.Fx.csproj | 29 +- .../Backend.Fx/Backend.Fx.csproj.DotSettings | 10 +- .../Backend.Fx/BackendFxApplication.cs | 73 ++- .../DependencyInjection/CompositionRoot.cs | 21 - .../LogRegistrationsDecorator.cs | 88 ++++ .../ServiceDescriptorEx.cs | 1 - .../Backend.Fx/Domain/AggregateRoot.cs | 17 - src/abstractions/Backend.Fx/Domain/Entity.cs | 18 - .../Backend.Fx/Domain/IAggregateRoot.cs | 12 + .../Domain/IAsyncAggregateQueryable.cs | 8 - .../Backend.Fx/Domain/IRepository.cs | 83 --- src/abstractions/Backend.Fx/Domain/IView.cs | 41 -- .../Backend.Fx/Domain/Identified.cs | 48 +- .../Backend.Fx/Domain/ValueObject.cs | 52 +- .../Backend.Fx/Exceptions/ClientException.cs | 2 +- .../Backend.Fx/Exceptions/ExceptionBuilder.cs | 2 +- .../ExecutionPipeline/AnonymousIdentity.cs | 19 +- .../BackendFxApplicationInvoker.cs | 45 +- .../CurrentCorrelationHolder.cs | 1 - .../CurrentIdentityHolder.cs | 11 +- .../ExceptionLoggingAndHandlingInvoker.cs | 2 +- .../ExecutionPipelineModule.cs | 1 - .../ExecutionPipeline/FrozenClock.cs | 2 +- .../Backend.Fx/ExecutionPipeline/Operation.cs | 7 +- .../ExecutionPipeline/SystemIdentity.cs | 19 +- .../BackendFxApplicationExtension.cs | 68 --- .../DataGenerationApplication.cs | 59 --- .../DataGeneration/DataGenerationModule.cs | 26 - .../Extensions/Persistence/ICanFlush.cs | 7 - .../Persistence/PersistentApplication.cs | 56 -- .../Features/Authorization/AllowAll.cs | 4 +- .../Authorization/AuthorizationFeature.cs | 30 +- .../Authorization/AuthorizationModule.cs | 63 ++- .../Authorization/AuthorizationPolicy.cs | 11 +- .../Authorization/AuthorizingQueryable.cs | 41 +- .../Authorization/AuthorizingRepository.cs | 23 +- .../Features/Authorization/DenyAll.cs | 4 +- .../Authorization/IAuthorizationPolicy.cs | 5 +- .../ConfigurationSettingsFeature.cs | 37 +- .../ConfigurationSettingsModule.cs | 12 +- .../InMem/InMemorySettingRepository.cs | 28 + .../LocalDateTimeSerializer.cs | 23 + .../DataGeneration/BackendFxApplicationEx.cs | 16 + .../DataGeneration/DataGenerationFeature.cs | 41 ++ .../DataGeneration/DataGenerationModule.cs | 44 ++ .../DataGeneration/DataGenerator.cs | 18 +- .../ForEachTenantDataGenerationContext.cs | 58 ++ .../DataGeneration/IDataGenerationContext.cs | 50 ++ .../DataGeneration/IDemoDataGenerator.cs | 2 +- .../IProductiveDataGenerator.cs | 2 +- .../DomainEvents/DomainEventAggregator.cs | 4 +- .../DomainEvents/DomainEventsFeature.cs | 24 +- .../DomainEvents/DomainEventsModule.cs | 4 +- .../DomainEvents/IDomainEventAggregator.cs | 6 +- .../DomainServices/DomainServicesFeature.cs | 21 +- .../Backend.Fx/Features/Feature.cs | 27 + .../Backend.Fx/Features/IBootableFeature.cs | 13 + .../Features/IMultiTenancyFeature.cs | 10 + .../IdGeneration}/HiLoIdGenerator.cs | 5 +- .../IdGeneration}/IEntityIdGenerator.cs | 2 +- .../IdGeneration}/IIdGenerator.cs | 2 +- .../IdGeneration}/ISequence.cs | 2 +- .../IdGeneration/IdGenerationFeature.cs | 37 ++ .../IdGeneration/InMem/InMemorySequence.cs | 27 + .../IdGeneration}/SequenceHiLoIdGenerator.cs | 2 +- .../IdGeneration}/SequenceIdGenerator.cs | 4 +- .../Features/Jobs/ApplicationWithJobs.cs | 40 -- .../Features/Jobs/BackendFxApplicationEx.cs | 16 + .../Features/Jobs/ForEachTenantJobExecutor.cs | 35 ++ .../Backend.Fx/Features/Jobs/IJob.cs | 5 +- .../Backend.Fx/Features/Jobs/IJobExecutor.cs | 36 ++ .../Backend.Fx/Features/Jobs/JobsFeature.cs | 21 + .../Jobs/{JobModule.cs => JobsModule.cs} | 16 +- .../MessageBus/IIntegrationEventHandler.cs | 2 +- .../MessageBus/InProc/InProcMessageBus.cs | 4 +- .../InProc/InProcMessageBusChannel.cs | 4 +- .../MessageBus/IntegrationEvent.cs | 4 +- .../IntegrationEventHandlingInvoker.cs | 4 +- .../MessageBus/IntegrationEventSerializer.cs | 2 +- .../MessageBus/MessageBus.cs | 2 +- .../MessageBus/MessageBusFeature.cs} | 36 +- .../MessageBus/MessageBusModule.cs | 22 +- .../MessageBus/MessageBusScope.cs | 4 +- .../MultiTenantIntegrationEventSerializer.cs | 2 +- ...IntegrationEventsWhenOperationCompleted.cs | 2 +- .../MessageBus/SerializedMessage.cs | 2 +- .../MultiTenancy/CurrentTenantIdHolder.cs | 17 +- .../MultiTenancy/ForEachTenantIdInvoker.cs | 52 ++ .../MultiTenancy/ITenantEnumerator.cs | 11 + .../InProcTenantWideMutexManager.cs} | 21 +- .../MultiTenancy/MultiTenancyFeature.cs | 26 - .../MultiTenancy/MultiTenancyModule.cs | 31 +- .../MultiTenancy/TenantFilteredQueryable.cs | 37 -- .../Features/MultiTenancy/TenantId.cs | 46 +- .../DirectTenantEnumerator.cs | 30 ++ .../MultiTenancyAdmin}/ITenantRepository.cs | 4 +- .../InMem/InMemoryTenantRepository.cs | 43 ++ .../MultiTenancyAdminFeature.cs | 40 ++ .../MultiTenancyAdminModule.cs | 21 + .../Features/MultiTenancyAdmin}/Tenant.cs | 15 +- .../MultiTenancyAdmin}/TenantService.cs | 4 +- .../AdoNet/AdoNetPersistenceModule.cs | 39 ++ .../AdoNet}/DbConnectionOperationDecorator.cs | 33 +- .../DbTransactionOperationDecorator.cs | 4 +- .../AdoNet}/IDbConnectionFactory.cs | 2 +- .../Persistence/AdoNet/MsSqlSequence.cs | 66 +++ .../Persistence/AdoNet/OracleSequence.cs | 84 +++ .../Persistence/AdoNet/PostgresSequence.cs | 72 +++ .../Persistence/AggregateQueryable.cs | 43 ++ .../FlushDomainEventAggregatorDecorator.cs | 15 +- .../Persistence/FlushOperationDecorator.cs | 2 +- .../Features/Persistence/ICanFlush.cs | 16 + .../IDatabaseAvailabilityAwaiter.cs | 2 +- .../Persistence/IDatabaseBootstrapper.cs | 6 +- .../Features/Persistence/IRepository.cs | 67 +++ .../ITenantFilterExpressionFactory.cs | 6 +- .../InMem/AggregateDictionaries.cs | 27 + .../Persistence/InMem/AggregateDictionary.cs | 89 ++++ .../InMem/InMemoryAggregateQueryable.cs | 38 ++ .../Persistence/InMem/InMemoryDatabase.cs | 20 + .../InMem/InMemoryDatabaseAccessor.cs | 42 ++ .../InMem/InMemoryPersistenceModule.cs | 43 ++ .../Persistence/InMem/InMemoryRepository.cs | 93 ++++ .../Persistence/PersistenceFeature.cs | 54 ++ .../Features/Persistence/PersistenceModule.cs | 30 ++ .../Backend.Fx/Logging/DurationLogger.cs | 2 +- src/abstractions/Backend.Fx/Logging/Log.cs | 4 +- .../MultiTenancyBackendFxApplication.cs | 54 ++ .../Backend.Fx/Util/AsyncHelper.cs | 4 +- .../Backend.Fx/Util/CurrentTHolder.cs | 4 +- .../Backend.Fx/Util/GenericsHelper.cs | 79 +++ .../MicrosoftCompositionRoot.cs | 21 +- .../Backend.Fx.RabbitMq/RabbitMqChannel.cs | 2 +- .../Backend.Fx.RabbitMq/RabbitMqMessageBus.cs | 2 +- .../SimpleInjectorCompositionRoot.cs | 20 - .../Backend.Fx.Tests/Backend.Fx.Tests.csproj | 19 +- tests/Backend.Fx.Tests/Backup.bak | Bin 2867200 -> 0 bytes .../BuildingBlocks/TheAggregateRoot.cs | 140 ----- .../BuildingBlocks/TheRepository.cs | 497 ------------------ .../ConfigurationSettings/TheSetting.cs | 132 ----- .../TheSettingSerializerFactory.cs | 76 --- .../TheSettingsService.cs | 96 ---- .../DependencyInjection/TheCompositionRoot.cs | 62 +++ .../Backend.Fx.Tests/Domain/TheIdentified.cs | 112 ++++ .../TheValueObject.cs | 20 +- .../DummyServices/DummyAggregate.cs | 17 + .../DummyServices/DummyApplicationService.cs | 21 + .../DummyServices/DummyAuthorizationPolicy.cs | 38 ++ .../DummyServices/DummyDemoDataGenerator.cs | 49 ++ .../DummyServices/DummyDomainEventHandler.cs | 25 + .../DummyServices/DummyDomainService.cs | 21 + .../DummyServices/DummyJob.cs | 27 + .../DummyProductiveDataGenerator.cs | 49 ++ .../DummyServices/DummyServicesModule.cs | 63 +++ .../Authentication/TheAnonymousIdentity.cs | 32 -- .../TheCurrentIdentityHolder.cs | 37 -- .../Authentication/TheSystemIdentity.cs | 32 -- .../DateAndTime/TheAdjustableClock.cs | 29 - .../Environment/DateAndTime/TheFrozenClock.cs | 27 - ...TheAllTenantBackendFxApplicationInvoker.cs | 48 -- .../MultiTenancy/TheCurrentTenantIdHolder.cs | 29 - .../TheSingleTenantApplication.cs | 85 --- .../Environment/MultiTenancy/TheTenant.cs | 33 -- .../Environment/MultiTenancy/TheTenantId.cs | 29 - .../MultiTenancy/TheTenantService.cs | 72 --- .../Persistence/ThePersistentApplication.cs | 73 --- .../Exceptions/TheClientException.cs | 92 ++++ .../Exceptions/TheConflictedException.cs | 16 + .../Exceptions/TheForbiddenException.cs | 16 + .../Exceptions/TheNotFoundException.cs | 25 +- .../Exceptions/TheTooManyRequestsException.cs | 23 + .../Exceptions/TheUnauthorizedException.cs | 16 + .../Exceptions/TheUnprocessableException.cs | 16 + .../TheUnprocessableExceptionBuilder.cs | 32 +- .../ExecutionPipeline/OperationSpy.cs | 38 ++ .../ExecutionPipeline/TheAnonymousIdentity.cs | 53 ++ .../ExecutionPipeline/TheCorrelation.cs | 29 + .../TheCurrentIdentityHolder.cs | 35 ++ .../TheExceptionLoggingAndHandlingInvoker.cs | 34 ++ .../ExecutionPipeline/TheFrozenClock.cs | 23 + .../ExecutionPipeline/TheSystemIdentity.cs | 53 ++ .../Extensions/TheDateTimeEx.cs | 45 -- .../Extensions/TheEnumerableEx.cs | 30 -- .../Extensions/TheStringEnumUtil.cs | 45 -- .../Authorization/TheAllowAllPolicy.cs | 53 ++ .../Authorization/TheAuthorizationFeature.cs | 232 ++++++++ .../Authorization/TheDenyAllPolicy.cs | 53 ++ .../TheConfigurationSettingsFeature.cs | 128 +++++ .../TheDataGenerationFeature.cs | 89 ++++ ...heDataGenerationFeatureWithMultiTenancy.cs | 104 ++++ .../DomainEvents/TheDomainEventsFeature.cs | 71 +++ .../DomainServices/TheDomainServiceFeature.cs | 59 +++ .../IdGeneration/TheHiLoIdGenerator.cs | 8 +- .../IdGeneration/TheIdGenerationFeature.cs | 41 ++ .../TheSequenceHiLoIdGenerator.cs | 5 +- .../IdGeneration/TheSequenceIdGenerator.cs | 8 +- .../Features/Jobs/TheJobsFeature.cs | 43 ++ .../Jobs/TheJobsFeatureWithMultiTenancy.cs | 65 +++ .../MultiTenancy/TheCurrentTenantIdHolder.cs | 33 ++ .../TheMultiTenancyApplication.cs | 54 ++ .../Features/MultiTenancy/TheTenantId.cs | 55 ++ .../TheMultiTenancyAdminFeature.cs | 53 ++ .../MultiTenancyAdmin/TheTenantService.cs | 108 ++++ .../Features/Persistence/CanFlushSpy.cs | 25 + .../Persistence/ThePersistenceFeature.cs | 120 +++++ .../ThePersistenceFeatureWithMultiTenancy.cs | 87 +++ .../TheAllowAllImplementation.cs | 44 -- .../TheAuthorizingApplication.cs | 49 -- .../Authorization/TheDenyAllImplementation.cs | 45 -- .../TheDataGenerationContext.cs | 129 ----- .../TheGenerateDataOnBootDecorator.cs | 87 --- .../DataGeneration/TheInitialDataGenerator.cs | 61 --- .../DependencyInjection/DITestFakes.cs | 43 -- .../DependencyInjection/SimulatedException.cs | 12 - .../TheBackendFxApplication.cs | 148 ------ .../TheBackendFxApplicationAsyncInvoker.cs | 100 ---- .../TheBackendFxApplicationInvoker.cs | 97 ---- .../DependencyInjection/TheCorrelation.cs | 32 -- .../DependencyInjection/TheCurrentTHolder.cs | 33 -- .../DependencyInjection/TheDomainModule.cs | 146 ----- .../DependencyInjection/TheOperation.cs | 107 ---- ...uentializingBackendFxApplicationInvoker.cs | 79 --- .../Domain/FailingDomainEventHandler.cs | 13 - .../Domain/TestDomainEvent.cs | 14 - .../Domain/TestDomainEventHandler.cs | 15 - .../Domain/TestIntegrationEvent.cs | 16 - .../Domain/TheEventAggregator.cs | 56 -- .../Integration/BlockingEventHandler.cs | 24 - .../Integration/DynamicEventHandler.cs | 19 - .../Integration/LongRunningEventHandler.cs | 21 - .../Integration/SerializingMessageBus.cs | 53 -- .../Integration/TestIntegrationEvent.cs | 21 - .../TheInMemoryMessageBusChannel.cs | 104 ---- .../Integration/TheMessageBus.cs | 192 ------- .../Integration/TheMessageBusApplication.cs | 34 -- .../Integration/TheMessageBusScope.cs | 67 --- .../ThrowingDynamicEventHandler.cs | 22 - .../Integration/ThrowingTypedEventHandler.cs | 22 - .../Integration/TypedEventHandler.cs | 20 - .../Patterns/Jobs/TheApplicationWithJobs.cs | 108 ---- .../TheDbTransactionOperationDecorator.cs | 204 ------- .../Transactions/TheReadonlyDecorator.cs | 39 -- .../RandomData/TheGenerators.cs | 77 --- tests/Backend.Fx.Tests/TestHelpers.cs | 4 - tests/Backend.Fx.Tests/TestWithLogging.cs | 37 ++ .../TheBackendFxApplication.cs | 269 ++++++++++ 248 files changed, 5107 insertions(+), 4944 deletions(-) create mode 100644 src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs delete mode 100644 src/abstractions/Backend.Fx/Domain/AggregateRoot.cs delete mode 100644 src/abstractions/Backend.Fx/Domain/Entity.cs create mode 100644 src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs delete mode 100644 src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs delete mode 100644 src/abstractions/Backend.Fx/Domain/IRepository.cs delete mode 100644 src/abstractions/Backend.Fx/Domain/IView.cs delete mode 100644 src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs delete mode 100644 src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs delete mode 100644 src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs delete mode 100644 src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs delete mode 100644 src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/InMem/InMemorySettingRepository.cs create mode 100644 src/abstractions/Backend.Fx/Features/ConfigurationSettings/LocalDateTimeSerializer.cs create mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/BackendFxApplicationEx.cs create mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs rename src/abstractions/Backend.Fx/{Extensions => Features}/DataGeneration/DataGenerator.cs (78%) create mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/ForEachTenantDataGenerationContext.cs create mode 100644 src/abstractions/Backend.Fx/Features/DataGeneration/IDataGenerationContext.cs rename src/abstractions/Backend.Fx/{Extensions => Features}/DataGeneration/IDemoDataGenerator.cs (79%) rename src/abstractions/Backend.Fx/{Extensions => Features}/DataGeneration/IProductiveDataGenerator.cs (79%) create mode 100644 src/abstractions/Backend.Fx/Features/Feature.cs create mode 100644 src/abstractions/Backend.Fx/Features/IBootableFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/IMultiTenancyFeature.cs rename src/abstractions/Backend.Fx/{Extensions/Persistence => Features/IdGeneration}/HiLoIdGenerator.cs (90%) rename src/abstractions/Backend.Fx/{Domain => Features/IdGeneration}/IEntityIdGenerator.cs (60%) rename src/abstractions/Backend.Fx/{Domain => Features/IdGeneration}/IIdGenerator.cs (60%) rename src/abstractions/Backend.Fx/{Extensions/Persistence => Features/IdGeneration}/ISequence.cs (80%) create mode 100644 src/abstractions/Backend.Fx/Features/IdGeneration/IdGenerationFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/IdGeneration/InMem/InMemorySequence.cs rename src/abstractions/Backend.Fx/{Extensions/Persistence => Features/IdGeneration}/SequenceHiLoIdGenerator.cs (89%) rename src/abstractions/Backend.Fx/{Extensions/Persistence => Features/IdGeneration}/SequenceIdGenerator.cs (81%) delete mode 100644 src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs create mode 100644 src/abstractions/Backend.Fx/Features/Jobs/BackendFxApplicationEx.cs create mode 100644 src/abstractions/Backend.Fx/Features/Jobs/ForEachTenantJobExecutor.cs create mode 100644 src/abstractions/Backend.Fx/Features/Jobs/IJobExecutor.cs create mode 100644 src/abstractions/Backend.Fx/Features/Jobs/JobsFeature.cs rename src/abstractions/Backend.Fx/Features/Jobs/{JobModule.cs => JobsModule.cs} (57%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/IIntegrationEventHandler.cs (76%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/InProc/InProcMessageBus.cs (94%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/InProc/InProcMessageBusChannel.cs (92%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/IntegrationEvent.cs (92%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/IntegrationEventHandlingInvoker.cs (92%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/IntegrationEventSerializer.cs (96%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/MessageBus.cs (98%) rename src/abstractions/Backend.Fx/{Extensions/MessageBus/MessageBusExtension.cs => Features/MessageBus/MessageBusFeature.cs} (55%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/MessageBusModule.cs (87%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/MessageBusScope.cs (93%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/MultiTenantIntegrationEventSerializer.cs (97%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs (95%) rename src/abstractions/Backend.Fx/{Extensions => Features}/MessageBus/SerializedMessage.cs (88%) create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/ForEachTenantIdInvoker.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantEnumerator.cs rename src/abstractions/Backend.Fx/Features/MultiTenancy/{InMemoryTenantWideMutexManager.cs => InProc/InProcTenantWideMutexManager.cs} (72%) delete mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs delete mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/DirectTenantEnumerator.cs rename src/{features/Backend.Fx.Features.MultiTenancy.Admin => abstractions/Backend.Fx/Features/MultiTenancyAdmin}/ITenantRepository.cs (69%) create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/InMem/InMemoryTenantRepository.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminModule.cs rename src/{features/Backend.Fx.Features.MultiTenancy.Admin => abstractions/Backend.Fx/Features/MultiTenancyAdmin}/Tenant.cs (68%) rename src/{features/Backend.Fx.Features.MultiTenancy.Admin => abstractions/Backend.Fx/Features/MultiTenancyAdmin}/TenantService.cs (96%) create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/AdoNet/AdoNetPersistenceModule.cs rename src/abstractions/Backend.Fx/{Extensions/Persistence => Features/Persistence/AdoNet}/DbConnectionOperationDecorator.cs (61%) rename src/abstractions/Backend.Fx/{Extensions/Persistence => Features/Persistence/AdoNet}/DbTransactionOperationDecorator.cs (97%) rename src/{implementations/Backend.Fx.EfCore5Persistence/Bootstrapping => abstractions/Backend.Fx/Features/Persistence/AdoNet}/IDbConnectionFactory.cs (67%) create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/AdoNet/MsSqlSequence.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/AdoNet/OracleSequence.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/AdoNet/PostgresSequence.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/AggregateQueryable.cs rename src/abstractions/Backend.Fx/{Extensions => Features}/Persistence/FlushDomainEventAggregatorDecorator.cs (57%) rename src/abstractions/Backend.Fx/{Extensions => Features}/Persistence/FlushOperationDecorator.cs (96%) create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs rename src/abstractions/Backend.Fx/{Extensions => Features}/Persistence/IDatabaseAvailabilityAwaiter.cs (80%) rename src/abstractions/Backend.Fx/{Extensions => Features}/Persistence/IDatabaseBootstrapper.cs (59%) create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/IRepository.cs rename src/abstractions/Backend.Fx/Features/{MultiTenancy => Persistence}/ITenantFilterExpressionFactory.cs (57%) create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionaries.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionary.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryAggregateQueryable.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabase.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabaseAccessor.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryPersistenceModule.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryRepository.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/PersistenceFeature.cs create mode 100644 src/abstractions/Backend.Fx/Features/Persistence/PersistenceModule.cs create mode 100644 src/abstractions/Backend.Fx/MultiTenancyBackendFxApplication.cs create mode 100644 src/abstractions/Backend.Fx/Util/GenericsHelper.cs delete mode 100644 tests/Backend.Fx.Tests/Backup.bak delete mode 100644 tests/Backend.Fx.Tests/BuildingBlocks/TheAggregateRoot.cs delete mode 100644 tests/Backend.Fx.Tests/BuildingBlocks/TheRepository.cs delete mode 100644 tests/Backend.Fx.Tests/ConfigurationSettings/TheSetting.cs delete mode 100644 tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingSerializerFactory.cs delete mode 100644 tests/Backend.Fx.Tests/ConfigurationSettings/TheSettingsService.cs create mode 100644 tests/Backend.Fx.Tests/DependencyInjection/TheCompositionRoot.cs create mode 100644 tests/Backend.Fx.Tests/Domain/TheIdentified.cs rename tests/Backend.Fx.Tests/{BuildingBlocks => Domain}/TheValueObject.cs (86%) create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyAggregate.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyApplicationService.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyAuthorizationPolicy.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyDemoDataGenerator.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyDomainEventHandler.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyDomainService.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyJob.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyProductiveDataGenerator.cs create mode 100644 tests/Backend.Fx.Tests/DummyServices/DummyServicesModule.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/Authentication/TheAnonymousIdentity.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/Authentication/TheCurrentIdentityHolder.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/Authentication/TheSystemIdentity.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/DateAndTime/TheAdjustableClock.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/DateAndTime/TheFrozenClock.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/MultiTenancy/TheAllTenantBackendFxApplicationInvoker.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/MultiTenancy/TheCurrentTenantIdHolder.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/MultiTenancy/TheSingleTenantApplication.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenant.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantId.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/MultiTenancy/TheTenantService.cs delete mode 100644 tests/Backend.Fx.Tests/Environment/Persistence/ThePersistentApplication.cs create mode 100644 tests/Backend.Fx.Tests/Exceptions/TheClientException.cs create mode 100644 tests/Backend.Fx.Tests/Exceptions/TheConflictedException.cs create mode 100644 tests/Backend.Fx.Tests/Exceptions/TheForbiddenException.cs create mode 100644 tests/Backend.Fx.Tests/Exceptions/TheTooManyRequestsException.cs create mode 100644 tests/Backend.Fx.Tests/Exceptions/TheUnauthorizedException.cs create mode 100644 tests/Backend.Fx.Tests/Exceptions/TheUnprocessableException.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/OperationSpy.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/TheAnonymousIdentity.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/TheCorrelation.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/TheCurrentIdentityHolder.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/TheExceptionLoggingAndHandlingInvoker.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/TheFrozenClock.cs create mode 100644 tests/Backend.Fx.Tests/ExecutionPipeline/TheSystemIdentity.cs delete mode 100644 tests/Backend.Fx.Tests/Extensions/TheDateTimeEx.cs delete mode 100644 tests/Backend.Fx.Tests/Extensions/TheEnumerableEx.cs delete mode 100644 tests/Backend.Fx.Tests/Extensions/TheStringEnumUtil.cs create mode 100644 tests/Backend.Fx.Tests/Features/Authorization/TheAllowAllPolicy.cs create mode 100644 tests/Backend.Fx.Tests/Features/Authorization/TheAuthorizationFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/Authorization/TheDenyAllPolicy.cs create mode 100644 tests/Backend.Fx.Tests/Features/ConfigurationSettings/TheConfigurationSettingsFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/DataGeneration/TheDataGenerationFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/DataGeneration/TheDataGenerationFeatureWithMultiTenancy.cs create mode 100644 tests/Backend.Fx.Tests/Features/DomainEvents/TheDomainEventsFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/DomainServices/TheDomainServiceFeature.cs rename tests/Backend.Fx.Tests/{Patterns => Features}/IdGeneration/TheHiLoIdGenerator.cs (89%) create mode 100644 tests/Backend.Fx.Tests/Features/IdGeneration/TheIdGenerationFeature.cs rename tests/Backend.Fx.Tests/{Patterns => Features}/IdGeneration/TheSequenceHiLoIdGenerator.cs (89%) rename tests/Backend.Fx.Tests/{Patterns => Features}/IdGeneration/TheSequenceIdGenerator.cs (89%) create mode 100644 tests/Backend.Fx.Tests/Features/Jobs/TheJobsFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/Jobs/TheJobsFeatureWithMultiTenancy.cs create mode 100644 tests/Backend.Fx.Tests/Features/MultiTenancy/TheCurrentTenantIdHolder.cs create mode 100644 tests/Backend.Fx.Tests/Features/MultiTenancy/TheMultiTenancyApplication.cs create mode 100644 tests/Backend.Fx.Tests/Features/MultiTenancy/TheTenantId.cs create mode 100644 tests/Backend.Fx.Tests/Features/MultiTenancyAdmin/TheMultiTenancyAdminFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/MultiTenancyAdmin/TheTenantService.cs create mode 100644 tests/Backend.Fx.Tests/Features/Persistence/CanFlushSpy.cs create mode 100644 tests/Backend.Fx.Tests/Features/Persistence/ThePersistenceFeature.cs create mode 100644 tests/Backend.Fx.Tests/Features/Persistence/ThePersistenceFeatureWithMultiTenancy.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/Authorization/TheAllowAllImplementation.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/Authorization/TheAuthorizingApplication.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/Authorization/TheDenyAllImplementation.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DataGeneration/TheDataGenerationContext.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DataGeneration/TheGenerateDataOnBootDecorator.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DataGeneration/TheInitialDataGenerator.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/DITestFakes.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/SimulatedException.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplication.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationAsyncInvoker.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheBackendFxApplicationInvoker.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCorrelation.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheCurrentTHolder.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheDomainModule.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheOperation.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/DependencyInjection/TheSequentializingBackendFxApplicationInvoker.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/FailingDomainEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEvent.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestDomainEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TestIntegrationEvent.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Domain/TheEventAggregator.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/BlockingEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/DynamicEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/LongRunningEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/SerializingMessageBus.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TestIntegrationEvent.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheInMemoryMessageBusChannel.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBus.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusApplication.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TheMessageBusScope.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingDynamicEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/ThrowingTypedEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/EventAggregation/Integration/TypedEventHandler.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/Jobs/TheApplicationWithJobs.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/Transactions/TheDbTransactionOperationDecorator.cs delete mode 100644 tests/Backend.Fx.Tests/Patterns/Transactions/TheReadonlyDecorator.cs delete mode 100644 tests/Backend.Fx.Tests/RandomData/TheGenerators.cs delete mode 100644 tests/Backend.Fx.Tests/TestHelpers.cs create mode 100644 tests/Backend.Fx.Tests/TestWithLogging.cs create mode 100644 tests/Backend.Fx.Tests/TheBackendFxApplication.cs diff --git a/Backend.Fx.sln b/Backend.Fx.sln index 6ca1ad95..07533205 100644 --- a/Backend.Fx.sln +++ b/Backend.Fx.sln @@ -62,10 +62,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.TestUtil", "test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Domain", "tests\SampleApp.Domain\SampleApp.Domain.csproj", "{ADCBD99B-0C75-484C-9C7F-6E174455B3AE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "features", "features", "{060355A0-F0E1-41EF-9F71-CDE5021CDC3A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend.Fx.Features.MultiTenancy.Admin", "src\features\Backend.Fx.Features.MultiTenancy.Admin\Backend.Fx.Features.MultiTenancy.Admin.csproj", "{FF5F8A10-A439-4859-BE78-C2764EDF6462}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,10 +131,6 @@ Global {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {ADCBD99B-0C75-484C-9C7F-6E174455B3AE}.Release|Any CPU.Build.0 = Release|Any CPU - {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FF5F8A10-A439-4859-BE78-C2764EDF6462}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -167,8 +159,6 @@ Global {B4791DB0-F8DD-4248-86CB-407E46F55B13} = {22E4DE95-C3E5-49E6-83BF-BF30905A746B} {3AD4F223-DC1D-40B7-9DB9-DC88FDD0178D} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} {ADCBD99B-0C75-484C-9C7F-6E174455B3AE} = {C7885592-A4B8-4BA8-8D3A-1EDA4025D17A} - {060355A0-F0E1-41EF-9F71-CDE5021CDC3A} = {53D4501E-953C-4A7C-97C4-1F9DE04BD092} - {FF5F8A10-A439-4859-BE78-C2764EDF6462} = {060355A0-F0E1-41EF-9F71-CDE5021CDC3A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {45648557-C751-44AD-9C87-0F12EB673969} diff --git a/Backend.Fx.sln.DotSettings b/Backend.Fx.sln.DotSettings index afc9e37f..8177320f 100644 --- a/Backend.Fx.sln.DotSettings +++ b/Backend.Fx.sln.DotSettings @@ -1,4 +1,5 @@  + DI True True True diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj b/src/abstractions/Backend.Fx/Backend.Fx.csproj index f6939a4e..3d7cd83d 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj @@ -8,7 +8,7 @@ false false false - 8 + 9 @@ -24,33 +24,16 @@ https://github.com/marcwittke/Backend.Fx.git - - - - - - + - - + + + - - - - - - - - - - ..\..\..\..\..\.nuget\packages\system.memory\4.5.4\lib\netstandard2.0\System.Memory.dll - - - ..\..\..\..\..\.nuget\packages\system.text.json\4.7.2\lib\netstandard2.0\System.Text.Json.dll - + \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings b/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings index 89316e41..c2f4bbdb 100644 --- a/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings +++ b/src/abstractions/Backend.Fx/Backend.Fx.csproj.DotSettings @@ -1,2 +1,8 @@ - - Library \ No newline at end of file + + Library + \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/BackendFxApplication.cs b/src/abstractions/Backend.Fx/BackendFxApplication.cs index 123ca65b..6fc4687d 100644 --- a/src/abstractions/Backend.Fx/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/BackendFxApplication.cs @@ -1,13 +1,15 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Backend.Fx.DependencyInjection; using Backend.Fx.ExecutionPipeline; -using Backend.Fx.Extensions; +using Backend.Fx.Features; using Backend.Fx.Logging; using Backend.Fx.Util; +using JetBrains.Annotations; using Microsoft.Extensions.Logging; namespace Backend.Fx @@ -15,6 +17,7 @@ namespace Backend.Fx /// /// The root object of the whole backend fx application framework /// + [PublicAPI] public interface IBackendFxApplication : IDisposable { /// @@ -45,15 +48,31 @@ public interface IBackendFxApplication : IDisposable /// Task BootAsync(CancellationToken cancellationToken = default); - TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationExtension; + /// + /// Enables an optional feature. Must be done before calling . + /// + /// + void EnableFeature(Feature feature); + + void RequireDependantFeature() where TFeature : Feature; } public class BackendFxApplication : IBackendFxApplication { private static readonly ILogger Logger = Log.Create(); - private readonly ManualResetEventSlim _isBooted = new ManualResetEventSlim(false); + private readonly ManualResetEventSlim _isBooted = new(false); + private readonly List _features = new(); + /// + /// Initializes the application's runtime instance + /// + /// The composition root of the dependency injection framework + /// + public BackendFxApplication(ICompositionRoot compositionRoot, params Assembly[] assemblies) + : this(compositionRoot, new ExceptionLogger(Logger), assemblies) + { } + /// /// Initializes the application's runtime instance /// @@ -62,7 +81,7 @@ public class BackendFxApplication : IBackendFxApplication /// public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger exceptionLogger, params Assembly[] assemblies) { - assemblies = assemblies ?? Array.Empty(); + assemblies ??= Array.Empty(); Logger.LogInformation( "Initializing application with {CompositionRoot} providing services from [{Assemblies}]", @@ -73,7 +92,7 @@ public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger e Invoker = new ExceptionLoggingInvoker(exceptionLogger, invoker); - CompositionRoot = compositionRoot; + CompositionRoot = new LogRegistrationsDecorator(compositionRoot); ExceptionLogger = exceptionLogger; Assemblies = assemblies; CompositionRoot.RegisterModules(new ExecutionPipelineModule(withFrozenClockDuringExecution: true)); @@ -87,20 +106,39 @@ public BackendFxApplication(ICompositionRoot compositionRoot, IExceptionLogger e public IExceptionLogger ExceptionLogger { get; } - public bool IsMultiTenancyApplication { get; set; } + public virtual void EnableFeature(Feature feature) + { + if (_isBooted.IsSet) + { + throw new InvalidOperationException("Features must be enabled before booting the application"); + } + + feature.Enable(this); + _features.Add(feature); + } - public Task BootAsync(CancellationToken cancellationToken = default) + public void RequireDependantFeature() where TFeature : Feature { - Logger.LogInformation("Booting application"); - CompositionRoot.Verify(); - _isBooted.Set(); - return Task.CompletedTask; + if (!_features.OfType().Any()) + { + throw new InvalidOperationException($"This feature requires the {typeof(TFeature).Name} to be enabled first"); + } } - public virtual TBackendFxApplicationDecorator As() - where TBackendFxApplicationDecorator : BackendFxApplicationExtension + public async Task BootAsync(CancellationToken cancellationToken = default) { - return null; + Logger.LogInformation("Booting application"); + CompositionRoot.Verify(); + + foreach (Feature feature in _features) + { + if (feature is IBootableFeature bootableFeature) + { + await bootableFeature.BootAsync(this, cancellationToken).ConfigureAwait(false); + } + } + + _isBooted.Set(); } public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToken cancellationToken = default) @@ -111,8 +149,11 @@ public bool WaitForBoot(int timeoutMilliSeconds = int.MaxValue, CancellationToke public void Dispose() { Logger.LogInformation("Application shut down initialized"); + foreach (var feature in _features) + { + feature.Dispose(); + } CompositionRoot?.Dispose(); - GC.SuppressFinalize(this); } } -} \ No newline at end of file +} diff --git a/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs b/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs index 556f9040..8178f8cf 100644 --- a/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs +++ b/src/abstractions/Backend.Fx/DependencyInjection/CompositionRoot.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Backend.Fx.Logging; -using Backend.Fx.Util; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -42,8 +41,6 @@ public abstract class CompositionRoot : ICompositionRoot public abstract IServiceProvider ServiceProvider { get; } - public abstract bool HasRegistration(); - public abstract void Verify(); public virtual void RegisterModules(params IModule[] modules) @@ -65,24 +62,6 @@ public virtual void RegisterModules(params IModule[] modules) protected abstract void Dispose(bool disposing); - protected static void LogAddRegistration(ServiceDescriptor serviceDescriptor) - => LogDetails("Adding", serviceDescriptor); - - protected static void LogReplaceRegistration(ServiceDescriptor serviceDescriptor) - => LogDetails("Replacing", serviceDescriptor); - - protected static void LogAddDecoratorRegistration(ServiceDescriptor serviceDescriptor) - => LogDetails("Adding decorator", serviceDescriptor); - - private static void LogDetails(string prefix, ServiceDescriptor serviceDescriptor) - { - Logger.LogDebug("{Prefix} {Lifetime} registration for {ServiceType}: {ImplementationType}", - prefix, - serviceDescriptor.Lifetime.ToString(), - serviceDescriptor.ServiceType.GetDetailedTypeName(), - serviceDescriptor.GetImplementationTypeDescription()); - } - public void Dispose() { Dispose(true); diff --git a/src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs b/src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs new file mode 100644 index 00000000..5de6427f --- /dev/null +++ b/src/abstractions/Backend.Fx/DependencyInjection/LogRegistrationsDecorator.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Backend.Fx.Logging; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.DependencyInjection +{ + public class LogRegistrationsDecorator : ICompositionRoot + { + private static readonly ILogger Logger = Log.Create(); + private readonly ICompositionRoot _compositionRoot; + + public LogRegistrationsDecorator(ICompositionRoot compositionRoot) + { + _compositionRoot = compositionRoot; + } + + public void Dispose() + { + _compositionRoot.Dispose(); + } + + public void Verify() + { + _compositionRoot.Verify(); + } + + public void RegisterModules(params IModule[] modules) + { + _compositionRoot.RegisterModules(modules); + } + + public void Register(ServiceDescriptor serviceDescriptor) + { + LogDetails("Adding", "registration", serviceDescriptor); + _compositionRoot.Register(serviceDescriptor); + } + + public void RegisterDecorator(ServiceDescriptor serviceDescriptor) + { + LogDetails("Adding", "decorator", serviceDescriptor); + _compositionRoot.RegisterDecorator(serviceDescriptor); + } + + public void RegisterCollection(IEnumerable serviceDescriptors) + { + serviceDescriptors = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); + LogAddCollectionRegistration(serviceDescriptors); + if (serviceDescriptors.GroupBy(sd => sd.ServiceType).Count() > 1) + { + Logger.LogError("Attempt to register a collection of services for different service types"); + } + _compositionRoot.RegisterCollection(serviceDescriptors); + } + + private static void LogAddCollectionRegistration(IEnumerable serviceDescriptors) + { + serviceDescriptors = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); + Logger.LogDebug("{Verb} {Lifetime} {RegistrationType} for {ServiceType}: {ImplementationType}", + "Adding", + serviceDescriptors.First().Lifetime.ToString().ToLowerInvariant(), + "collection registration", + serviceDescriptors.First().ServiceType.GetDetailedTypeName(), + string.Join(", ", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))); + } + + public IServiceScope BeginScope() + { + return _compositionRoot.BeginScope(); + } + + public IServiceProvider ServiceProvider => _compositionRoot.ServiceProvider; + + + private static void LogDetails(string verb, string registrationType, ServiceDescriptor serviceDescriptor) + { + Logger.LogDebug("{Verb} {Lifetime} {RegistrationType} for {ServiceType}: {ImplementationType}", + verb, + serviceDescriptor.Lifetime.ToString().ToLowerInvariant(), + registrationType, + serviceDescriptor.ServiceType.GetDetailedTypeName(), + serviceDescriptor.GetImplementationTypeDescription()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs b/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs index 87f6a4c0..cfa36738 100644 --- a/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs +++ b/src/abstractions/Backend.Fx/DependencyInjection/ServiceDescriptorEx.cs @@ -1,7 +1,6 @@ using Backend.Fx.Util; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace Backend.Fx.DependencyInjection { diff --git a/src/abstractions/Backend.Fx/Domain/AggregateRoot.cs b/src/abstractions/Backend.Fx/Domain/AggregateRoot.cs deleted file mode 100644 index 17006bbf..00000000 --- a/src/abstractions/Backend.Fx/Domain/AggregateRoot.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Backend.Fx.Domain -{ - /// - /// A collection of objects that are bound together by a root entity - /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks - /// - public abstract class AggregateRoot : Entity - { - protected AggregateRoot() - { - } - - protected AggregateRoot(int id) : base(id) - { - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/Entity.cs b/src/abstractions/Backend.Fx/Domain/Entity.cs deleted file mode 100644 index ceeadb4a..00000000 --- a/src/abstractions/Backend.Fx/Domain/Entity.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Backend.Fx.Domain -{ - /// - /// An object that is not defined by its attributes, but rather by a thread of continuity and its identity. - /// https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks - /// - public abstract class Entity : Identified - { - protected Entity() - { - } - - protected Entity(int id) - { - Id = id; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs b/src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs new file mode 100644 index 00000000..e633ac4a --- /dev/null +++ b/src/abstractions/Backend.Fx/Domain/IAggregateRoot.cs @@ -0,0 +1,12 @@ +using System; + +namespace Backend.Fx.Domain +{ + /// + /// The root of an aggregate, identified by an id of type . + /// + public interface IAggregateRoot where TId : struct, IEquatable + { + public TId Id { get; } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs b/src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs deleted file mode 100644 index 31551810..00000000 --- a/src/abstractions/Backend.Fx/Domain/IAsyncAggregateQueryable.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Linq; - -namespace Backend.Fx.Domain -{ - public interface IAsyncAggregateQueryable : IAsyncQueryable - where TAggregateRoot: AggregateRoot - { } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/IRepository.cs b/src/abstractions/Backend.Fx/Domain/IRepository.cs deleted file mode 100644 index a62d4e13..00000000 --- a/src/abstractions/Backend.Fx/Domain/IRepository.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; - -namespace Backend.Fx.Domain -{ - /// - /// Encapsulates methods for retrieving domain objects - /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks - /// - /// - [PublicAPI] - public interface IRepository where TAggregateRoot : AggregateRoot - { - Task SingleAsync(int id, CancellationToken cancellationToken = default); - - Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default); - - Task GetAllAsync(CancellationToken cancellationToken = default); - - Task AnyAsync(CancellationToken cancellationToken = default); - - Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default); - - Task DeleteAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default); - - Task AddAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default); - - Task AddRangeAsync(TAggregateRoot[] aggregateRoots, CancellationToken cancellationToken = default); - } - - public abstract class Repository : IRepository where TAggregateRoot : AggregateRoot - { - private readonly IAsyncAggregateQueryable _queryable; - - protected Repository(IAsyncAggregateQueryable queryable) - { - _queryable = queryable; - } - - public async Task SingleAsync(int id, CancellationToken cancellationToken = default) - { - return await _queryable.SingleAsync(ar => ar.Id == id, cancellationToken).ConfigureAwait(false); - } - - public async Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default) - { - return await _queryable.SingleOrDefaultAsync(ar => ar.Id == id, cancellationToken).ConfigureAwait(false); - } - - public async Task GetAllAsync(CancellationToken cancellationToken = default) - { - return await _queryable.ToArrayAsync(cancellationToken).ConfigureAwait(false); - } - - public async Task AnyAsync(CancellationToken cancellationToken = default) - { - return await _queryable.AnyAsync(cancellationToken).ConfigureAwait(false); - } - - public async Task ResolveAsync( - IEnumerable ids, - CancellationToken cancellationToken = default) - { - var idArray = ids as int[] ?? ids.ToArray(); - var resolved = new TAggregateRoot[idArray.Length]; - for (var i = 0; i < idArray.Length; i++) - { - resolved[i] = await SingleAsync(idArray[i], cancellationToken).ConfigureAwait(false); - } - - return resolved; - } - - public abstract Task DeleteAsync(TAggregateRoot aggregateRoot,CancellationToken cancellationToken = default); - - public abstract Task AddAsync(TAggregateRoot aggregateRoot,CancellationToken cancellationToken = default); - - public abstract Task AddRangeAsync(TAggregateRoot[] aggregateRoots, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/IView.cs b/src/abstractions/Backend.Fx/Domain/IView.cs deleted file mode 100644 index 3b1766c6..00000000 --- a/src/abstractions/Backend.Fx/Domain/IView.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using JetBrains.Annotations; - -namespace Backend.Fx.Domain -{ - [PublicAPI] - public interface IView : IQueryable - { - } - - [PublicAPI] - public abstract class View : IView - { - private readonly IQueryable _viewImplementation; - - protected View(IQueryable viewImplementation) - { - _viewImplementation = viewImplementation; - } - - public IEnumerator GetEnumerator() - { - return _viewImplementation.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable) _viewImplementation).GetEnumerator(); - } - - public Type ElementType => _viewImplementation.ElementType; - - public Expression Expression => _viewImplementation.Expression; - - public IQueryProvider Provider => _viewImplementation.Provider; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Domain/Identified.cs b/src/abstractions/Backend.Fx/Domain/Identified.cs index b30c1d9f..63229661 100644 --- a/src/abstractions/Backend.Fx/Domain/Identified.cs +++ b/src/abstractions/Backend.Fx/Domain/Identified.cs @@ -1,18 +1,33 @@ using System; -using System.ComponentModel.DataAnnotations; using System.Diagnostics; using JetBrains.Annotations; namespace Backend.Fx.Domain { + [PublicAPI] [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public abstract class Identified : IEquatable + public abstract class Identified : IEquatable> + where TId : struct { - [Key] public int Id { get; set; } + public abstract TId Id { get; protected set; } + + /// + /// DON'T USE! + /// This ctor is only here to allow O/R-Mappers to materialize an object coming from a persistent + /// store using reflection. + /// + protected Identified() + { + } + + protected Identified(TId id) + { + Id = id; + } [UsedImplicitly] public string DebuggerDisplay => $"{GetType().Name}[{Id}]"; - public bool Equals(Identified other) + public bool Equals(Identified other) { if (other == null || other.GetType() != GetType()) { @@ -24,34 +39,21 @@ public bool Equals(Identified other) public override bool Equals(object obj) { - var other = obj as Identified; - if (other == null) - { - return false; - } - - return Equals(other); + var other = obj as Identified; + return other != null && Equals(other); } public override int GetHashCode() { - // ReSharper disable NonReadonlyMemberInGetHashCode - if (Id != 0) - { - return Id.GetHashCode(); - } - // ReSharper enable NonReadonlyMemberInGetHashCode - - // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode - return base.GetHashCode(); + return Id.GetHashCode(); } - public static bool operator ==(Identified x, Identified y) + public static bool operator ==(Identified x, Identified y) { - return Equals(x, y); + return Equals(x?.Id, y?.Id); } - public static bool operator !=(Identified x, Identified y) + public static bool operator !=(Identified x, Identified y) { return !(x == y); } diff --git a/src/abstractions/Backend.Fx/Domain/ValueObject.cs b/src/abstractions/Backend.Fx/Domain/ValueObject.cs index ed714c29..5057bd18 100644 --- a/src/abstractions/Backend.Fx/Domain/ValueObject.cs +++ b/src/abstractions/Backend.Fx/Domain/ValueObject.cs @@ -8,7 +8,7 @@ namespace Backend.Fx.Domain /// An object that contains attributes but has no conceptual identity. /// https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks /// - public abstract class ValueObject + public abstract class ValueObject : IEquatable { /// /// When overriden in a derived class, returns all components of a value objects which constitute its identity. @@ -16,6 +16,14 @@ public abstract class ValueObject /// An ordered list of equality components. protected abstract IEnumerable GetEqualityComponents(); + public bool Equals(ValueObject other) + { + if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(null, other)) return false; + + return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); + } + public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) return true; @@ -74,35 +82,33 @@ protected int CompareTo(ComparableValueObject other) { return 1; } - - using (var thisComponents = GetComparableComponents().GetEnumerator()) - using (var otherComponents = other.GetComparableComponents().GetEnumerator()) + + using var thisComponents = GetComparableComponents().GetEnumerator(); + using var otherComponents = other.GetComparableComponents().GetEnumerator(); + while (true) { - while (true) + var x = thisComponents.MoveNext(); + var y = otherComponents.MoveNext(); + if (x != y) { - var x = thisComponents.MoveNext(); - var y = otherComponents.MoveNext(); - if (x != y) - { - throw new InvalidOperationException(); - } + throw new InvalidOperationException(); + } - if (x) - { - var c = thisComponents.Current?.CompareTo(otherComponents.Current) ?? 0; - if (c != 0) - { - return c; - } - } - else + if (x) + { + var c = thisComponents.Current?.CompareTo(otherComponents.Current) ?? 0; + if (c != 0) { - break; + return c; } } - - return 0; + else + { + break; + } } + + return 0; } } diff --git a/src/abstractions/Backend.Fx/Exceptions/ClientException.cs b/src/abstractions/Backend.Fx/Exceptions/ClientException.cs index 9b199d6e..4a07a4b4 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ClientException.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ClientException.cs @@ -32,7 +32,7 @@ public ClientException(string message, Exception innerException) { } - public Errors Errors { get; } = new Errors(); + public Errors Errors { get; } = new(); public bool HasErrors() { diff --git a/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs b/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs index adefe44d..1139a784 100644 --- a/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs +++ b/src/abstractions/Backend.Fx/Exceptions/ExceptionBuilder.cs @@ -16,7 +16,7 @@ public interface IExceptionBuilder : IDisposable [PublicAPI] public class ExceptionBuilder : IExceptionBuilder where TEx : ClientException, new() { - private readonly TEx _clientException = new TEx(); + private readonly TEx _clientException = new(); public void Add(string error) { diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs index 29cc5d26..ee54c2fc 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/AnonymousIdentity.cs @@ -4,12 +4,27 @@ namespace Backend.Fx.ExecutionPipeline { [PublicAPI] - public sealed class AnonymousIdentity : IIdentity + public readonly struct AnonymousIdentity : IIdentity { public string Name => "ANONYMOUS"; - public string AuthenticationType => string.Empty; + public string AuthenticationType => null; public bool IsAuthenticated => false; + + public override bool Equals(object obj) + { + return obj is AnonymousIdentity; + } + + public override int GetHashCode() + { + return 1564925492; + } + + public bool Equals(AnonymousIdentity other) + { + return true; + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs index 10c4c4ee..321d82a8 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/BackendFxApplicationInvoker.cs @@ -2,7 +2,6 @@ using System.Security.Principal; using System.Threading.Tasks; using Backend.Fx.DependencyInjection; -using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Logging; using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +13,7 @@ public interface IBackendFxApplicationInvoker { /// The async action to be invoked by the application /// The acting identity - Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity); + Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity = null); } @@ -28,27 +27,23 @@ public BackendFxApplicationInvoker(ICompositionRoot compositionRoot) _compositionRoot = compositionRoot; } - - public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity) + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity = null) { - Logger.LogInformation("Invoking asynchronous action as {@Identity}", identity); - using (IServiceScope serviceScope = BeginScope(identity)) + identity ??= new AnonymousIdentity(); + Logger.LogInformation("Invoking action as {@Identity}", identity); + using IServiceScope serviceScope = BeginScope(identity); + using IDisposable durationLogger = UseDurationLogger(serviceScope); + var operation = serviceScope.ServiceProvider.GetRequiredService(); + try + { + operation.Begin(serviceScope); + await awaitableAsyncAction.Invoke(serviceScope.ServiceProvider).ConfigureAwait(false); + operation.Complete(); + } + catch { - using (UseDurationLogger(serviceScope)) - { - var operation = serviceScope.ServiceProvider.GetRequiredService(); - try - { - operation.Begin(serviceScope); - await awaitableAsyncAction.Invoke(serviceScope.ServiceProvider).ConfigureAwait(false); - operation.Complete(); - } - catch - { - operation.Cancel(); - throw; - } - } + operation.Cancel(); + throw; } } @@ -57,11 +52,9 @@ private IServiceScope BeginScope(IIdentity identity) { IServiceScope serviceScope = _compositionRoot.BeginScope(); - identity = identity ?? new AnonymousIdentity(); + identity ??= new AnonymousIdentity(); serviceScope.ServiceProvider.GetRequiredService>().ReplaceCurrent(identity); - - return serviceScope; } @@ -71,8 +64,8 @@ private static IDisposable UseDurationLogger(IServiceScope serviceScope) IIdentity identity = serviceScope.ServiceProvider.GetRequiredService>().Current; Correlation correlation = serviceScope.ServiceProvider.GetRequiredService>().Current; return Logger.LogInformationDuration( - $"Starting scope (correlation [{correlation.Id}]) for {identity.Name}", - $"Ended scope (correlation [{correlation.Id}]) for {identity.Name}"); + $"Starting invocation (correlation [{correlation.Id}]) for {identity.Name}", + $"Ended invocation (correlation [{correlation.Id}]) for {identity.Name}"); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs index 6540ef16..9e406ac4 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentCorrelationHolder.cs @@ -1,4 +1,3 @@ -using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Util; using JetBrains.Annotations; diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs index 48247b00..7447c25e 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/CurrentIdentityHolder.cs @@ -20,13 +20,10 @@ public override IIdentity ProvideInstance() protected override string Describe(IIdentity instance) { - if (instance == null) - { - return ""; - } - - string auth = instance.IsAuthenticated ? $"authenticated via {instance.AuthenticationType}" : "not authenticated"; - return $"Identity: {instance.Name}, {auth}"; + var auth = instance?.IsAuthenticated == true + ? $"authenticated via {instance.AuthenticationType}" + : "not authenticated"; + return $"Identity: {instance?.Name ?? ""}, {auth}"; } public static ICurrentTHolder CreateSystem() diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs index 3e76dfa4..69689d5e 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/ExceptionLoggingAndHandlingInvoker.cs @@ -5,7 +5,7 @@ namespace Backend.Fx.ExecutionPipeline { - internal class ExceptionLoggingAndHandlingInvoker : IBackendFxApplicationInvoker + public class ExceptionLoggingAndHandlingInvoker : IBackendFxApplicationInvoker { private readonly IExceptionLogger _exceptionLogger; private readonly IBackendFxApplicationInvoker _invoker; diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs index 9dee156b..ef586bf5 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/ExecutionPipelineModule.cs @@ -1,6 +1,5 @@ using System.Security.Principal; using Backend.Fx.DependencyInjection; -using Backend.Fx.Extensions.MessageBus; using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; using NodaTime; diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs index 01af6841..4236a35a 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/FrozenClock.cs @@ -7,7 +7,7 @@ namespace Backend.Fx.ExecutionPipeline /// /// Best practice for web (service) applications: time does not advance during an invocation /// - internal class FrozenClock : IClock + public class FrozenClock : IClock { private static readonly ILogger Logger = Log.Create(); private readonly Instant _frozenInstant; diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs index 781c7802..dc676b4b 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/Operation.cs @@ -7,7 +7,7 @@ namespace Backend.Fx.ExecutionPipeline { /// - /// The basic interface of an operation invoked by the (or its async counterpart). + /// The basic interface of an operation invoked by the . /// Decorate this interface to provide operation specific infrastructure services (like a database connection, a database transaction /// an entry-exit logging etc.) /// @@ -21,6 +21,7 @@ public interface IOperation void Cancel(); } + [UsedImplicitly] internal sealed class Operation : IOperation { private static readonly ILogger Logger = Log.Create(); @@ -56,10 +57,6 @@ public void Complete() public void Cancel() { Logger.LogInformation("Canceling operation #{OperationId}", _instanceId); - if (_isActive != true) - { - throw new InvalidOperationException($"Cannot cancel an operation that is {(_isActive == false ? "terminated" : "not active")}"); - } _isActive = false; _lifetimeLogger?.Dispose(); _lifetimeLogger = null; diff --git a/src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs b/src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs index 80c8f598..90bad159 100644 --- a/src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs +++ b/src/abstractions/Backend.Fx/ExecutionPipeline/SystemIdentity.cs @@ -4,12 +4,27 @@ namespace Backend.Fx.ExecutionPipeline { [PublicAPI] - public sealed class SystemIdentity : IIdentity + public struct SystemIdentity : IIdentity { public string Name => "SYSTEM"; - public string AuthenticationType => "system internal"; + public string AuthenticationType => "Internal"; public bool IsAuthenticated => true; + + public override bool Equals(object obj) + { + return obj is SystemIdentity; + } + + public override int GetHashCode() + { + return 542451621; + } + + public bool Equals(SystemIdentity other) + { + return true; + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs b/src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs deleted file mode 100644 index 763a663e..00000000 --- a/src/abstractions/Backend.Fx/Extensions/BackendFxApplicationExtension.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.DependencyInjection; -using Backend.Fx.ExecutionPipeline; -using Backend.Fx.Logging; -using Backend.Fx.Util; -using Microsoft.Extensions.Logging; - -namespace Backend.Fx.Extensions -{ - public abstract class BackendFxApplicationExtension : IBackendFxApplication - { - private static readonly ILogger Logger = Log.Create(); - private readonly IBackendFxApplication _application; - - protected BackendFxApplicationExtension(IBackendFxApplication application) - { - Logger.LogInformation("Decorating the application with {Decorator}", GetType().GetDetailedTypeName()); - _application = application; - } - - public Assembly[] Assemblies => _application.Assemblies; - - public virtual ICompositionRoot CompositionRoot => _application.CompositionRoot; - - public virtual IExceptionLogger ExceptionLogger => _application.ExceptionLogger; - - public virtual IBackendFxApplicationInvoker Invoker => _application.Invoker; - - public virtual bool WaitForBoot( - int timeoutMilliSeconds = int.MaxValue, - CancellationToken cancellationToken = default) - { - return _application.WaitForBoot(timeoutMilliSeconds, cancellationToken); - } - - public virtual Task BootAsync(CancellationToken cancellationToken = default) - { - return _application.BootAsync(cancellationToken); - } - - public TBackendFxApplicationDecorator As() where TBackendFxApplicationDecorator : BackendFxApplicationExtension - { - if (this is TBackendFxApplicationDecorator matchingDecorator) - { - return matchingDecorator; - } - - return _application.As(); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _application?.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs deleted file mode 100644 index d06fb8fb..00000000 --- a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationApplication.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.ExecutionPipeline; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.Extensions.DataGeneration -{ - /// - /// The feature "Data Generation" makes sure that all implemented data generators are executed on application boot - /// - [PublicAPI] - public class DataGenerationApplication : BackendFxApplicationExtension - { - private readonly bool _runDemoDataGenerators; - - public DataGenerationApplication(IBackendFxApplication application, bool runDemoDataGenerators) : base( - application) - { - _runDemoDataGenerators = runDemoDataGenerators; - application.CompositionRoot.RegisterModules(new DataGenerationModule(application.Assemblies)); - } - - public override async Task BootAsync(CancellationToken cancellationToken = default) - { - await base.BootAsync(cancellationToken).ConfigureAwait(false); - await RunDataGenerators(cancellationToken).ConfigureAwait(false); - } - - private async Task RunDataGenerators(CancellationToken cancellationToken) - { - var dataGeneratorTypes = Type.EmptyTypes; - await Invoker.InvokeAsync(sp => - { - dataGeneratorTypes = sp - .GetServices() - .OrderBy(dg => dg.Priority) - .Select(dg => dg.GetType()) - .ToArray(); - return Task.CompletedTask; - }, new SystemIdentity()).ConfigureAwait(false); - - foreach (var dataGeneratorType in dataGeneratorTypes) - { - if (typeof(IProductiveDataGenerator).IsAssignableFrom(dataGeneratorType) - || typeof(IDemoDataGenerator).IsAssignableFrom(dataGeneratorType) && _runDemoDataGenerators) - { - await Invoker.InvokeAsync(async sp => - { - var dataGenerator = (IDataGenerator)sp.GetRequiredService(dataGeneratorType); - await dataGenerator.GenerateAsync().ConfigureAwait(false); - }, new SystemIdentity()).ConfigureAwait(false); - } - } - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs b/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs deleted file mode 100644 index 2f35613b..00000000 --- a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerationModule.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Linq; -using System.Reflection; -using Backend.Fx.DependencyInjection; -using Backend.Fx.Util; -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.Extensions.DataGeneration -{ - public class DataGenerationModule : IModule - { - private readonly Assembly[] _assemblies; - - public DataGenerationModule(Assembly[] assemblies) - { - _assemblies = assemblies; - } - - public void Register(ICompositionRoot compositionRoot) - { - compositionRoot.RegisterCollection( - _assemblies - .GetImplementingTypes(typeof(IDataGenerator)) - .Select(t => new ServiceDescriptor(typeof(IDataGenerator), t, ServiceLifetime.Scoped))); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs deleted file mode 100644 index 7cdc2027..00000000 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/ICanFlush.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Backend.Fx.Extensions.Persistence -{ - public interface ICanFlush - { - void Flush(); - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs b/src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs deleted file mode 100644 index a7de7aa4..00000000 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/PersistentApplication.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Backend.Fx.DependencyInjection; -using Backend.Fx.Logging; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; - -namespace Backend.Fx.Extensions.Persistence -{ - [PublicAPI] - public class PersistentApplication : BackendFxApplicationExtension - { - private static readonly ILogger Logger = Log.Create(); - - private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; - private readonly IDatabaseBootstrapper _databaseBootstrapper; - - public PersistentApplication(IModule persistenceModule, IBackendFxApplication application) - : this(null, null, persistenceModule, application) - { - } - - public PersistentApplication( - IDatabaseBootstrapper databaseBootstrapper, - IModule persistenceModule, - IBackendFxApplication application) - : this(databaseBootstrapper, null, persistenceModule, application) - { - } - - public PersistentApplication( - IDatabaseBootstrapper databaseBootstrapper, - IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter, - IModule persistenceModule, - IBackendFxApplication application) - : base(application) - { - _databaseBootstrapper = databaseBootstrapper; - _databaseAvailabilityAwaiter = databaseAvailabilityAwaiter; - application.CompositionRoot.RegisterModules(persistenceModule); - } - - - public override async Task BootAsync(CancellationToken cancellationToken = default) - { - Logger.LogTrace("Booting..."); - if (_databaseAvailabilityAwaiter != null) - { - await _databaseAvailabilityAwaiter.WaitForDatabase(cancellationToken).ConfigureAwait(false); - } - - _databaseBootstrapper?.EnsureDatabaseExistence(); - await base.BootAsync(cancellationToken).ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs b/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs index 87113553..a7bdebdf 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AllowAll.cs @@ -4,7 +4,9 @@ namespace Backend.Fx.Features.Authorization { - public class AllowAll : AuthorizationPolicy where TAggregateRoot : AggregateRoot + public class AllowAll : AuthorizationPolicy + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable { public override Expression> HasAccessExpression { diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs index ba54b606..23bb1a34 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationFeature.cs @@ -1,23 +1,25 @@ -using System.Reflection; using Backend.Fx.Domain; using Backend.Fx.Exceptions; +using Backend.Fx.Features.Persistence; using JetBrains.Annotations; namespace Backend.Fx.Features.Authorization { - public static class AuthorizationFeature + /// + /// The feature "Authorization" obligates you the implementation of an + /// for every . Instances of these policy classes are applied to the repositories, so + /// that on every read or write operation on it, the policy is automatically enforced. Denied reads won't fail but + /// just appear invisible, while a denied write throws a . + /// While implementing policies, you can start by deriving from or + /// . + /// + [PublicAPI] + public class AuthorizationFeature : Feature { - /// - /// The feature "Authorization" obligates you the implementation of an - /// for every . Instances of these policy classes are applied to the repositories, so - /// that on every read or write operation on it, the policy is automatically enforced. Denied reads won't fail but - /// just appear invisible, while a denied write throws a . - /// While implementing policies, you can start by deriving from or - /// . - /// - /// - [PublicAPI] - public static void EnableAuthorization(this IBackendFxApplication application) - => application.CompositionRoot.RegisterModules(new AuthorizationModule(application.Assemblies)); + public override void Enable(IBackendFxApplication application) + { + application.RequireDependantFeature(); + application.CompositionRoot.RegisterModules(new AuthorizationModule(application.Assemblies)); + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs index beb30bca..eca7349f 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationModule.cs @@ -3,6 +3,7 @@ using System.Reflection; using Backend.Fx.DependencyInjection; using Backend.Fx.Domain; +using Backend.Fx.Features.Persistence; using Backend.Fx.Logging; using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; @@ -31,44 +32,54 @@ public void Register(ICompositionRoot compositionRoot) private static void RegisterAuthorizingDecorators(ICompositionRoot compositionRoot) { Logger.LogDebug("Registering authorization decorators"); - compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped(typeof(IAsyncAggregateQueryable<>), typeof(AuthorizingQueryable<>))); - compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped(typeof(IRepository<>), typeof(AuthorizingRepository<>))); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped(typeof(IAggregateQueryable<,>), + typeof(AuthorizingQueryable<,>))); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped(typeof(IRepository<,>), + typeof(AuthorizingRepository<,>))); } - + private void RegisterAuthorizationPolicies(ICompositionRoot compositionRoot) { Logger.LogDebug("Registering authorization services from {Assemblies}", _assembliesForLogging); - var aggregateRootTypes = _assemblies.GetImplementingTypes(typeof(AggregateRoot)).ToArray(); + var aggregateRootTypes = _assemblies + .SelectMany(ass => ass.GetTypes()) + .Where(t => t.IsImplementationOfOpenGenericInterface(typeof(IAggregateRoot<>))) + .ToArray(); + foreach (var aggregateRootType in aggregateRootTypes) { - var aggregateAuthorizationTypes = _assemblies - .GetImplementingTypes(typeof(IAuthorizationPolicy<>).MakeGenericType(aggregateRootType)) + var idType = aggregateRootType + .GetInterfaces() + .Where(inf => inf.IsGenericType) + .Single(inf => inf.GetGenericTypeDefinition() == typeof(IAggregateRoot<>)) + .GenericTypeArguments[0]; + + var authorizationPolicyInterfaceType = + typeof(IAuthorizationPolicy<,>).MakeGenericType(aggregateRootType, idType); + var authorizationPolicyTypes = _assemblies + .GetImplementingTypes(authorizationPolicyInterfaceType) .ToArray(); - foreach (Type aggregateAuthorizationType in aggregateAuthorizationTypes) + if (authorizationPolicyTypes.Length == 0) { - var serviceTypes = aggregateAuthorizationType - .GetTypeInfo() - .ImplementedInterfaces - .Where(i => i.GetTypeInfo().IsGenericType - && i.GenericTypeArguments.Length == 1 - && typeof(AggregateRoot).GetTypeInfo() - .IsAssignableFrom(i.GenericTypeArguments[0].GetTypeInfo())); + Logger.LogWarning( + "No authorization policies for {AggregateRootTyp} found", aggregateRootType.Name); + return; + } - foreach (Type serviceType in serviceTypes) - { - Logger.LogDebug( - "Registering scoped authorization service {ServiceType} with implementation {ImplementationType}", - serviceType.Name, - aggregateAuthorizationType.Name); - compositionRoot.Register( - new ServiceDescriptor( - serviceType, - aggregateAuthorizationType, - ServiceLifetime.Scoped)); - } + if (authorizationPolicyTypes.Length > 1) + { + throw new InvalidOperationException( + $"Multiple authorization policies found for {aggregateRootType.Name}: " + + $"[{string.Join(", ", authorizationPolicyTypes.Select(t => t.Name))}]"); } + + Logger.LogInformation( + "Registering scoped authorization service {ServiceType} with implementation {ImplementationType}", + authorizationPolicyInterfaceType.Name, + authorizationPolicyTypes[0].Name); + compositionRoot.Register(ServiceDescriptor.Scoped(authorizationPolicyInterfaceType, authorizationPolicyTypes[0])); } } } diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs index 68d3ed8c..958fc955 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizationPolicy.cs @@ -6,9 +6,10 @@ namespace Backend.Fx.Features.Authorization { - public abstract class AuthorizationPolicy : IAuthorizationPolicy where TAggregateRoot : AggregateRoot + public abstract class AuthorizationPolicy : IAuthorizationPolicy + where TAggregateRoot : IAggregateRoot where TId : struct, IEquatable { - private static readonly ILogger Logger = Log.Create>(); + private static readonly ILogger Logger = Log.Create>(); /// > public abstract Expression> HasAccessExpression { get; } @@ -19,12 +20,12 @@ public abstract class AuthorizationPolicy : IAuthorizationPolicy /// /// Implement a guard that might disallow modifying an existing aggregate. /// This overload is called directly before saving modification of an instance, so that you can use the instance's state for deciding. - /// This default implementation forwards to + /// This default implementation forwards to /// public virtual bool CanModify(TAggregateRoot t) { var canCreate = CanCreate(t); - Logger.LogTrace("CanCreate({AggregateRootTypeName}): {CanCreate}", t.DebuggerDisplay, canCreate); + Logger.LogTrace("CanCreate({AggregateRootTypeName}[{Id}]): {CanCreate}", t.GetType().Name, t.Id, canCreate); return canCreate; } @@ -32,7 +33,7 @@ public virtual bool CanModify(TAggregateRoot t) public virtual bool CanDelete(TAggregateRoot t) { var canModify = CanModify(t); - Logger.LogTrace("CanModify({AggregateRootTypeName}): {CanCreate}", t.DebuggerDisplay, canModify); + Logger.LogTrace("CanModify({AggregateRootTypeName}[{Id}]): {CanCreate}", t.GetType().Name, t.Id, canModify); return canModify; } } diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs index 87003189..4a29f0f5 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingQueryable.cs @@ -1,22 +1,24 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Threading; using Backend.Fx.Domain; +using Backend.Fx.Features.Persistence; namespace Backend.Fx.Features.Authorization { /// /// Applies the authorization policy expression to the queryable via decoration /// - /// - internal class AuthorizingQueryable : IAsyncAggregateQueryable where TAggregateRoot : AggregateRoot + internal class AuthorizingQueryable : IAggregateQueryable + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable { - private readonly AuthorizationPolicy _authorizationPolicy; - private readonly IAsyncAggregateQueryable _aggregateQueryable; + private readonly IAuthorizationPolicy _authorizationPolicy; + private readonly IAggregateQueryable _aggregateQueryable; - public AuthorizingQueryable(AuthorizationPolicy authorizationPolicy, IAsyncAggregateQueryable aggregateQueryable) + public AuthorizingQueryable(IAuthorizationPolicy authorizationPolicy, IAggregateQueryable aggregateQueryable) { _authorizationPolicy = authorizationPolicy; _aggregateQueryable = aggregateQueryable; @@ -24,13 +26,32 @@ public AuthorizingQueryable(AuthorizationPolicy authorizationPol public Type ElementType => _aggregateQueryable.ElementType; - public Expression Expression => Expression.And(_aggregateQueryable.Expression, _authorizationPolicy.HasAccessExpression); + public Expression Expression + { + get + { + // expression tree manipulation: apply the HasAccessExpression to the basic Queryable Expression using "Where" + MethodCallExpression queryableWhereExpression = Expression.Call( + typeof(Queryable), + "Where", + new [] { ElementType }, + _aggregateQueryable.Expression, + Expression.Quote(_authorizationPolicy.HasAccessExpression)); + + return queryableWhereExpression; + } + } - IAsyncQueryProvider IAsyncQueryable.Provider => _aggregateQueryable.Provider; + public IQueryProvider Provider => _aggregateQueryable.Provider; + + public IEnumerator GetEnumerator() + { + return _aggregateQueryable.GetEnumerator(); + } - public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) + IEnumerator IEnumerable.GetEnumerator() { - return _aggregateQueryable.GetAsyncEnumerator(cancellationToken); + return ((IEnumerable)_aggregateQueryable).GetEnumerator(); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs index 87951037..8c086a77 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/AuthorizingRepository.cs @@ -1,35 +1,38 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Backend.Fx.Domain; using Backend.Fx.Exceptions; +using Backend.Fx.Features.Persistence; namespace Backend.Fx.Features.Authorization { /// /// Checks the authorization policy for write operations /// - /// - internal class AuthorizingRepository : IRepository where TAggregateRoot : AggregateRoot + internal class AuthorizingRepository : IRepository + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable { - private readonly IAuthorizationPolicy _authorizationPolicy; - private readonly IRepository _repository; + private readonly IAuthorizationPolicy _authorizationPolicy; + private readonly IRepository _repository; - public AuthorizingRepository(IAuthorizationPolicy authorizationPolicy, IRepository repository) + public AuthorizingRepository(IAuthorizationPolicy authorizationPolicy, IRepository repository) { _authorizationPolicy = authorizationPolicy; _repository = repository; } - public Task SingleAsync(int id, CancellationToken cancellationToken = default) + public Task GetAsync(TId id, CancellationToken cancellationToken = default) { - return _repository.SingleAsync(id, cancellationToken); + return _repository.GetAsync(id, cancellationToken); } - public Task SingleOrDefaultAsync(int id, CancellationToken cancellationToken = default) + public Task FindAsync(TId id, CancellationToken cancellationToken = default) { - return _repository.SingleOrDefaultAsync(id, cancellationToken); + return _repository.FindAsync(id, cancellationToken); } public Task GetAllAsync(CancellationToken cancellationToken = default) @@ -42,7 +45,7 @@ public Task AnyAsync(CancellationToken cancellationToken = default) return _repository.AnyAsync(cancellationToken); } - public Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default) + public Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default) { return _repository.ResolveAsync(ids, cancellationToken); } diff --git a/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs b/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs index 5d1cfe47..3726d962 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/DenyAll.cs @@ -4,7 +4,9 @@ namespace Backend.Fx.Features.Authorization { - public class DenyAll : AuthorizationPolicy where TAggregateRoot : AggregateRoot + public class DenyAll : AuthorizationPolicy + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable { public override Expression> HasAccessExpression { diff --git a/src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs b/src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs index 19ba2662..b5490117 100644 --- a/src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs +++ b/src/abstractions/Backend.Fx/Features/Authorization/IAuthorizationPolicy.cs @@ -9,9 +9,10 @@ namespace Backend.Fx.Features.Authorization /// Implements permissions on aggregate level. The respective instance is applied when creating an , /// so that the repository never allows reading or writing of an aggregate without permissions. /// - /// [PublicAPI] - public interface IAuthorizationPolicy where TAggregateRoot : AggregateRoot + public interface IAuthorizationPolicy + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable { /// /// Express a filter for repository queryable diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs index 172a9c6c..8f5a7b4c 100644 --- a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsFeature.cs @@ -2,27 +2,30 @@ namespace Backend.Fx.Features.ConfigurationSettings { - public static class ConfigurationSettingsFeature + /// + /// The feature "Configuration Settings" provides a simple abstraction over an arbitrary key/value configuration + /// setting store. The default already provides serialization to and from + /// string for various configuration setting types, but you can provide your own implementation to extend the + /// functionality. + /// + /// The abstraction over your key/value store. Instances of this type will + /// be injected with a scoped lifetime. + [PublicAPI] + public class ConfigurationSettingsFeature : Feature + where TSettingRepository : class, ISettingRepository { - /// - /// The feature "Configuration Settings" provides a simple abstraction over an arbitrary key/value configuration - /// setting store. The default already provides serialization to and from - /// string for various configuration setting types, but you can provide your own implementation to extend the - /// functionality. - /// - /// + private readonly SettingSerializerFactory _settingSerializerFactory; + /// The factory that provides serializers. A singleton instance is being held. - /// The abstraction over your key/value store. Instances of this type will - /// be injected with a scoped lifetime. - [PublicAPI] - public static void AddConfigurationSettings( - this IBackendFxApplication application, - SettingSerializerFactory settingSerializerFactory = null) - where TSettingRepository : class, ISettingRepository + public ConfigurationSettingsFeature(SettingSerializerFactory settingSerializerFactory = null) + { + _settingSerializerFactory = settingSerializerFactory ?? new SettingSerializerFactory(); + } + + public override void Enable(IBackendFxApplication application) { - settingSerializerFactory = settingSerializerFactory ?? new SettingSerializerFactory(); application.CompositionRoot.RegisterModules( - new ConfigurationSettingsModule(settingSerializerFactory)); + new ConfigurationSettingsModule(_settingSerializerFactory, application.Assemblies)); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs index e2e8f7cf..e3fb9adc 100644 --- a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/ConfigurationSettingsModule.cs @@ -1,4 +1,6 @@ +using System.Reflection; using Backend.Fx.DependencyInjection; +using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; namespace Backend.Fx.Features.ConfigurationSettings @@ -7,10 +9,12 @@ internal class ConfigurationSettingsModule : IModule where TSettingRepository : class, ISettingRepository { private readonly SettingSerializerFactory _settingSerializerFactory; + private readonly Assembly[] _assemblies; - public ConfigurationSettingsModule(SettingSerializerFactory settingSerializerFactory) + public ConfigurationSettingsModule(SettingSerializerFactory settingSerializerFactory, Assembly[] assemblies) { _settingSerializerFactory = settingSerializerFactory; + _assemblies = assemblies; } public void Register(ICompositionRoot compositionRoot) @@ -20,6 +24,12 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( ServiceDescriptor.Scoped()); + + foreach (var settingsCategoryType in _assemblies.GetImplementingTypes()) + { + compositionRoot.Register(ServiceDescriptor.Scoped(settingsCategoryType, settingsCategoryType)); + } + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/InMem/InMemorySettingRepository.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/InMem/InMemorySettingRepository.cs new file mode 100644 index 00000000..c454e3a4 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/InMem/InMemorySettingRepository.cs @@ -0,0 +1,28 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.ConfigurationSettings.InMem +{ + [PublicAPI] + public abstract class InMemorySettingRepository : ISettingRepository + { + protected abstract ConcurrentDictionary> SettingsStore { get; } + + public string GetSerializedValue(string category, string key) + { + if (SettingsStore.TryGetValue(category, out var categorizedValues) && categorizedValues.TryGetValue(key, out var value)) + { + return value; + } + + return null; + } + + public void WriteSerializedValue(string category, string key, string serializedValue) + { + var categorizedValues = SettingsStore.GetOrAdd(category, s => new Dictionary()); + categorizedValues[key] = serializedValue; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/ConfigurationSettings/LocalDateTimeSerializer.cs b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/LocalDateTimeSerializer.cs new file mode 100644 index 00000000..89b32768 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/ConfigurationSettings/LocalDateTimeSerializer.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using JetBrains.Annotations; +using NodaTime; + +namespace Backend.Fx.Features.ConfigurationSettings +{ + [UsedImplicitly] + public class LocalDateTimeSerializer : ISettingSerializer + { + public string Serialize(LocalDateTime? setting) + { + return setting?.ToString("O", CultureInfo.InvariantCulture); + } + + public LocalDateTime? Deserialize(string value) + { + return string.IsNullOrWhiteSpace(value) + ? (LocalDateTime?) null + : LocalDateTime.FromDateTime(DateTime.Parse(value, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind)); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/BackendFxApplicationEx.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/BackendFxApplicationEx.cs new file mode 100644 index 00000000..6d594df5 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/BackendFxApplicationEx.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.DataGeneration +{ + public static class BackendFxApplicationEx + { + public static async Task GenerateData(this IBackendFxApplication application, CancellationToken cancellationToken = default) + { + var dataGenerationContext = application.CompositionRoot.ServiceProvider.GetRequiredService(); + var dataGeneratorTypes = await dataGenerationContext.GetDataGeneratorTypesAsync(application.Invoker).ConfigureAwait(false); + await dataGenerationContext.GenerateDataAsync(application.Invoker, dataGeneratorTypes, cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationFeature.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationFeature.cs new file mode 100644 index 00000000..33d74b28 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationFeature.cs @@ -0,0 +1,41 @@ +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.DataGeneration +{ + /// + /// The extension "Data Generation" makes sure that all implemented data generators are executed on application boot + /// + [PublicAPI] + public class DataGenerationFeature : Feature, IBootableFeature, IMultiTenancyFeature + { + private readonly bool _allowDemoDataGeneration; + + /// + /// Controls, whether demo data generators should run. In case of a multi tenancy application, this flag must + /// be set to true to allow specific tenants to contain demo data, following the respective tenant + /// configuration. + /// + public DataGenerationFeature(bool allowDemoDataGeneration = true) + { + _allowDemoDataGeneration = allowDemoDataGeneration; + } + + public override void Enable(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules( + new DataGenerationModule(application.Assemblies, _allowDemoDataGeneration)); + } + + public void EnableMultiTenancyServices(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new MultiTenancyDataGenerationModule()); + } + + public async Task BootAsync(IBackendFxApplication application, CancellationToken cancellationToken = default) + { + await application.GenerateData(cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs new file mode 100644 index 00000000..12f91bcb --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerationModule.cs @@ -0,0 +1,44 @@ +using System.Linq; +using System.Reflection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.DataGeneration +{ + internal class DataGenerationModule : IModule + { + private readonly Assembly[] _assemblies; + private readonly bool _allowDemoDataGeneration; + + public DataGenerationModule(Assembly[] assemblies, bool allowDemoDataGeneration) + { + _assemblies = assemblies; + _allowDemoDataGeneration = allowDemoDataGeneration; + } + + public void Register(ICompositionRoot compositionRoot) + { + var serviceType = _allowDemoDataGeneration + ? typeof(IDataGenerator) + : typeof(IProductiveDataGenerator); + + compositionRoot.RegisterCollection( + _assemblies + .GetImplementingTypes(serviceType) + .Select(t => new ServiceDescriptor(typeof(IDataGenerator), t, ServiceLifetime.Scoped))); + + compositionRoot.Register( + ServiceDescriptor.Singleton()); + } + } + + internal class MultiTenancyDataGenerationModule : IModule + { + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.RegisterDecorator( + ServiceDescriptor.Singleton()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerator.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs similarity index 78% rename from src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerator.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs index d5dbb4fe..288874ff 100644 --- a/src/abstractions/Backend.Fx/Extensions/DataGeneration/DataGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/DataGenerator.cs @@ -1,10 +1,12 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Backend.Fx.Logging; using JetBrains.Annotations; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.DataGeneration +namespace Backend.Fx.Features.DataGeneration { + [PublicAPI] public interface IDataGenerator { /// @@ -12,7 +14,7 @@ public interface IDataGenerator /// int Priority { get; } - Task GenerateAsync(); + Task GenerateAsync(CancellationToken cancellationToken = default); } /// @@ -22,6 +24,7 @@ public interface IDataGenerator /// Any implementation is automatically picked up by the injection container, so no extra plumbing is required. /// You can require any application or domain service including repositories via constructor parameter. /// + [PublicAPI] public abstract class DataGenerator : IDataGenerator { @@ -32,13 +35,13 @@ public abstract class DataGenerator : IDataGenerator /// public abstract int Priority { get; } - public async Task GenerateAsync() + public async Task GenerateAsync(CancellationToken cancellationToken = default) { if (ShouldRun()) { Initialize(); Logger.LogInformation("{DataGeneratorTypeName} is now generating initial data", GetType().FullName); - await GenerateCoreAsync(); + await GenerateCoreAsync(cancellationToken).ConfigureAwait(false); } else { @@ -49,7 +52,8 @@ public async Task GenerateAsync() /// /// Implement your generate Logic here /// - protected abstract Task GenerateCoreAsync(); + /// + protected abstract Task GenerateCoreAsync(CancellationToken cancellationToken); /// /// Implement your initial logic here (e.g. loading from external source) @@ -58,7 +62,7 @@ public async Task GenerateAsync() /// /// return true, if the generator should be executed. Generators must be implemented idempotent, - /// since they're all executed on application start + /// since they're all executed on each application start /// /// protected abstract bool ShouldRun(); diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/ForEachTenantDataGenerationContext.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/ForEachTenantDataGenerationContext.cs new file mode 100644 index 00000000..e967513b --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/ForEachTenantDataGenerationContext.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Features.MultiTenancy; + +namespace Backend.Fx.Features.DataGeneration +{ + public class ForEachTenantDataGenerationContext : IDataGenerationContext + { + private readonly ITenantEnumerator _tenantEnumerator; + private readonly ITenantWideMutexManager _tenantWideMutexManager; + private readonly IDataGenerationContext _dataGenerationContext; + + public ForEachTenantDataGenerationContext( + ITenantEnumerator tenantEnumerator, + ITenantWideMutexManager tenantWideMutexManager, + IDataGenerationContext dataGenerationContext) + { + _tenantEnumerator = tenantEnumerator; + _tenantWideMutexManager = tenantWideMutexManager; + _dataGenerationContext = dataGenerationContext; + } + + public Task GetDataGeneratorTypesAsync(IBackendFxApplicationInvoker invoker) + { + return _dataGenerationContext.GetDataGeneratorTypesAsync(invoker); + } + + public async Task GenerateDataAsync(IBackendFxApplicationInvoker invoker, IEnumerable dataGeneratorTypes, + CancellationToken cancellationToken = default) + { + dataGeneratorTypes = dataGeneratorTypes as Type[] ?? dataGeneratorTypes.ToArray(); + + await _dataGenerationContext.GenerateDataAsync( + new ForEachTenantIdInvoker( + _tenantEnumerator.GetActiveTenantIds(), + _tenantWideMutexManager, + "DataGeneration", + invoker), + dataGeneratorTypes.Where(t => typeof(IProductiveDataGenerator).IsAssignableFrom(t)), + cancellationToken) + .ConfigureAwait(false); + + await _dataGenerationContext.GenerateDataAsync( + new ForEachTenantIdInvoker( + _tenantEnumerator.GetActiveDemoTenantIds(), + _tenantWideMutexManager, + "DataGeneration", + invoker), + dataGeneratorTypes.Where(t => typeof(IDemoDataGenerator).IsAssignableFrom(t)), + cancellationToken) + .ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DataGeneration/IDataGenerationContext.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/IDataGenerationContext.cs new file mode 100644 index 00000000..a55bfd08 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/IDataGenerationContext.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.DataGeneration +{ + public interface IDataGenerationContext + { + Task GetDataGeneratorTypesAsync(IBackendFxApplicationInvoker invoker); + Task GenerateDataAsync(IBackendFxApplicationInvoker invoker, IEnumerable dataGeneratorTypes, CancellationToken cancellationToken = default); + } + + [UsedImplicitly] + public class DataGenerationContext : IDataGenerationContext + { + public async Task GetDataGeneratorTypesAsync(IBackendFxApplicationInvoker invoker) + { + var dataGeneratorTypes = Type.EmptyTypes; + await invoker.InvokeAsync(sp => + { + dataGeneratorTypes = sp + .GetServices() + .OrderBy(dg => dg.Priority) + .Select(dg => dg.GetType()) + .ToArray(); + return Task.CompletedTask; + }, new SystemIdentity()).ConfigureAwait(false); + + return dataGeneratorTypes; + } + + public async Task GenerateDataAsync(IBackendFxApplicationInvoker invoker, IEnumerable dataGeneratorTypes, + CancellationToken cancellationToken = default) + { + foreach (var dataGeneratorType in dataGeneratorTypes) + { + await invoker.InvokeAsync(async sp => + { + var dataGenerator = sp.GetServices().Single(dgt => dgt.GetType() == dataGeneratorType); + await dataGenerator.GenerateAsync(cancellationToken).ConfigureAwait(false); + }, new SystemIdentity()).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/DataGeneration/IDemoDataGenerator.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs similarity index 79% rename from src/abstractions/Backend.Fx/Extensions/DataGeneration/IDemoDataGenerator.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs index 6ba2209f..63f28de0 100644 --- a/src/abstractions/Backend.Fx/Extensions/DataGeneration/IDemoDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/IDemoDataGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Extensions.DataGeneration +namespace Backend.Fx.Features.DataGeneration { /// /// Marks an as active in development environments only diff --git a/src/abstractions/Backend.Fx/Extensions/DataGeneration/IProductiveDataGenerator.cs b/src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs similarity index 79% rename from src/abstractions/Backend.Fx/Extensions/DataGeneration/IProductiveDataGenerator.cs rename to src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs index ac43e473..255fd759 100644 --- a/src/abstractions/Backend.Fx/Extensions/DataGeneration/IProductiveDataGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/DataGeneration/IProductiveDataGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Extensions.DataGeneration +namespace Backend.Fx.Features.DataGeneration { /// /// Marks an as active in all environments diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs index 1fd8cabc..e8008ecb 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventAggregator.cs @@ -6,7 +6,7 @@ namespace Backend.Fx.Features.DomainEvents { - public class DomainEventAggregator : IDomainEventAggregator + public class DomainEventAggregator : IDomainEventAggregator, IDomainEventAggregatorScope { private class HandleAction { @@ -24,7 +24,7 @@ public HandleAction(string domainEventName, string handlerTypeName, Action actio private static readonly ILogger Logger = Log.Create(); private readonly IServiceProvider _serviceProvider; - private readonly ConcurrentQueue _handleActions = new ConcurrentQueue(); + private readonly ConcurrentQueue _handleActions = new(); public DomainEventAggregator(IServiceProvider serviceProvider) { diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs index b383c0b1..b4a03974 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsFeature.cs @@ -3,20 +3,18 @@ namespace Backend.Fx.Features.DomainEvents { - - public static class DomainEventsFeature + /// + /// The feature "Domain Events" provides you with a domain event aggregator, that will be injected as a scoped + /// instance and generic domain event handlers that will also be injected as scoped instances. You can publish + /// arbitrary domain events using the instance, but domain events won't be + /// raised until the is completing. + /// Failures when handling domain events will result in canceling the whole operation, thus in rolling back a + /// possible transaction. + /// + [PublicAPI] + public class DomainEventsFeature : Feature { - /// - /// The feature "Domain Events" provides you with a domain event aggregator, that will be injected as a scoped - /// instance and generic domain event handlers that will also be injected as scoped instances. You can publish - /// arbitrary domain events using the instance, but domain events won't be - /// raised until the is completing. - /// Failures when handling domain events will result in canceling the whole operation, thus in rolling back a - /// possible transaction. - /// - /// - [PublicAPI] - public static void EnableDomainEvents(this IBackendFxApplication application) + public override void Enable(IBackendFxApplication application) { application.CompositionRoot.RegisterModules(new DomainEventsModule(application.Assemblies)); } diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs index 63e3e8f1..0f5b9f53 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/DomainEventsModule.cs @@ -21,7 +21,9 @@ public DomainEventsModule(params Assembly[] assemblies) public void Register(ICompositionRoot compositionRoot) { - compositionRoot.Register(ServiceDescriptor.Scoped(sp => new DomainEventAggregator(sp))); + compositionRoot.Register(ServiceDescriptor.Scoped(sp => new DomainEventAggregator(sp))); + compositionRoot.Register(ServiceDescriptor.Scoped(sp => sp.GetRequiredService())); + compositionRoot.Register(ServiceDescriptor.Scoped(sp => sp.GetRequiredService())); compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); RegisterDomainEventHandlers(compositionRoot); } diff --git a/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs index 0087c1e6..586401c1 100644 --- a/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs +++ b/src/abstractions/Backend.Fx/Features/DomainEvents/IDomainEventAggregator.cs @@ -1,12 +1,16 @@ namespace Backend.Fx.Features.DomainEvents { + public interface IDomainEventAggregatorScope + { + void PublishDomainEvent(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent; + } + /// /// Channel events from multiple objects into a single object to simplify registration for clients. /// https://martinfowler.com/eaaDev/EventAggregator.html /// public interface IDomainEventAggregator { - void PublishDomainEvent(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent; void RaiseEvents(); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs b/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs index 00d1f138..1ffdfe09 100644 --- a/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs +++ b/src/abstractions/Backend.Fx/Features/DomainServices/DomainServicesFeature.cs @@ -1,18 +1,17 @@ -using System.Reflection; using JetBrains.Annotations; namespace Backend.Fx.Features.DomainServices { - public static class DomainServicesFeature + /// + /// The feature "Domain Services" makes sure that all implementations of and + /// are injected as scoped instances. + /// + [PublicAPI] + public class DomainServicesFeature : Feature { - /// - /// The feature "Domain Services" makes sure that all implementations of and - /// are injected as scoped instances. - /// - /// - /// - [PublicAPI] - public static void AddDomainServices(this IBackendFxApplication application) - => application.CompositionRoot.RegisterModules(new DomainServicesModule(application.Assemblies)); + public override void Enable(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new DomainServicesModule(application.Assemblies)); + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Feature.cs b/src/abstractions/Backend.Fx/Features/Feature.cs new file mode 100644 index 00000000..972a3d12 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Feature.cs @@ -0,0 +1,27 @@ +using System; +using JetBrains.Annotations; + +namespace Backend.Fx.Features +{ + /// + /// Base class for optional features that can be added to the Backend.Fx execution pipeline + /// + [PublicAPI] + public abstract class Feature : IDisposable + { + public abstract void Enable(IBackendFxApplication application); + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/IBootableFeature.cs b/src/abstractions/Backend.Fx/Features/IBootableFeature.cs new file mode 100644 index 00000000..5754cd94 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/IBootableFeature.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Backend.Fx.Features +{ + /// + /// Marks a to require stuff done during startup of the application + /// + public interface IBootableFeature + { + public Task BootAsync(IBackendFxApplication application, CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/IMultiTenancyFeature.cs b/src/abstractions/Backend.Fx/Features/IMultiTenancyFeature.cs new file mode 100644 index 00000000..bc812098 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/IMultiTenancyFeature.cs @@ -0,0 +1,10 @@ +namespace Backend.Fx.Features +{ + /// + /// Marks a to add behavior in case of multi tenancy enabled + /// + public interface IMultiTenancyFeature + { + void EnableMultiTenancyServices(IBackendFxApplication application); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/HiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/HiLoIdGenerator.cs similarity index 90% rename from src/abstractions/Backend.Fx/Extensions/Persistence/HiLoIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/IdGeneration/HiLoIdGenerator.cs index d098f26a..929aeb51 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/HiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/HiLoIdGenerator.cs @@ -1,16 +1,15 @@ using System.Threading; -using Backend.Fx.Domain; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.IdGeneration { public abstract class HiLoIdGenerator : IIdGenerator { private static readonly ILogger Logger = Log.Create(); private int _highId = -1; private int _lowId = -1; - private static readonly object Mutex = new object(); + private static readonly object Mutex = new(); private readonly bool _isTraceEnabled; protected HiLoIdGenerator() diff --git a/src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/IEntityIdGenerator.cs similarity index 60% rename from src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/IdGeneration/IEntityIdGenerator.cs index 302da473..d6b0b165 100644 --- a/src/abstractions/Backend.Fx/Domain/IEntityIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/IEntityIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Domain +namespace Backend.Fx.Features.IdGeneration { public interface IEntityIdGenerator : IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Domain/IIdGenerator.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/IIdGenerator.cs similarity index 60% rename from src/abstractions/Backend.Fx/Domain/IIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/IdGeneration/IIdGenerator.cs index b2282f71..c1397f86 100644 --- a/src/abstractions/Backend.Fx/Domain/IIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/IIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Domain +namespace Backend.Fx.Features.IdGeneration { public interface IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/ISequence.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/ISequence.cs similarity index 80% rename from src/abstractions/Backend.Fx/Extensions/Persistence/ISequence.cs rename to src/abstractions/Backend.Fx/Features/IdGeneration/ISequence.cs index c612abb5..403f0fd9 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/ISequence.cs +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/ISequence.cs @@ -1,6 +1,6 @@ using JetBrains.Annotations; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.IdGeneration { [PublicAPI] public interface ISequence diff --git a/src/abstractions/Backend.Fx/Features/IdGeneration/IdGenerationFeature.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/IdGenerationFeature.cs new file mode 100644 index 00000000..31146ece --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/IdGenerationFeature.cs @@ -0,0 +1,37 @@ +using Backend.Fx.DependencyInjection; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.IdGeneration +{ + [PublicAPI] + public class IdGenerationFeature : Feature + { + private readonly IEntityIdGenerator _entityIdGenerator; + + public IdGenerationFeature(IEntityIdGenerator entityIdGenerator) + { + _entityIdGenerator = entityIdGenerator; + } + + public override void Enable(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new IdGenerationModule(_entityIdGenerator)); + } + } + + public class IdGenerationModule : IModule + { + private readonly IEntityIdGenerator _entityIdGenerator; + + public IdGenerationModule(IEntityIdGenerator entityIdGenerator) + { + _entityIdGenerator = entityIdGenerator; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.Register(ServiceDescriptor.Singleton(_entityIdGenerator)); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/IdGeneration/InMem/InMemorySequence.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/InMem/InMemorySequence.cs new file mode 100644 index 00000000..06a86eb4 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/InMem/InMemorySequence.cs @@ -0,0 +1,27 @@ +namespace Backend.Fx.Features.IdGeneration.InMem +{ + public class InMemorySequence : ISequence + { + private int _currentValue = 1; + + public InMemorySequence(int increment = 1) + { + Increment = increment; + } + + public void EnsureSequence() + { } + + public int GetNextValue() + { + lock (this) + { + int nextValue = _currentValue; + _currentValue += Increment; + return nextValue; + } + } + + public int Increment { get; } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceHiLoIdGenerator.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/SequenceHiLoIdGenerator.cs similarity index 89% rename from src/abstractions/Backend.Fx/Extensions/Persistence/SequenceHiLoIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/IdGeneration/SequenceHiLoIdGenerator.cs index 3ce90dde..abbf5274 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceHiLoIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/SequenceHiLoIdGenerator.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.IdGeneration { public class SequenceHiLoIdGenerator : HiLoIdGenerator { diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceIdGenerator.cs b/src/abstractions/Backend.Fx/Features/IdGeneration/SequenceIdGenerator.cs similarity index 81% rename from src/abstractions/Backend.Fx/Extensions/Persistence/SequenceIdGenerator.cs rename to src/abstractions/Backend.Fx/Features/IdGeneration/SequenceIdGenerator.cs index c861a4ef..4ab96cf2 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/SequenceIdGenerator.cs +++ b/src/abstractions/Backend.Fx/Features/IdGeneration/SequenceIdGenerator.cs @@ -1,6 +1,4 @@ -using Backend.Fx.Domain; - -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.IdGeneration { public class SequenceIdGenerator : IIdGenerator { diff --git a/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs b/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs deleted file mode 100644 index 9e2670d4..00000000 --- a/src/abstractions/Backend.Fx/Features/Jobs/ApplicationWithJobs.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Security.Principal; -using System.Threading.Tasks; -using Backend.Fx.ExecutionPipeline; -using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; - -namespace Backend.Fx.Features.Jobs -{ - public static class JobsFeature - { - /// - /// The feature "Jobs" makes sure, that all implementations of are injected as scoped instances. - /// You can use to execute any job later on, using a scheduler or other triggers. - /// - /// - [PublicAPI] - public static void EnableJobs(IBackendFxApplication application) - { - application.CompositionRoot.RegisterModules(new JobModule(application.Assemblies)); - } - - /// - /// Runs the given job. - /// - /// - /// The identity who should run the job. Defaults to when omitted. - /// - [PublicAPI] - public static async Task RunJob(this IBackendFxApplication application, IIdentity identity = null) - where TJob : IJob - { - identity = identity ?? new SystemIdentity(); - await application - .Invoker - .InvokeAsync(async sp => - await sp.GetRequiredService().RunAsync().ConfigureAwait(false), identity) - .ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/BackendFxApplicationEx.cs b/src/abstractions/Backend.Fx/Features/Jobs/BackendFxApplicationEx.cs new file mode 100644 index 00000000..aaabbc5d --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Jobs/BackendFxApplicationEx.cs @@ -0,0 +1,16 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.Jobs +{ + public static class BackendFxApplicationEx + { + public static async Task ExecuteJob(this IBackendFxApplication application, CancellationToken cancellationToken = default) + where TJob : IJob + { + var jobExecutor = application.CompositionRoot.ServiceProvider.GetRequiredService(); + await jobExecutor.ExecuteAsync(application, cancellationToken: cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/ForEachTenantJobExecutor.cs b/src/abstractions/Backend.Fx/Features/Jobs/ForEachTenantJobExecutor.cs new file mode 100644 index 00000000..4bf12284 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Jobs/ForEachTenantJobExecutor.cs @@ -0,0 +1,35 @@ +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Features.MultiTenancy; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.Jobs +{ + public class ForEachTenantJobExecutor : IJobExecutor + { + private readonly ITenantEnumerator _tenantEnumerator; + + public ForEachTenantJobExecutor( + ITenantEnumerator tenantEnumerator, + IJobExecutor _) + { + _tenantEnumerator = tenantEnumerator; + } + + public async Task ExecuteAsync( + IBackendFxApplication application, + IIdentity identity = null, + CancellationToken cancellationToken = default) + where TJob : IJob + { + var tenantWideMutexManager = + application.CompositionRoot.ServiceProvider.GetRequiredService(); + + await new ForEachTenantIdInvoker(_tenantEnumerator.GetActiveTenantIds(), tenantWideMutexManager, typeof(TJob).FullName, application.Invoker).InvokeAsync( + async sp => await sp.GetRequiredService().RunAsync(cancellationToken).ConfigureAwait(false), + identity ?? new SystemIdentity()).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs b/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs index 3ee01409..4f9f5aad 100644 --- a/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/IJob.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Backend.Fx.Features.Jobs { @@ -7,6 +8,6 @@ namespace Backend.Fx.Features.Jobs /// public interface IJob { - Task RunAsync(); + Task RunAsync(CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/IJobExecutor.cs b/src/abstractions/Backend.Fx/Features/Jobs/IJobExecutor.cs new file mode 100644 index 00000000..1b8ce3fb --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Jobs/IJobExecutor.cs @@ -0,0 +1,36 @@ +using System.Security.Principal; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.Jobs +{ + public interface IJobExecutor + { + Task ExecuteAsync(IBackendFxApplication application, + IIdentity identity = null, + CancellationToken cancellationToken = default) + where TJob : IJob; + } + + [UsedImplicitly] + public class JobExecutor : IJobExecutor + { + public async Task ExecuteAsync(IBackendFxApplication application, + IIdentity identity = null, + CancellationToken cancellationToken = default) + where TJob : IJob + { + await application + .Invoker + .InvokeAsync(async sp => + await sp + .GetRequiredService() + .RunAsync(cancellationToken) + .ConfigureAwait(false) + , identity) + .ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/JobsFeature.cs b/src/abstractions/Backend.Fx/Features/Jobs/JobsFeature.cs new file mode 100644 index 00000000..20529d98 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Jobs/JobsFeature.cs @@ -0,0 +1,21 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Jobs +{ + /// + /// The feature "Jobs" makes sure, that all implementations of are injected as scoped instances. + /// + [PublicAPI] + public class JobsFeature : Feature, IMultiTenancyFeature + { + public override void Enable(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new JobsModule(application.Assemblies)); + } + + public void EnableMultiTenancyServices(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new MultiTenancyJobsModule()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs b/src/abstractions/Backend.Fx/Features/Jobs/JobsModule.cs similarity index 57% rename from src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs rename to src/abstractions/Backend.Fx/Features/Jobs/JobsModule.cs index 4cb9811a..408aafe2 100644 --- a/src/abstractions/Backend.Fx/Features/Jobs/JobModule.cs +++ b/src/abstractions/Backend.Fx/Features/Jobs/JobsModule.cs @@ -6,11 +6,11 @@ namespace Backend.Fx.Features.Jobs { - internal class JobModule : IModule + internal class JobsModule : IModule { private readonly Assembly[] _assemblies; - public JobModule(Assembly[] assemblies) + public JobsModule(Assembly[] assemblies) { _assemblies = assemblies; } @@ -23,6 +23,18 @@ public void Register(ICompositionRoot compositionRoot) compositionRoot.Register( new ServiceDescriptor(jobType, jobType, ServiceLifetime.Scoped)); } + + compositionRoot.Register( + ServiceDescriptor.Singleton()); + } + } + + internal class MultiTenancyJobsModule : IModule + { + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.RegisterDecorator( + ServiceDescriptor.Singleton()); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationEventHandler.cs similarity index 76% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationEventHandler.cs index 1395b350..bb658a2e 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/IIntegrationEventHandler.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IIntegrationEventHandler.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { internal interface IIntegrationEventHandler { diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/InProc/InProcMessageBus.cs similarity index 94% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/InProc/InProcMessageBus.cs index 52a185b6..58269cb4 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBus.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/InProc/InProcMessageBus.cs @@ -1,12 +1,12 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Backend.Fx.Extensions.MessageBus.InProc +namespace Backend.Fx.Features.MessageBus.InProc { public class InProcMessageBus : MessageBus { private readonly InProcMessageBusChannel _channel; - private readonly HashSet _subscribedEventTypeNames = new HashSet(); + private readonly HashSet _subscribedEventTypeNames = new(); public InProcMessageBus() { diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBusChannel.cs b/src/abstractions/Backend.Fx/Features/MessageBus/InProc/InProcMessageBusChannel.cs similarity index 92% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBusChannel.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/InProc/InProcMessageBusChannel.cs index 00258ebc..5a9ed897 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/InProc/InProcMessageBusChannel.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/InProc/InProcMessageBusChannel.cs @@ -2,11 +2,11 @@ using System.Collections.Concurrent; using System.Threading.Tasks; -namespace Backend.Fx.Extensions.MessageBus.InProc +namespace Backend.Fx.Features.MessageBus.InProc { public class InProcMessageBusChannel { - private readonly ConcurrentBag _messageHandlingTasks = new ConcurrentBag(); + private readonly ConcurrentBag _messageHandlingTasks = new(); internal event EventHandler MessageReceived; diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs similarity index 92% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs index a12c5b86..87b9e733 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEvent.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEvent.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; using NodaTime; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { [PublicAPI] public interface IIntegrationEvent @@ -28,6 +28,6 @@ public abstract class IntegrationEvent : IIntegrationEvent public Guid CorrelationId { get; internal set; } - public Dictionary Properties { get; } = new Dictionary(); + public Dictionary Properties { get; } = new(); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEventHandlingInvoker.cs similarity index 92% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEventHandlingInvoker.cs index 6fb40984..d213c6a6 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventHandlingInvoker.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEventHandlingInvoker.cs @@ -5,14 +5,14 @@ using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { /// /// Ensures events to be handled sequentially and catches all exceptions. /// public class IntegrationEventHandlingInvoker : IBackendFxApplicationInvoker { - private readonly object _syncLock = new object(); + private readonly object _syncLock = new(); private readonly IExceptionLogger _exceptionLogger; private readonly IBackendFxApplicationInvoker _invoker; diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEventSerializer.cs similarity index 96% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEventSerializer.cs index 6adb6edc..387fe1e2 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/IntegrationEventSerializer.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/IntegrationEventSerializer.cs @@ -3,7 +3,7 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { public interface IIntegrationEventSerializer { diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs similarity index 98% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs index 2192c702..9eca9ed8 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBus.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBus.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { [PublicAPI] public abstract class MessageBus diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusFeature.cs similarity index 55% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBusFeature.cs index 4375499e..1ca89f2f 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusExtension.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusFeature.cs @@ -1,33 +1,23 @@ using System.Threading; using System.Threading.Tasks; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { /// /// The extension "Message Bus" adds integration message sending and handling of received integration messages to the /// application. If the feature "Multi Tenancy" has been activated, this feature takes care of adding a tenant id /// to all outgoing messages and handling incoming messages in the respective tenant. /// - public class MessageBusExtension : BackendFxApplicationExtension + public class MessageBusFeature : Feature, IBootableFeature, IMultiTenancyFeature { private readonly MessageBus _messageBus; - private readonly MessageBusModule _messageBusModule; + private MessageBusModule _messageBusModule; - public MessageBusExtension(MessageBus messageBus, IBackendFxApplication application) - : base(application) + public MessageBusFeature(MessageBus messageBus) { - _messageBusModule = new MessageBusModule(messageBus, application); - application.CompositionRoot.RegisterModules(_messageBusModule); _messageBus = messageBus; - } - public override async Task BootAsync(CancellationToken cancellationToken = default) - { - await base.BootAsync(cancellationToken).ConfigureAwait(false); - _messageBus.Connect(); - _messageBusModule.SubscribeToAllEvents(); - } protected override void Dispose(bool disposing) { @@ -37,5 +27,23 @@ protected override void Dispose(bool disposing) } base.Dispose(disposing); } + + public override void Enable(IBackendFxApplication application) + { + _messageBusModule = new MessageBusModule(_messageBus, application); + application.CompositionRoot.RegisterModules(_messageBusModule); + } + + public Task BootAsync(IBackendFxApplication application, CancellationToken cancellationToken = default) + { + _messageBus.Connect(); + _messageBusModule.SubscribeToAllEvents(); + return Task.CompletedTask; + } + + public void EnableMultiTenancyServices(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new MultiTenancyMessageBusModule()); + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs similarity index 87% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs index fdd0aedc..eec2f0de 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusModule.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusModule.cs @@ -8,14 +8,14 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { internal class MessageBusModule : IModule { private static readonly ILogger Logger = Log.Create(); private readonly MessageBus _messageBus; private readonly IBackendFxApplication _application; - private readonly List _eventTypesToSubscribe = new List(); + private readonly List _eventTypesToSubscribe = new(); public MessageBusModule(MessageBus messageBus, IBackendFxApplication application) { @@ -47,14 +47,6 @@ public void Register(ICompositionRoot compositionRoot) ServiceDescriptor.Scoped, CurrentCorrelationHolder>()); RegisterIntegrationEventHandlers(compositionRoot); - - // support multi tenancy, when available - if ((_application as BackendFxApplication)?.IsMultiTenancyApplication == true) - { - // enrich the integration event with a TenantId property - compositionRoot.RegisterDecorator( - ServiceDescriptor.Scoped()); - } } public void SubscribeToAllEvents() @@ -91,4 +83,14 @@ private void RegisterIntegrationEventHandlers(ICompositionRoot compositionRoot) } } } + + internal class MultiTenancyMessageBusModule : IModule + { + public void Register(ICompositionRoot compositionRoot) + { + // enrich the integration event with a TenantId property + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); + } + } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusScope.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs similarity index 93% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusScope.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs index 15435c92..889d70b8 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/MessageBusScope.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MessageBusScope.cs @@ -3,7 +3,7 @@ using Backend.Fx.ExecutionPipeline; using Backend.Fx.Util; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { public interface IMessageBusScope { @@ -19,7 +19,7 @@ public interface IMessageBusScope internal class MessageBusScope : IMessageBusScope { - private readonly ConcurrentQueue _integrationEvents = new ConcurrentQueue(); + private readonly ConcurrentQueue _integrationEvents = new(); private readonly MessageBus _messageBus; private readonly ICurrentTHolder _correlationHolder; diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs b/src/abstractions/Backend.Fx/Features/MessageBus/MultiTenantIntegrationEventSerializer.cs similarity index 97% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/MultiTenantIntegrationEventSerializer.cs index d7409645..3dd6cb81 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/MultiTenantIntegrationEventSerializer.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/MultiTenantIntegrationEventSerializer.cs @@ -4,7 +4,7 @@ using Backend.Fx.Features.MultiTenancy; using Backend.Fx.Util; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { public class MultiTenantIntegrationEventSerializer : IIntegrationEventSerializer { diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs b/src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs similarity index 95% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs index dd992fbe..76497378 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/RaiseIntegrationEventsWhenOperationCompleted.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { [UsedImplicitly] internal class RaiseIntegrationEventsWhenOperationCompleted : IOperation diff --git a/src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs b/src/abstractions/Backend.Fx/Features/MessageBus/SerializedMessage.cs similarity index 88% rename from src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs rename to src/abstractions/Backend.Fx/Features/MessageBus/SerializedMessage.cs index 2dbae042..1bcc2aa5 100644 --- a/src/abstractions/Backend.Fx/Extensions/MessageBus/SerializedMessage.cs +++ b/src/abstractions/Backend.Fx/Features/MessageBus/SerializedMessage.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Extensions.MessageBus +namespace Backend.Fx.Features.MessageBus { public struct SerializedMessage { diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs index 7f767237..952f26fe 100644 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/CurrentTenantIdHolder.cs @@ -1,10 +1,11 @@ -using Backend.Fx.ExecutionPipeline; -using Backend.Fx.Util; +using Backend.Fx.Util; +using JetBrains.Annotations; namespace Backend.Fx.Features.MultiTenancy { public class CurrentTenantIdHolder : CurrentTHolder { + [UsedImplicitly] public CurrentTenantIdHolder() { } @@ -30,17 +31,7 @@ public override TenantId ProvideInstance() protected override string Describe(TenantId instance) { - if (instance == null) - { - return ""; - } - - if (instance.HasValue) - { - return $"TenantId: {instance.Value}"; - } - - return "TenantId: null"; + return instance.HasValue ? $"TenantId: {instance.Value}" : "TenantId: null"; } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/ForEachTenantIdInvoker.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/ForEachTenantIdInvoker.cs new file mode 100644 index 00000000..f10677d1 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/ForEachTenantIdInvoker.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Security.Principal; +using System.Threading.Tasks; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Util; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.MultiTenancy +{ + public class ForEachTenantIdInvoker : IBackendFxApplicationInvoker + { + private readonly IEnumerable _tenantIds; + private readonly ITenantWideMutexManager _tenantWideMutexManager; + private readonly string _mutexKey; + private readonly IBackendFxApplicationInvoker _invoker; + + public ForEachTenantIdInvoker( + IEnumerable tenantIds, + ITenantWideMutexManager tenantWideMutexManager, + string mutexKey, + IBackendFxApplicationInvoker invoker) + { + _tenantIds = tenantIds; + _tenantWideMutexManager = tenantWideMutexManager; + _mutexKey = mutexKey; + _invoker = invoker; + } + + public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identity) + { + foreach (var tenantId in _tenantIds) + { + if (_tenantWideMutexManager.TryAcquire(tenantId, _mutexKey, out var mutex)) + { + try + { + await _invoker.InvokeAsync(sp => + { + sp.GetRequiredService>().ReplaceCurrent(tenantId); + return awaitableAsyncAction(sp); + }, identity).ConfigureAwait(false); + } + finally + { + mutex.Dispose(); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantEnumerator.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantEnumerator.cs new file mode 100644 index 00000000..78e2feb0 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantEnumerator.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Backend.Fx.Features.MultiTenancy +{ + public interface ITenantEnumerator + { + IEnumerable GetActiveDemoTenantIds(); + IEnumerable GetActiveProductiveTenantIds(); + IEnumerable GetActiveTenantIds(); + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/InMemoryTenantWideMutexManager.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/InProc/InProcTenantWideMutexManager.cs similarity index 72% rename from src/abstractions/Backend.Fx/Features/MultiTenancy/InMemoryTenantWideMutexManager.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancy/InProc/InProcTenantWideMutexManager.cs index 3fdb12f5..9a61e13e 100644 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/InMemoryTenantWideMutexManager.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/InProc/InProcTenantWideMutexManager.cs @@ -3,18 +3,17 @@ using System.Threading; using JetBrains.Annotations; -namespace Backend.Fx.Features.MultiTenancy +namespace Backend.Fx.Features.MultiTenancy.InProc { /// /// If an instance of this class is being hold as singleton it can be used to manage tenant wide /// mutexes in a single process. When multiple processes are running, another locking mechanism /// must be implemented (e.g. using MS SQL`s sp_getapplock or Postgres` advisory locks) /// - public class InMemoryTenantWideMutexManager : ITenantWideMutexManager + public class InProcTenantWideMutexManager : ITenantWideMutexManager { - private readonly ConcurrentDictionary> _mutexes = - new ConcurrentDictionary>(); - + private readonly ConcurrentDictionary> _mutexes = new(); + public bool TryAcquire(TenantId tenantId, string key, out ITenantWideMutex tenantWideMutex) { if (!tenantId.HasValue) @@ -25,10 +24,10 @@ public bool TryAcquire(TenantId tenantId, string key, out ITenantWideMutex tenan lock (this) { var subDictionary = _mutexes.GetOrAdd(tenantId.Value, i => new ConcurrentDictionary()); - var mutex = subDictionary.GetOrAdd(key, s => new Mutex()); + var mutex = subDictionary.GetOrAdd(key, _ => new Mutex()); if (mutex.WaitOne(300)) { - tenantWideMutex = new InMemoryTenantWideMutex(() => mutex.ReleaseMutex()); + tenantWideMutex = new InProcTenantWideMutex(() => mutex.ReleaseMutex()); return true; } @@ -36,16 +35,16 @@ public bool TryAcquire(TenantId tenantId, string key, out ITenantWideMutex tenan return false; } } - - private class InMemoryTenantWideMutex : ITenantWideMutex + + private class InProcTenantWideMutex : ITenantWideMutex { private readonly Action _dispose; - public InMemoryTenantWideMutex([NotNull] Action dispose) + public InProcTenantWideMutex([NotNull] Action dispose) { _dispose = dispose ?? throw new ArgumentNullException(nameof(dispose)); } - + public void Dispose() { _dispose.Invoke(); diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs deleted file mode 100644 index e6d72216..00000000 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyFeature.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Backend.Fx.ExecutionPipeline; -using Backend.Fx.Extensions.Persistence; -using Backend.Fx.Util; -using JetBrains.Annotations; - -namespace Backend.Fx.Features.MultiTenancy -{ - /// - /// The feature "Multi Tenancy" makes you provide an implementation of and - /// takes care of asking it for the current tenant id and setting it in the - /// on every operation. - /// - /// - [PublicAPI] - public static class MultiTenancyFeature - { - public static void EnableMultiTenancy (this IBackendFxApplication application) - where T : class, ICurrentTenantIdSelector - { - application.CompositionRoot.RegisterModules( - new MultiTenancyModule(application.As() != null)); - - ((BackendFxApplication)application).IsMultiTenancyApplication = true; - } - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs index 12a8e2d4..1f8ed352 100644 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/MultiTenancyModule.cs @@ -1,4 +1,3 @@ -using System.Linq; using Backend.Fx.DependencyInjection; using Backend.Fx.ExecutionPipeline; using Backend.Fx.Util; @@ -6,36 +5,34 @@ namespace Backend.Fx.Features.MultiTenancy { - public class MultiTenancyModule : IModule + internal class MultiTenancyModule : IModule where TCurrentTenantIdSelector : class, ICurrentTenantIdSelector - { - private readonly bool _isPersistentApplication; + private readonly ITenantEnumerator _tenantEnumerator; + private readonly ITenantWideMutexManager _tenantWideMutexManager; - public MultiTenancyModule(bool isPersistentApplication) + public MultiTenancyModule(ITenantEnumerator tenantEnumerator, ITenantWideMutexManager tenantWideMutexManager) { - _isPersistentApplication = isPersistentApplication; + _tenantEnumerator = tenantEnumerator; + _tenantWideMutexManager = tenantWideMutexManager; } public void Register(ICompositionRoot compositionRoot) { compositionRoot.Register( - ServiceDescriptor.Scoped()); + ServiceDescriptor.Singleton(_tenantEnumerator)); + + compositionRoot.Register( + ServiceDescriptor.Singleton(_tenantWideMutexManager)); + compositionRoot.Register( + ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator( ServiceDescriptor.Scoped()); - + compositionRoot.Register( ServiceDescriptor.Scoped, CurrentTenantIdHolder>()); - - if (_isPersistentApplication) - { - compositionRoot.RegisterDecorator( - ServiceDescriptor.Scoped(typeof(IQueryable<>), typeof(TenantFilteredQueryable<>))); - - // compositionRoot.Register( - // ServiceDescriptor.Scoped(typeof(ITenantFilterExpressionFactory<>),typeof(TTenantFilterExpressionFactory<>))); - } } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs deleted file mode 100644 index 7d151b03..00000000 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantFilteredQueryable.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using Backend.Fx.Domain; - -namespace Backend.Fx.Features.MultiTenancy -{ - internal class TenantFilteredQueryable : IQueryable where TAggregateRoot : AggregateRoot - { - private readonly ITenantFilterExpressionFactory _tenantFilterExpressionFactory; - private readonly IQueryable _queryable; - - public TenantFilteredQueryable(ITenantFilterExpressionFactory tenantFilterExpressionFactory, IQueryable queryable) - { - _tenantFilterExpressionFactory = tenantFilterExpressionFactory; - _queryable = queryable; - } - - public IEnumerator GetEnumerator() - { - return _queryable.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_queryable).GetEnumerator(); - } - - public Type ElementType => _queryable.ElementType; - - public Expression Expression => Expression.And(_queryable.Expression, _tenantFilterExpressionFactory.CreateTenantFilterExpression()); - - public IQueryProvider Provider => _queryable.Provider; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs index 1fc1056b..4c646618 100644 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancy/TenantId.cs @@ -4,7 +4,7 @@ namespace Backend.Fx.Features.MultiTenancy { [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public class TenantId + public readonly struct TenantId { private readonly int? _id; @@ -31,7 +31,7 @@ public int Value public bool HasValue => _id.HasValue; - protected string DebuggerDisplay + public string DebuggerDisplay { get { @@ -40,7 +40,7 @@ protected string DebuggerDisplay return $"TenantId: {Value}"; } - return "TenantId: null"; + return "TenantId: NULL"; } } @@ -51,18 +51,50 @@ public override string ToString() public override bool Equals(object obj) { - if (ReferenceEquals(this, obj)) return true; if (ReferenceEquals(null, obj)) return false; if (GetType() != obj.GetType()) return false; - return Equals(Value, (obj as TenantId)?.Value); + if (obj is TenantId tenantId) + { + if (!HasValue && !tenantId.HasValue) + { + return true; + } + + if (HasValue && tenantId.HasValue) + { + return Equals(Value, tenantId.Value); + } + } + + return false; } public override int GetHashCode() { - return HasValue ? Value.GetHashCode() : 17.GetHashCode(); + return HasValue ? Value.GetHashCode() : 487623523.GetHashCode(); } public static explicit operator int(TenantId tid) => tid.Value; - public static explicit operator TenantId(int id) => new TenantId(id); + public static explicit operator TenantId(int id) => new(id); + + public static bool operator ==(TenantId left, TenantId right) + { + if (!left.HasValue && !right.HasValue) + { + return true; + } + + if (left.HasValue && right.HasValue) + { + return Equals(left.Value, right.Value); + } + + return false; + } + + public static bool operator !=(TenantId left, TenantId right) + { + return !(left == right); + } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/DirectTenantEnumerator.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/DirectTenantEnumerator.cs new file mode 100644 index 00000000..25cf24b2 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/DirectTenantEnumerator.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using Backend.Fx.Features.MultiTenancy; + +namespace Backend.Fx.Features.MultiTenancyAdmin +{ + public class DirectTenantEnumerator : ITenantEnumerator + { + private readonly ITenantRepository _tenantRepository; + + public DirectTenantEnumerator(ITenantRepository tenantRepository) + { + _tenantRepository = tenantRepository; + } + public IEnumerable GetActiveDemoTenantIds() + { + return _tenantRepository.GetTenants().Where(t => t.IsActive && t.IsDemoTenant).Select(t => t.TenantId).ToArray(); + } + + public IEnumerable GetActiveProductiveTenantIds() + { + return _tenantRepository.GetTenants().Where(t => t.IsActive && !t.IsDemoTenant).Select(t => t.TenantId).ToArray(); + } + + public IEnumerable GetActiveTenantIds() + { + return _tenantRepository.GetTenants().Where(t => t.IsActive).Select(t => t.TenantId).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/ITenantRepository.cs similarity index 69% rename from src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/ITenantRepository.cs index 516b5c9b..1350fb26 100644 --- a/src/features/Backend.Fx.Features.MultiTenancy.Admin/ITenantRepository.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/ITenantRepository.cs @@ -1,4 +1,4 @@ -namespace Backend.Fx.Features.TenantsAdmin +namespace Backend.Fx.Features.MultiTenancyAdmin { public interface ITenantRepository { @@ -9,5 +9,7 @@ public interface ITenantRepository Tenant GetTenant(int tenantId); void DeleteTenant(int tenantId); + + int GetNextTenantId(); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/InMem/InMemoryTenantRepository.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/InMem/InMemoryTenantRepository.cs new file mode 100644 index 00000000..fe014b39 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/InMem/InMemoryTenantRepository.cs @@ -0,0 +1,43 @@ +using System.Collections.Concurrent; +using System.Linq; +using Backend.Fx.Exceptions; + +namespace Backend.Fx.Features.MultiTenancyAdmin.InMem +{ + public class InMemoryTenantRepository : ITenantRepository + { + private readonly ConcurrentDictionary _tenantsDictionary = new(); + + public void SaveTenant(Tenant tenant) + { + _tenantsDictionary[tenant.Id] = tenant; + } + + public Tenant[] GetTenants() + { + return _tenantsDictionary.Values.ToArray(); + } + + public Tenant GetTenant(int tenantId) + { + return _tenantsDictionary.ContainsKey(tenantId) + ? _tenantsDictionary[tenantId] + : throw new NotFoundException(tenantId); + } + + public void DeleteTenant(int tenantId) + { + _tenantsDictionary.TryRemove(tenantId, out _); + } + + public int GetNextTenantId() + { + if (_tenantsDictionary.IsEmpty) + { + return 1; + } + + return _tenantsDictionary.Keys.Max() + 1; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminFeature.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminFeature.cs new file mode 100644 index 00000000..c7a22b0f --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminFeature.cs @@ -0,0 +1,40 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.MultiTenancyAdmin +{ + [PublicAPI] + public class MultiTenancyAdminFeature : Feature, IBootableFeature + { + private readonly ITenantRepository _tenantRepository; + private readonly bool _ensureDemoTenantOnBoot; + + public MultiTenancyAdminFeature(ITenantRepository tenantRepository, bool ensureDemoTenantOnBoot = false) + { + _tenantRepository = tenantRepository; + _ensureDemoTenantOnBoot = ensureDemoTenantOnBoot; + } + + public override void Enable(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(new MultiTenancyAdminModule(_tenantRepository)); + } + + public Task BootAsync(IBackendFxApplication application, CancellationToken cancellationToken = default) + { + if (_ensureDemoTenantOnBoot) + { + var tenantService = application.CompositionRoot.ServiceProvider.GetRequiredService(); + if (!tenantService.GetTenants().Any(t => t.IsDemoTenant)) + { + tenantService.CreateTenant("demo", "Demonstration Tenant", true); + } + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminModule.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminModule.cs new file mode 100644 index 00000000..dcc517fc --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/MultiTenancyAdminModule.cs @@ -0,0 +1,21 @@ +using Backend.Fx.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.MultiTenancyAdmin +{ + internal class MultiTenancyAdminModule : IModule + { + private readonly ITenantRepository _tenantRepository; + + public MultiTenancyAdminModule(ITenantRepository tenantRepository) + { + _tenantRepository = tenantRepository; + } + + public void Register(ICompositionRoot compositionRoot) + { + compositionRoot.Register(ServiceDescriptor.Singleton(_tenantRepository)); + compositionRoot.Register(ServiceDescriptor.Singleton()); + } + } +} \ No newline at end of file diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/Tenant.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/Tenant.cs similarity index 68% rename from src/features/Backend.Fx.Features.MultiTenancy.Admin/Tenant.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/Tenant.cs index 9f240cff..aea704b2 100644 --- a/src/features/Backend.Fx.Features.MultiTenancy.Admin/Tenant.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/Tenant.cs @@ -1,8 +1,8 @@ using System; -using System.ComponentModel.DataAnnotations; +using Backend.Fx.Features.MultiTenancy; using JetBrains.Annotations; -namespace Backend.Fx.Features.TenantsAdmin +namespace Backend.Fx.Features.MultiTenancyAdmin { /// /// Represents a tenant in the application @@ -14,9 +14,10 @@ private Tenant() { } - public Tenant([NotNull] string name, string description, bool isDemoTenant, string configuration = null) + public Tenant(int id, [NotNull] string name, string description, bool isDemoTenant, string configuration = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + Id = id; Name = name; Description = description; IsDemoTenant = isDemoTenant; @@ -24,13 +25,13 @@ public Tenant([NotNull] string name, string description, bool isDemoTenant, stri IsActive = true; } - [Key] public int Id { get; set; } + public int Id { get; } - [Required] public string Name { get; set; } + public string Name { get; set; } public string Description { get; set; } - public bool IsDemoTenant { get; set; } + public bool IsDemoTenant { get; } public bool IsActive { get; set; } @@ -38,5 +39,7 @@ public Tenant([NotNull] string name, string description, bool isDemoTenant, stri /// optional: a generic field to store your arbitrary config data /// public string Configuration { get; set; } + + public TenantId TenantId => new(Id); } } \ No newline at end of file diff --git a/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/TenantService.cs similarity index 96% rename from src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs rename to src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/TenantService.cs index c88edfca..8cc7e53f 100644 --- a/src/features/Backend.Fx.Features.MultiTenancy.Admin/TenantService.cs +++ b/src/abstractions/Backend.Fx/Features/MultiTenancyAdmin/TenantService.cs @@ -5,7 +5,7 @@ using JetBrains.Annotations; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Features.TenantsAdmin +namespace Backend.Fx.Features.MultiTenancyAdmin { /// /// Encapsulates the management of tenants @@ -52,7 +52,7 @@ public Tenant CreateTenant(string name, string description, bool isDemonstration throw new ArgumentException($"There is already a tenant named {name}"); } - var tenant = new Tenant(name, description, isDemonstrationTenant) { Configuration = configuration }; + var tenant = new Tenant(_tenantRepository.GetNextTenantId(), name, description, isDemonstrationTenant) { Configuration = configuration }; _tenantRepository.SaveTenant(tenant); return tenant; diff --git a/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/AdoNetPersistenceModule.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/AdoNetPersistenceModule.cs new file mode 100644 index 00000000..7b055b21 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/AdoNetPersistenceModule.cs @@ -0,0 +1,39 @@ +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Features.DomainEvents; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.Persistence.AdoNet +{ + public abstract class AdoNetPersistenceModule : PersistenceModule + { + private readonly IDbConnectionFactory _dbConnectionFactory; + + protected AdoNetPersistenceModule(IDbConnectionFactory dbConnectionFactory) + { + _dbConnectionFactory = dbConnectionFactory; + } + + protected override void RegisterInfrastructure(ICompositionRoot compositionRoot) + { + // the DbConnectionFactory is registered as a singleton + compositionRoot.Register(ServiceDescriptor.Singleton(_dbConnectionFactory)); + + // by letting the container create the connection we can be sure, that only one connection per scope is used, and disposing is done accordingly + compositionRoot.Register(ServiceDescriptor.Scoped(_ => _dbConnectionFactory.Create())); + + // wrapping the operation: + // invoke -> connection.open -> transaction.begin ---+ + // | + // v + // operation + // | + // v + // flush + // | + // end invoke <- connection.close <- transaction.commit <-+ + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/DbConnectionOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/DbConnectionOperationDecorator.cs similarity index 61% rename from src/abstractions/Backend.Fx/Extensions/Persistence/DbConnectionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/AdoNet/DbConnectionOperationDecorator.cs index 53536708..f19d9c5f 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/DbConnectionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/DbConnectionOperationDecorator.cs @@ -2,50 +2,51 @@ using System.Data; using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.Persistence.AdoNet { + [UsedImplicitly] public class DbConnectionOperationDecorator : IOperation { private static readonly ILogger Logger = Log.Create(); - private IDisposable _connectionLifetimeLogger; + private IDisposable _connectionLifetimeLogger; + private readonly IOperation _operation; + private readonly IDbConnection _dbConnection; + public DbConnectionOperationDecorator(IDbConnection dbConnection, IOperation operation) { - DbConnection = dbConnection; - Operation = operation; + _dbConnection = dbConnection; + _operation = operation; } - public IOperation Operation { get; } - - public IDbConnection DbConnection { get; } - public void Begin(IServiceScope serviceScope) { Logger.LogDebug("Opening database connection"); - DbConnection.Open(); + _dbConnection.Open(); _connectionLifetimeLogger = Logger.LogDebugDuration("Database connection open", "Database connection closed"); - Operation.Begin(serviceScope); + _operation.Begin(serviceScope); } public void Complete() { - Operation.Complete(); + _operation.Complete(); Logger.LogDebug("Closing database connection"); - DbConnection.Close(); + _dbConnection.Close(); _connectionLifetimeLogger?.Dispose(); } public void Cancel() { - Operation.Cancel(); + _operation.Cancel(); Logger.LogDebug("Closing database connection"); - DbConnection.Close(); + _dbConnection.Close(); _connectionLifetimeLogger?.Dispose(); - // note: we do not dispose the DbConnection here, because we did not instantiate it. Disposing is always up to the creator of - // the instance, that is in this case the injection container. + // note: we do not dispose the DbConnection here, because we did not instantiate it. Disposing is always + // up to the creator of the instance, that is in this case the injection container. } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/DbTransactionOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/DbTransactionOperationDecorator.cs similarity index 97% rename from src/abstractions/Backend.Fx/Extensions/Persistence/DbTransactionOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/AdoNet/DbTransactionOperationDecorator.cs index fcf32ee1..7fcdd68e 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/DbTransactionOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/DbTransactionOperationDecorator.cs @@ -2,15 +2,17 @@ using System.Data; using Backend.Fx.ExecutionPipeline; using Backend.Fx.Logging; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.Persistence.AdoNet { /// /// Enriches the operation to use a database transaction during lifetime. The transaction gets started, before IOperation.Begin() /// is being called and gets committed after IOperation.Complete() is being called. /// + [UsedImplicitly] public class DbTransactionOperationDecorator : IOperation { private static readonly ILogger Logger = Log.Create(); diff --git a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/IDbConnectionFactory.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/IDbConnectionFactory.cs similarity index 67% rename from src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/IDbConnectionFactory.cs rename to src/abstractions/Backend.Fx/Features/Persistence/AdoNet/IDbConnectionFactory.cs index ccb05b17..64501e62 100644 --- a/src/implementations/Backend.Fx.EfCore5Persistence/Bootstrapping/IDbConnectionFactory.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/IDbConnectionFactory.cs @@ -1,6 +1,6 @@ using System.Data; -namespace Backend.Fx.EfCore5Persistence.Bootstrapping +namespace Backend.Fx.Features.Persistence.AdoNet { public interface IDbConnectionFactory { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/MsSqlSequence.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/MsSqlSequence.cs new file mode 100644 index 00000000..432f1aee --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/MsSqlSequence.cs @@ -0,0 +1,66 @@ +using System; +using System.Data; +using Backend.Fx.Features.IdGeneration; +using Backend.Fx.Logging; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Features.Persistence.AdoNet +{ + [PublicAPI] + public abstract class MsSqlSequence : ISequence + { + private static readonly ILogger Logger = Log.Create(); + private readonly IDbConnectionFactory _dbConnectionFactory; + private readonly int _startWith; + + protected MsSqlSequence(IDbConnectionFactory dbConnectionFactory, int startWith = 1) + { + _dbConnectionFactory = dbConnectionFactory; + _startWith = startWith; + } + + protected abstract string SequenceName { get; } + protected virtual string SchemaName { get; } = "dbo"; + + public void EnsureSequence() + { + Logger.LogInformation("Ensuring existence of mssql sequence {SchemaName}.{SequenceName}", SchemaName, SequenceName); + using IDbConnection dbConnection = _dbConnectionFactory.Create(); + dbConnection.Open(); + bool sequenceExists; + using (IDbCommand cmd = dbConnection.CreateCommand()) + { + cmd.CommandText = $"SELECT count(*) FROM sys.sequences seq join sys.schemas s on s.schema_id = seq.schema_id WHERE seq.name = '{SequenceName}' and s.name = '{SchemaName}'"; + sequenceExists = (int) cmd.ExecuteScalar() == 1; + } + + if (sequenceExists) + { + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} exists", SchemaName, SequenceName); + } + else + { + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} does not exist yet and will be created now", SchemaName, SequenceName); + using IDbCommand cmd = dbConnection.CreateCommand(); + cmd.CommandText = $"CREATE SEQUENCE [{SchemaName}].[{SequenceName}] START WITH {_startWith} INCREMENT BY {Increment}"; + cmd.ExecuteNonQuery(); + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} created", SchemaName, SequenceName); + } + } + + public int GetNextValue() + { + using IDbConnection dbConnection = _dbConnectionFactory.Create(); + dbConnection.Open(); + using IDbCommand selectNextValCommand = dbConnection.CreateCommand(); + selectNextValCommand.CommandText = $"SELECT next value FOR {SchemaName}.{SequenceName}"; + var nextValue = Convert.ToInt32(selectNextValCommand.ExecuteScalar()); + Logger.LogDebug("{SchemaName}.{SequenceName} served {NextValue} as next value", SchemaName, SequenceName, nextValue); + + return nextValue; + } + + public abstract int Increment { get; } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/OracleSequence.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/OracleSequence.cs new file mode 100644 index 00000000..031bbdfc --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/OracleSequence.cs @@ -0,0 +1,84 @@ +using System; +using System.Data; +using Backend.Fx.Features.IdGeneration; +using Backend.Fx.Logging; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Features.Persistence.AdoNet +{ + [PublicAPI] + public abstract class OracleSequence : ISequence + { + private static readonly ILogger Logger = Log.Create(); + private readonly IDbConnectionFactory _dbConnectionFactory; + private readonly int _startWith; + + protected OracleSequence(IDbConnectionFactory dbConnectionFactory, int startWith = 1) + { + _dbConnectionFactory = dbConnectionFactory; + _startWith = startWith; + } + + protected abstract string SequenceName { get; } + protected abstract string SchemaName { get; } + + private string SchemaPrefix + { + get + { + if (string.IsNullOrEmpty(SchemaName)) return string.Empty; + + return SchemaName + "."; + } + } + + public void EnsureSequence() + { + Logger.LogInformation("Ensuring existence of oracle sequence {SchemaPrefix}.{SequenceName}", SchemaPrefix, SequenceName); + + using IDbConnection dbConnection = _dbConnectionFactory.Create(); + dbConnection.Open(); + bool sequenceExists; + using (IDbCommand command = dbConnection.CreateCommand()) + { + command.CommandText = $"SELECT count(*) FROM user_sequences WHERE sequence_name = '{SequenceName}'"; + sequenceExists = (decimal)command.ExecuteScalar() == 1; + } + + if (sequenceExists) + { + Logger.LogInformation("Oracle sequence {SchemaPrefix}.{SequenceName} exists", SchemaPrefix, SequenceName); + } + else + { + Logger.LogInformation("Oracle sequence {SchemaPrefix}.{SequenceName} does not exist yet and will be created now", + SchemaPrefix, + SequenceName); + using IDbCommand cmd = dbConnection.CreateCommand(); + cmd.CommandText = $"CREATE SEQUENCE {SchemaPrefix}{SequenceName} START WITH {_startWith} INCREMENT BY {Increment}"; + cmd.ExecuteNonQuery(); + Logger.LogInformation("Oracle sequence {SchemaPrefix}.{SequenceName} created", SchemaPrefix, SequenceName); + } + } + + public int GetNextValue() + { + using IDbConnection dbConnection = _dbConnectionFactory.Create(); + dbConnection.Open(); + + int nextValue; + using IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = $"SELECT {SchemaPrefix}{SequenceName}.NEXTVAL FROM dual"; + nextValue = Convert.ToInt32(command.ExecuteScalar()); + Logger.LogDebug("Oracle sequence {SchemaPrefix}.{SequenceName} served {NextValue} as next value", + SchemaPrefix, + SequenceName, + nextValue); + + return nextValue; + } + + public abstract int Increment { get; } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/PostgresSequence.cs b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/PostgresSequence.cs new file mode 100644 index 00000000..179b6ce1 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/AdoNet/PostgresSequence.cs @@ -0,0 +1,72 @@ +using System; +using System.Data; +using Backend.Fx.Features.IdGeneration; +using Backend.Fx.Logging; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx.Features.Persistence.AdoNet +{ + [PublicAPI] + public abstract class PostgresSequence : ISequence + { + private static readonly ILogger Logger = Log.Create(); + private readonly IDbConnectionFactory _dbConnectionFactory; + private readonly int _startWith; + + protected PostgresSequence(IDbConnectionFactory dbConnectionFactory, int startWith = 1) + { + _dbConnectionFactory = dbConnectionFactory; + _startWith = startWith; + } + + protected abstract string SequenceName { get; } + protected abstract string SchemaName { get; } + + public void EnsureSequence() + { + Logger.LogInformation("Ensuring existence of postgres sequence {SchemaName}.{SequenceName}", SchemaName, SequenceName); + + using IDbConnection dbConnection = _dbConnectionFactory.Create(); + dbConnection.Open(); + bool sequenceExists; + using (IDbCommand command = dbConnection.CreateCommand()) + { + command.CommandText = $"SELECT count(*) FROM information_schema.sequences WHERE sequence_name = '{SequenceName}' AND sequence_schema = '{SchemaName}'"; + sequenceExists = (long) command.ExecuteScalar() == 1L; + } + + if (sequenceExists) + { + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} exists", SchemaName, SequenceName); + } + else + { + Logger.LogInformation( + "Sequence {SchemaName}.{SequenceName} does not exist yet and will be created now", + SchemaName, + SequenceName); + using IDbCommand cmd = dbConnection.CreateCommand(); + cmd.CommandText = $"CREATE SEQUENCE {SchemaName}.{SequenceName} START WITH {_startWith} INCREMENT BY {Increment}"; + cmd.ExecuteNonQuery(); + Logger.LogInformation("Sequence {SchemaName}.{SequenceName} created", SchemaName, SequenceName); + } + } + + public int GetNextValue() + { + using IDbConnection dbConnection = _dbConnectionFactory.Create(); + dbConnection.Open(); + + int nextValue; + using IDbCommand command = dbConnection.CreateCommand(); + command.CommandText = $"SELECT nextval('{SchemaName}.{SequenceName}');"; + nextValue = Convert.ToInt32(command.ExecuteScalar()); + Logger.LogDebug("{SchemaName}.{SequenceName} served {2} as next value", SchemaName, SequenceName, nextValue); + + return nextValue; + } + + public abstract int Increment { get; } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/AggregateQueryable.cs b/src/abstractions/Backend.Fx/Features/Persistence/AggregateQueryable.cs new file mode 100644 index 00000000..dfac2bec --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/AggregateQueryable.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Backend.Fx.Domain; + +namespace Backend.Fx.Features.Persistence +{ + public interface IAggregateQueryable : IQueryable + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable + { + } + + public class AggregateQueryable : IAggregateQueryable + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable + { + private readonly IQueryable _queryable; + + public AggregateQueryable(IQueryable queryable) + { + _queryable = queryable; + } + + public IEnumerator GetEnumerator() + { + return _queryable.GetEnumerator(); + } + + public Type ElementType => _queryable.ElementType; + + public Expression Expression => _queryable.Expression; + + public IQueryProvider Provider => _queryable.Provider; + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/FlushDomainEventAggregatorDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs similarity index 57% rename from src/abstractions/Backend.Fx/Extensions/Persistence/FlushDomainEventAggregatorDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs index 1f4484fd..57f7c080 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/FlushDomainEventAggregatorDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/FlushDomainEventAggregatorDecorator.cs @@ -2,31 +2,26 @@ using Backend.Fx.Logging; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.Persistence { public class FlushDomainEventAggregatorDecorator : IDomainEventAggregator { private static readonly ILogger Logger = Log.Create(); private readonly ICanFlush _canFlush; - private readonly IDomainEventAggregator _domainEventAggregatorImplementation; + private readonly IDomainEventAggregator _domainEventAggregator; - public FlushDomainEventAggregatorDecorator(ICanFlush canFlush, IDomainEventAggregator domainEventAggregatorImplementation) + public FlushDomainEventAggregatorDecorator(ICanFlush canFlush, IDomainEventAggregator domainEventAggregator) { _canFlush = canFlush; - _domainEventAggregatorImplementation = domainEventAggregatorImplementation; - } - - public void PublishDomainEvent(TDomainEvent domainEvent) where TDomainEvent : IDomainEvent - { - _domainEventAggregatorImplementation.PublishDomainEvent(domainEvent); + _domainEventAggregator = domainEventAggregator; } public void RaiseEvents() { Logger.LogDebug("Flushing before raising domain events"); _canFlush.Flush(); - _domainEventAggregatorImplementation.RaiseEvents(); + _domainEventAggregator.RaiseEvents(); } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/FlushOperationDecorator.cs b/src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs similarity index 96% rename from src/abstractions/Backend.Fx/Extensions/Persistence/FlushOperationDecorator.cs rename to src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs index 6740b52d..ac1d964b 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/FlushOperationDecorator.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/FlushOperationDecorator.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.Persistence { public class FlushOperationDecorator : IOperation { diff --git a/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs b/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs new file mode 100644 index 00000000..a783a1c8 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/ICanFlush.cs @@ -0,0 +1,16 @@ +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Persistence +{ + public interface ICanFlush + { + void Flush(); + } + + [UsedImplicitly] + internal class DefaultFlush : ICanFlush + { + public void Flush() + { } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseAvailabilityAwaiter.cs b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs similarity index 80% rename from src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseAvailabilityAwaiter.cs rename to src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs index ae3680f1..40e9b2ce 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseAvailabilityAwaiter.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseAvailabilityAwaiter.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.Persistence { public interface IDatabaseAvailabilityAwaiter { diff --git a/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseBootstrapper.cs b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs similarity index 59% rename from src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseBootstrapper.cs rename to src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs index ddf825b5..2ef8d556 100644 --- a/src/abstractions/Backend.Fx/Extensions/Persistence/IDatabaseBootstrapper.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/IDatabaseBootstrapper.cs @@ -1,12 +1,14 @@ using System; +using System.Threading; +using System.Threading.Tasks; -namespace Backend.Fx.Extensions.Persistence +namespace Backend.Fx.Features.Persistence { /// /// Encapsulates database bootstrapping. This interface hides the implementation details for creating/migrating the database /// public interface IDatabaseBootstrapper : IDisposable { - void EnsureDatabaseExistence(); + Task EnsureDatabaseExistenceAsync(CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/IRepository.cs b/src/abstractions/Backend.Fx/Features/Persistence/IRepository.cs new file mode 100644 index 00000000..100f5609 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/IRepository.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Domain; +using Backend.Fx.Exceptions; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Persistence +{ + /// + /// Encapsulates methods for retrieving domain objects + /// See https://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks + /// + [PublicAPI] + public interface IRepository where TAggregateRoot : IAggregateRoot where TId : struct, IEquatable + { + /// + /// Throws a when nothing matches the given id + /// + /// + /// + /// + Task GetAsync(TId id, CancellationToken cancellationToken = default); + + /// + /// Returns null when nothing matches the given id + /// + /// + /// + /// + Task FindAsync(TId id, CancellationToken cancellationToken = default); + + Task GetAllAsync(CancellationToken cancellationToken = default); + + Task AnyAsync(CancellationToken cancellationToken = default); + + Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default); + + Task DeleteAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default); + + Task AddAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default); + + Task AddRangeAsync(TAggregateRoot[] aggregateRoots, CancellationToken cancellationToken = default); + } + + public static class RepositoryEx + { + public static async Task ResolveAsync( + this IRepository repository, + IEnumerable ids, + CancellationToken cancellationToken = default) + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable + { + var idArray = ids as TId[] ?? ids.ToArray(); + var resolved = new TAggregateRoot[idArray.Length]; + for (var i = 0; i < idArray.Length; i++) + { + resolved[i] = await repository.GetAsync(idArray[i], cancellationToken).ConfigureAwait(false); + } + + return resolved; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs b/src/abstractions/Backend.Fx/Features/Persistence/ITenantFilterExpressionFactory.cs similarity index 57% rename from src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs rename to src/abstractions/Backend.Fx/Features/Persistence/ITenantFilterExpressionFactory.cs index 37295eb8..30d984bc 100644 --- a/src/abstractions/Backend.Fx/Features/MultiTenancy/ITenantFilterExpressionFactory.cs +++ b/src/abstractions/Backend.Fx/Features/Persistence/ITenantFilterExpressionFactory.cs @@ -1,9 +1,11 @@ +using System; using System.Linq.Expressions; using Backend.Fx.Domain; -namespace Backend.Fx.Features.MultiTenancy +namespace Backend.Fx.Features.Persistence { - public interface ITenantFilterExpressionFactory where TAggregateRoot : AggregateRoot + public interface ITenantFilterExpressionFactory where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable { Expression CreateTenantFilterExpression(); } diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionaries.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionaries.cs new file mode 100644 index 00000000..92c6e1bc --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionaries.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Concurrent; +using Backend.Fx.Domain; + +namespace Backend.Fx.Features.Persistence.InMem +{ + public interface IAggregateDictionaries + { + AggregateDictionary Get() + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable; + } + + public class AggregateDictionaries : IAggregateDictionaries + { + private readonly ConcurrentDictionary _aggregateDictionaries = new(); + + public AggregateDictionary Get() + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable + { + var store = (AggregateDictionary)_aggregateDictionaries.GetOrAdd(typeof(TAggregateRoot), + _ => new AggregateDictionary()); + return store; + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionary.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionary.cs new file mode 100644 index 00000000..424f7b73 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/AggregateDictionary.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Backend.Fx.Domain; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Persistence.InMem +{ + public interface IAggregateDictionary : IDictionary where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable + { } + + [UsedImplicitly] + public class AggregateDictionary : IAggregateDictionary + where TAggregateRoot : IAggregateRoot + where TId : struct, IEquatable + { + private readonly IDictionary _dictionary = new Dictionary(); + + public IEnumerator> GetEnumerator() + { + return _dictionary.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) _dictionary).GetEnumerator(); + } + + public void Add(KeyValuePair item) + { + _dictionary.Add(item); + } + + public void Clear() + { + _dictionary.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return _dictionary.Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + _dictionary.CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return _dictionary.Remove(item); + } + + public int Count => _dictionary.Count; + + public bool IsReadOnly => _dictionary.IsReadOnly; + + public void Add(TId key, TAggregateRoot value) + { + _dictionary.Add(key, value); + } + + public bool ContainsKey(TId key) + { + return _dictionary.ContainsKey(key); + } + + public bool Remove(TId key) + { + return _dictionary.Remove(key); + } + + public bool TryGetValue(TId key, out TAggregateRoot value) + { + return _dictionary.TryGetValue(key, out value); + } + + public TAggregateRoot this[TId key] + { + get => _dictionary[key]; + set => _dictionary[key] = value; + } + + public ICollection Keys => _dictionary.Keys; + + public ICollection Values => _dictionary.Values; + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryAggregateQueryable.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryAggregateQueryable.cs new file mode 100644 index 00000000..c6b4c0ba --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryAggregateQueryable.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Backend.Fx.Domain; + +namespace Backend.Fx.Features.Persistence.InMem +{ + public class InMemoryAggregateQueryable : IAggregateQueryable + where TId : struct, IEquatable + where TAggregateRoot : IAggregateRoot + { + private readonly AggregateQueryable _aggregateQueryable; + + public InMemoryAggregateQueryable(IAggregateDictionaries aggregateDictionaries) + { + _aggregateQueryable = new AggregateQueryable( + aggregateDictionaries.Get().AsQueryable().Select(kvp => kvp.Value)); + } + + public Type ElementType => _aggregateQueryable.ElementType; + + public Expression Expression => _aggregateQueryable.Expression; + + IQueryProvider IQueryable.Provider => _aggregateQueryable.Provider; + + public IEnumerator GetEnumerator() + { + return _aggregateQueryable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_aggregateQueryable).GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabase.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabase.cs new file mode 100644 index 00000000..473c8fcf --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabase.cs @@ -0,0 +1,20 @@ +using System.Collections.Concurrent; +using Backend.Fx.Features.MultiTenancy; + +namespace Backend.Fx.Features.Persistence.InMem +{ + public class InMemoryDatabase + { + private readonly ConcurrentDictionary _inMemoryStores = new(); + + public IAggregateDictionaries GetInMemoryStores() + { + return _inMemoryStores.GetOrAdd(new TenantId(null), _ => new AggregateDictionaries()); + } + + public IAggregateDictionaries GetInMemoryStoresOfTenant(TenantId tenantId) + { + return _inMemoryStores.GetOrAdd(tenantId, _ => new AggregateDictionaries()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabaseAccessor.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabaseAccessor.cs new file mode 100644 index 00000000..8f8f594b --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryDatabaseAccessor.cs @@ -0,0 +1,42 @@ +using Backend.Fx.Features.MultiTenancy; +using Backend.Fx.Util; + +namespace Backend.Fx.Features.Persistence.InMem +{ + public interface IInMemoryDatabaseAccessor + { + IAggregateDictionaries GetAggregateDictionaries(); + } + + public class InMemoryDatabaseAccessor : IInMemoryDatabaseAccessor + { + private readonly InMemoryDatabase _inMemoryDatabase; + + public InMemoryDatabaseAccessor(InMemoryDatabase inMemoryDatabase) + { + _inMemoryDatabase = inMemoryDatabase; + } + + public IAggregateDictionaries GetAggregateDictionaries() + { + return _inMemoryDatabase.GetInMemoryStores(); + } + } + + public class MultiTenancyInMemoryDatabaseAccessor : IInMemoryDatabaseAccessor + { + private readonly InMemoryDatabase _inMemoryDatabase; + private readonly ICurrentTHolder _tenantIdHolder; + + public MultiTenancyInMemoryDatabaseAccessor(InMemoryDatabase inMemoryDatabase, ICurrentTHolder tenantIdHolder, IInMemoryDatabaseAccessor inMemoryDatabaseAccessorUnused) + { + _inMemoryDatabase = inMemoryDatabase; + _tenantIdHolder = tenantIdHolder; + } + + public IAggregateDictionaries GetAggregateDictionaries() + { + return _inMemoryDatabase.GetInMemoryStoresOfTenant(_tenantIdHolder.Current); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryPersistenceModule.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryPersistenceModule.cs new file mode 100644 index 00000000..bffbc2f1 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryPersistenceModule.cs @@ -0,0 +1,43 @@ +using Backend.Fx.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.Persistence.InMem +{ + public class InMemoryPersistenceModule : PersistenceModule + { + protected override void RegisterInfrastructure(ICompositionRoot compositionRoot) + { + // a singleton database + compositionRoot.Register(ServiceDescriptor.Singleton()); + + // a scoped accessor to get the aggregate dictionaries + compositionRoot.Register(ServiceDescriptor.Scoped()); + + // we use the scoped accessor to get the aggregate dictionaries. This will be decorated in case of Multi Tenancy to switch the tenant store + compositionRoot.Register(ServiceDescriptor.Scoped(sp => + sp.GetRequiredService().GetAggregateDictionaries())); + + compositionRoot.Register( + ServiceDescriptor.Scoped(typeof(IAggregateQueryable<,>), typeof(InMemoryAggregateQueryable<,>))); + } + + protected override void RegisterImplementationSpecificServices(ICompositionRoot compositionRoot) + { + compositionRoot.Register( + ServiceDescriptor.Scoped(typeof(IRepository<,>), typeof(InMemoryRepository<,>))); + } + + public override IModule MultiTenancyModule => new InMemoryMultiTenancyPersistenceModule(); + } + + public class InMemoryMultiTenancyPersistenceModule : IModule + { + public void Register(ICompositionRoot compositionRoot) + { + // the MultiTenancyInMemoryDatabaseAccessor used the ICurrentTHolder to determine the respective + // in memory collection specific to this tenant id + compositionRoot.RegisterDecorator(ServiceDescriptor + .Scoped()); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryRepository.cs b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryRepository.cs new file mode 100644 index 00000000..258a24f8 --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/InMem/InMemoryRepository.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Backend.Fx.Domain; +using Backend.Fx.Exceptions; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Persistence.InMem +{ + [PublicAPI] + public class InMemoryRepository : IRepository + where TAggregateRoot : class, IAggregateRoot + where TId : struct, IEquatable + { + private readonly IAggregateQueryable _aggregateQueryable; + + public InMemoryRepository( + IAggregateDictionaries aggregateDictionaries, + IAggregateQueryable aggregateQueryable) + { + _aggregateQueryable = aggregateQueryable; + Store = aggregateDictionaries.Get(); + } + + public virtual IAggregateDictionary Store { get; } + + public void Clear() + { + Store.Clear(); + } + + public Task GetAsync(TId id, CancellationToken cancellationToken = default) + { + return Task.FromResult(_aggregateQueryable.FirstOrDefault(agg => Equals(agg.Id, id)) + ?? throw new NotFoundException(id)); + } + + public Task FindAsync(TId id, CancellationToken cancellationToken = default) + { + return Task.FromResult(_aggregateQueryable.FirstOrDefault(agg => Equals(agg.Id, id))); + } + + public Task GetAllAsync(CancellationToken cancellationToken = default) + { + // the "useless" where condition makes sure we do not return the underlying array, but evaluate the + // expression, that might have been extended with additional filters (authorization, multi tenancy) + return Task.FromResult(_aggregateQueryable.Where(agg => true).ToArray()); + } + + public Task AnyAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(_aggregateQueryable.Any()); + } + + public Task ResolveAsync(IEnumerable ids, CancellationToken cancellationToken = default) + { + return RepositoryEx.ResolveAsync(this, ids, cancellationToken); + } + + public Task DeleteAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default) + { + if (Store.ContainsKey(aggregateRoot.Id)) + { + Store.Remove(aggregateRoot.Id); + } + + return Task.CompletedTask; + } + + public Task AddAsync(TAggregateRoot aggregateRoot, CancellationToken cancellationToken = default) + { + if (Store.ContainsKey(aggregateRoot.Id)) + { + throw new ConflictedException( + $"There is already an {aggregateRoot.GetType().Name} with id {aggregateRoot.Id} present"); + } + + Store[aggregateRoot.Id] = aggregateRoot; + return Task.CompletedTask; + } + + public async Task AddRangeAsync(TAggregateRoot[] aggregateRoots, + CancellationToken cancellationToken = default) + { + foreach (var aggregateRoot in aggregateRoots) + { + await AddAsync(aggregateRoot, cancellationToken).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/PersistenceFeature.cs b/src/abstractions/Backend.Fx/Features/Persistence/PersistenceFeature.cs new file mode 100644 index 00000000..844eb3bf --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/PersistenceFeature.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Backend.Fx.Features.Persistence +{ + [PublicAPI] + public sealed class PersistenceFeature : Feature, IBootableFeature, IMultiTenancyFeature + { + private readonly PersistenceModule _persistenceModule; + private readonly IDatabaseAvailabilityAwaiter _databaseAvailabilityAwaiter; + private readonly IDatabaseBootstrapper _databaseBootstrapper; + + + public PersistenceFeature( + PersistenceModule persistenceModule, + IDatabaseAvailabilityAwaiter databaseAvailabilityAwaiter = null, + IDatabaseBootstrapper databaseBootstrapper = null) + { + _persistenceModule = persistenceModule; + _databaseAvailabilityAwaiter = databaseAvailabilityAwaiter; + _databaseBootstrapper = databaseBootstrapper; + } + + public override void Enable(IBackendFxApplication application) + { + application.CompositionRoot.RegisterModules(_persistenceModule); + } + + public void EnableMultiTenancyServices(IBackendFxApplication application) + { + if (_persistenceModule.MultiTenancyModule == null) + { + throw new InvalidOperationException($"No multi tenancy module provided by {_persistenceModule.GetType().Name}"); + } + + application.CompositionRoot.RegisterModules(_persistenceModule.MultiTenancyModule); + } + + public async Task BootAsync(IBackendFxApplication application, CancellationToken cancellationToken = default) + { + if (_databaseAvailabilityAwaiter != null) + { + await _databaseAvailabilityAwaiter.WaitForDatabase(cancellationToken).ConfigureAwait(false); + } + + if (_databaseBootstrapper != null) + { + await _databaseBootstrapper.EnsureDatabaseExistenceAsync(cancellationToken).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Features/Persistence/PersistenceModule.cs b/src/abstractions/Backend.Fx/Features/Persistence/PersistenceModule.cs new file mode 100644 index 00000000..f9e7a94f --- /dev/null +++ b/src/abstractions/Backend.Fx/Features/Persistence/PersistenceModule.cs @@ -0,0 +1,30 @@ +using Backend.Fx.DependencyInjection; +using Backend.Fx.ExecutionPipeline; +using Backend.Fx.Features.DomainEvents; +using Microsoft.Extensions.DependencyInjection; + +namespace Backend.Fx.Features.Persistence +{ + public abstract class PersistenceModule : IModule + { + public void Register(ICompositionRoot compositionRoot) + { + // register a default flush scoped instance, so that it can be decorated later + compositionRoot.Register(ServiceDescriptor.Scoped()); + compositionRoot.RegisterDecorator(ServiceDescriptor.Scoped()); + + // make sure we flush pending changes before raising pending domain events + compositionRoot.RegisterDecorator( + ServiceDescriptor.Scoped()); + + RegisterImplementationSpecificServices(compositionRoot); + RegisterInfrastructure(compositionRoot); + } + + protected abstract void RegisterInfrastructure(ICompositionRoot compositionRoot); + + protected abstract void RegisterImplementationSpecificServices(ICompositionRoot compositionRoot); + + public virtual IModule MultiTenancyModule => null; + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs index ea8926d2..88fb7676 100644 --- a/src/abstractions/Backend.Fx/Logging/DurationLogger.cs +++ b/src/abstractions/Backend.Fx/Logging/DurationLogger.cs @@ -12,7 +12,7 @@ public class DurationLogger : IDisposable private readonly string _endMessage; private readonly Action _logAction; - private readonly Stopwatch _stopwatch = new Stopwatch(); + private readonly Stopwatch _stopwatch = new(); public DurationLogger(Action logAction, string activity) : this(logAction, activity, activity) diff --git a/src/abstractions/Backend.Fx/Logging/Log.cs b/src/abstractions/Backend.Fx/Logging/Log.cs index 2f961422..02a63ed7 100644 --- a/src/abstractions/Backend.Fx/Logging/Log.cs +++ b/src/abstractions/Backend.Fx/Logging/Log.cs @@ -15,8 +15,7 @@ public static class Log { private static ILoggerFactory _loggerFactory = new NullLoggerFactory(); - private static readonly AsyncLocal AsyncLocalLoggerFactory = - new AsyncLocal(); + private static readonly AsyncLocal AsyncLocalLoggerFactory = new(); public static void Initialize(ILoggerFactory loggerFactory) { @@ -65,6 +64,7 @@ public ILogger CreateLogger(string categoryName) public void AddProvider(ILoggerProvider provider) { + (AsyncLocalLoggerFactory.Value ?? _loggerFactory).AddProvider(provider); } } } diff --git a/src/abstractions/Backend.Fx/MultiTenancyBackendFxApplication.cs b/src/abstractions/Backend.Fx/MultiTenancyBackendFxApplication.cs new file mode 100644 index 00000000..60dd84c6 --- /dev/null +++ b/src/abstractions/Backend.Fx/MultiTenancyBackendFxApplication.cs @@ -0,0 +1,54 @@ +using System.Reflection; +using Backend.Fx.DependencyInjection; +using Backend.Fx.Features; +using Backend.Fx.Features.MultiTenancy; +using Backend.Fx.Features.MultiTenancy.InProc; +using Backend.Fx.Logging; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Backend.Fx +{ + [PublicAPI] + public class MultiTenancyBackendFxApplication : BackendFxApplication where TCurrentTenantIdSelector : class, ICurrentTenantIdSelector + { + private static readonly ILogger Logger = Log.Create>(); + + public MultiTenancyBackendFxApplication( + ICompositionRoot compositionRoot, + ITenantEnumerator tenantEnumerator, + params Assembly[] assemblies) + : this(compositionRoot, new ExceptionLogger(Logger), tenantEnumerator, new InProcTenantWideMutexManager(), assemblies) + { + } + + public MultiTenancyBackendFxApplication( + ICompositionRoot compositionRoot, + IExceptionLogger exceptionLogger, + ITenantEnumerator tenantEnumerator, + params Assembly[] assemblies) + : this(compositionRoot, exceptionLogger, tenantEnumerator, new InProcTenantWideMutexManager(), assemblies) + { + } + + public MultiTenancyBackendFxApplication( + ICompositionRoot compositionRoot, + IExceptionLogger exceptionLogger, + ITenantEnumerator tenantEnumerator, + ITenantWideMutexManager tenantWideMutexManager, + params Assembly[] assemblies) + : base(compositionRoot, exceptionLogger, assemblies) + { + CompositionRoot.RegisterModules(new MultiTenancyModule(tenantEnumerator, tenantWideMutexManager)); + } + + public override void EnableFeature(Feature feature) + { + base.EnableFeature(feature); + if (feature is IMultiTenancyFeature multiTenancyFeature) + { + multiTenancyFeature.EnableMultiTenancyServices(this); + } + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Util/AsyncHelper.cs b/src/abstractions/Backend.Fx/Util/AsyncHelper.cs index c89fef9b..3e73918c 100644 --- a/src/abstractions/Backend.Fx/Util/AsyncHelper.cs +++ b/src/abstractions/Backend.Fx/Util/AsyncHelper.cs @@ -94,8 +94,8 @@ private class ExclusiveSynchronizationContext : SynchronizationContext { private bool _done; public Exception InnerException { private get; set; } - private readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false); - private readonly Queue> _items = new Queue>(); + private readonly AutoResetEvent _workItemsWaiting = new(false); + private readonly Queue> _items = new(); public override void Send(SendOrPostCallback d, object state) { diff --git a/src/abstractions/Backend.Fx/Util/CurrentTHolder.cs b/src/abstractions/Backend.Fx/Util/CurrentTHolder.cs index 94c3c11c..061dbcaf 100644 --- a/src/abstractions/Backend.Fx/Util/CurrentTHolder.cs +++ b/src/abstractions/Backend.Fx/Util/CurrentTHolder.cs @@ -9,7 +9,7 @@ namespace Backend.Fx.Util /// /// [PublicAPI] - public interface ICurrentTHolder where T : class + public interface ICurrentTHolder { T Current { get; } @@ -19,7 +19,7 @@ public interface ICurrentTHolder where T : class } [PublicAPI] - public abstract class CurrentTHolder : ICurrentTHolder where T : class + public abstract class CurrentTHolder : ICurrentTHolder { private static readonly ILogger Logger = Log.Create>(); private T _current; diff --git a/src/abstractions/Backend.Fx/Util/GenericsHelper.cs b/src/abstractions/Backend.Fx/Util/GenericsHelper.cs new file mode 100644 index 00000000..6a084945 --- /dev/null +++ b/src/abstractions/Backend.Fx/Util/GenericsHelper.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Backend.Fx.Util +{ + public static class GenericsHelper + { + public static Type[] XGetGenericArguments(this Type t) + { + if (t == null) return null; + return t.IsGenericType ? t.GetGenericArguments() : new Type[0]; + } + + public static Type[] XGetGenericArguments(this MethodInfo mi) + { + if (mi == null) return null; + return mi.IsGenericMethod ? mi.GetGenericArguments() : new Type[0]; + } + + public static Type XGetGenericDefinition(this Type t) + { + if (t == null) return null; + return t.IsGenericType ? t.GetGenericTypeDefinition() : t; + } + + public static MethodInfo XGetGenericDefinition(this MethodInfo mi) + { + if (mi == null) return null; + return mi.IsGenericMethod ? mi.GetGenericMethodDefinition() : mi; + } + + public static Type XMakeGenericType(this Type t, params Type[] targs) + { + if (t == null) return null; + if (!t.IsGenericType || t.IsGenericParameter) + { + if (targs.Length != 0) + throw new NotSupportedException(targs.Length.ToString()); + return t; + } + + return t.GetGenericTypeDefinition().MakeGenericType(targs); + } + + public static MethodInfo XMakeGenericMethod(this MethodInfo mi, params Type[] margs) + { + return mi.XMakeGenericMethod(mi.DeclaringType.XGetGenericArguments(), margs); + } + + public static MethodInfo XMakeGenericMethod(this MethodInfo mi, Type[] targs, Type[] margs) + { + if (mi == null) return null; + var pattern = (MethodInfo)mi.Module.ResolveMethod(mi.MetadataToken); + + var typeImpl = pattern.DeclaringType; + if (targs != null && targs.Any()) typeImpl = typeImpl.MakeGenericType(targs); + + var methodImpl = typeImpl.GetMethods( + BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static) + .Single(mi2 => mi2.MetadataToken == mi.MetadataToken); + if (margs != null && margs.Any()) methodImpl = methodImpl.MakeGenericMethod(margs); + + return methodImpl; + } + + public static bool IsOpenGeneric(this Type t) + { + return t.IsGenericParameter || t.XGetGenericArguments().Any(arg => arg.IsOpenGeneric()); + } + + public static bool IsOpenGeneric(this MethodInfo t) + { + return t.ReturnType.IsOpenGeneric() || + t.GetParameters().Any(pi => pi.ParameterType.IsOpenGeneric()); + } + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs index 3b27d75d..5ee854b9 100644 --- a/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs +++ b/src/implementations/Backend.Fx.MicrosoftDependencyInjection/MicrosoftCompositionRoot.cs @@ -3,6 +3,7 @@ using System.Linq; using Backend.Fx.DependencyInjection; using Backend.Fx.Logging; +using Backend.Fx.Util; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -33,11 +34,6 @@ public MicrosoftCompositionRoot() public override IServiceProvider ServiceProvider => _serviceProvider.Value; - public override bool HasRegistration() - { - return ServiceCollection.Any(sd => sd.ServiceType is T); - } - public override void Verify() { // ensure creation of lazy service provider, this will trigger the validation @@ -56,19 +52,27 @@ public override void Register(ServiceDescriptor serviceDescriptor) if (existingRegistration == null) { - LogAddRegistration(serviceDescriptor); ServiceCollection.Add(serviceDescriptor); } else { - LogReplaceRegistration(serviceDescriptor); + Logger.LogDebug("{Verb} {Lifetime} {RegistrationType} for {ServiceType}: {ImplementationType}", + "Replacing", + serviceDescriptor.Lifetime.ToString().ToLowerInvariant(), + "registration", + serviceDescriptor.ServiceType.GetDetailedTypeName(), + serviceDescriptor.GetImplementationTypeDescription()); ServiceCollection.Replace(serviceDescriptor); } } public override void RegisterDecorator(ServiceDescriptor serviceDescriptor) { - LogAddDecoratorRegistration(serviceDescriptor); + if (serviceDescriptor.ServiceType.IsOpenGeneric() && serviceDescriptor.ImplementationType.IsOpenGeneric()) + { + throw new NotSupportedException("Microsoft's DI does not support decoration of open generic types. See https://github.com/khellang/Scrutor/issues/39 for more info"); + } + ServiceCollection.Decorate(serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType); } @@ -84,7 +88,6 @@ public override void RegisterCollection(IEnumerable serviceDe foreach (var serviceDescriptor in serviceDescriptorArray) { - LogAddRegistration(serviceDescriptor); ServiceCollection.Add(serviceDescriptor); } } diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs b/src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs index 107d1557..050f62b3 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs +++ b/src/implementations/Backend.Fx.RabbitMq/RabbitMqChannel.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Sockets; -using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; using Polly; diff --git a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs b/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs index 15659198..1bb8af29 100644 --- a/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs +++ b/src/implementations/Backend.Fx.RabbitMq/RabbitMqMessageBus.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using Backend.Fx.Extensions.MessageBus; +using Backend.Fx.Features.MessageBus; using Backend.Fx.Logging; using Microsoft.Extensions.Logging; using RabbitMQ.Client; diff --git a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs index ba5b4400..8de2217c 100644 --- a/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs +++ b/src/implementations/Backend.Fx.SimpleInjetorDependencyInjection/SimpleInjectorCompositionRoot.cs @@ -86,12 +86,6 @@ public override void RegisterCollection(IEnumerable serviceDe var serviceDescriptorArray = serviceDescriptors as ServiceDescriptor[] ?? serviceDescriptors.ToArray(); - if (serviceDescriptorArray.Length == 0) - { - Logger.LogWarning("Skipping registration of empty collection"); - return; - } - if (serviceDescriptorArray.Select(sd => sd.ServiceType).Distinct().Count() > 1) { throw new InvalidOperationException( @@ -101,11 +95,6 @@ public override void RegisterCollection(IEnumerable serviceDe _serviceCollections.Add(serviceDescriptorArray); } - public override bool HasRegistration() - { - return _services.Any(sd => sd.ServiceType is T); - } - public override void Verify() { FillContainer(); @@ -138,8 +127,6 @@ private void FillContainer() Logger.LogInformation("Registering services with container"); foreach (var serviceDescriptor in _services) { - LogAddRegistration(serviceDescriptor); - if (serviceDescriptor.ImplementationType != null) { Container.Register( @@ -168,8 +155,6 @@ private void FillContainer() foreach (var serviceDescriptor in _decorators) { - LogAddDecoratorRegistration(serviceDescriptor); - Container.RegisterDecorator( serviceDescriptor.ServiceType, serviceDescriptor.ImplementationType, @@ -178,11 +163,6 @@ private void FillContainer() foreach (var serviceDescriptors in _serviceCollections) { - Logger.LogDebug("Adding {Lifetime} collection registration: {ServiceType}: {ImplementationType}", - serviceDescriptors[0].Lifetime.ToString(), - serviceDescriptors[0].ServiceType.Name, - $"[{string.Join(",", serviceDescriptors.Select(sd => sd.GetImplementationTypeDescription()))}]"); - foreach (var serviceDescriptor in serviceDescriptors) { Container.Collection.Append( diff --git a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj index 89a48258..edbc347d 100644 --- a/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj +++ b/tests/Backend.Fx.Tests/Backend.Fx.Tests.csproj @@ -2,6 +2,8 @@ net6.0 + default + enable @@ -10,12 +12,13 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + + all @@ -25,20 +28,8 @@ - - - - - - - - - - - PreserveNewest - diff --git a/tests/Backend.Fx.Tests/Backup.bak b/tests/Backend.Fx.Tests/Backup.bak deleted file mode 100644 index 2783379b4ea0c2971e3b45f8df6020b754ebcfd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2867200 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�I2aq{Jc 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Ì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�-8`5bNouPg%z}86$ct~VY=-i!1b-s5*|cr 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