From a5919dfae44adf70e3e6ae61e755595c4597e136 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Sat, 22 Jan 2022 05:30:39 +0000 Subject: [PATCH] Startup refactored --- .../General/BootstrapperTests.cs | 56 +- .../Bootstrapper/ClientRegistry.cs | 49 ++ .../DomainEventHandlerRegistry.cs | 50 ++ .../Bootstrapper/DomainServiceRegistry.cs | 26 + .../Bootstrapper/MediatorRegistry.cs | 37 ++ .../Bootstrapper/MiddlewareRegistry.cs | 141 +++++ .../Bootstrapper/MiscRegistry.cs | 31 + .../Bootstrapper/OperatorRegistry.cs | 61 ++ .../Bootstrapper/RepositoryRegistry.cs | 60 ++ TransactionProcessor/Extensions.cs | 94 +++ TransactionProcessor/Program.cs | 58 +- TransactionProcessor/Startup.cs | 535 ++++-------------- .../TransactionProcessor.csproj | 4 +- 13 files changed, 690 insertions(+), 512 deletions(-) create mode 100644 TransactionProcessor/Bootstrapper/ClientRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/MediatorRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/MiscRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/OperatorRegistry.cs create mode 100644 TransactionProcessor/Bootstrapper/RepositoryRegistry.cs create mode 100644 TransactionProcessor/Extensions.cs diff --git a/TransactionProcessor.Tests/General/BootstrapperTests.cs b/TransactionProcessor.Tests/General/BootstrapperTests.cs index 34657fe8..e1ff931d 100644 --- a/TransactionProcessor.Tests/General/BootstrapperTests.cs +++ b/TransactionProcessor.Tests/General/BootstrapperTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using Lamar; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -28,15 +29,13 @@ public void VerifyBootstrapperIsValid() hostingEnvironment.Setup(he => he.ContentRootPath).Returns("/home"); hostingEnvironment.Setup(he => he.ApplicationName).Returns("Test Application"); - IServiceCollection services = new ServiceCollection(); + ServiceRegistry services = new ServiceRegistry(); Startup s = new Startup(hostingEnvironment.Object); Startup.Configuration = this.SetupMemoryConfiguration(); - s.ConfigureServices(services); - this.AddTestRegistrations(services, hostingEnvironment.Object); - - services.AssertConfigurationIsValid(); + s.ConfigureContainer(services); + Startup.Container.AssertConfigurationIsValid(AssertMode.Full); } private IConfigurationRoot SetupMemoryConfiguration() @@ -51,6 +50,7 @@ private IConfigurationRoot SetupMemoryConfiguration() configuration.Add("AppSettings:ClientId", "clientId"); configuration.Add("AppSettings:ClientSecret", "clientSecret"); configuration.Add("AppSettings:EstateManagementApi", "http://localhost"); + configuration.Add("AppSettings:VoucherManagementApi", "http://localhost"); configuration.Add("AppSettings:SecurityService", "http://localhost"); configuration.Add("SecurityConfiguration:Authority", "http://localhost"); @@ -77,27 +77,27 @@ private void AddTestRegistrations(IServiceCollection services, #endregion } - public static class ServiceCollectionExtensions - { - public static void AssertConfigurationIsValid(this IServiceCollection serviceCollection, - List typesToIgnore = null) - { - ServiceProvider buildServiceProvider = serviceCollection.BuildServiceProvider(); - - List list = serviceCollection.Where(x => x.ServiceType.Namespace != null && x.ServiceType.Namespace.Contains("Vme")).ToList(); - - if (typesToIgnore != null) - { - list.RemoveAll(listItem => typesToIgnore.Contains(listItem.ServiceType)); - } - - foreach (ServiceDescriptor serviceDescriptor in list) - { - Type type = serviceDescriptor.ServiceType; - - //This throws an Exception if the type cannot be instantiated. - buildServiceProvider.GetService(type); - } - } - } + //public static class ServiceCollectionExtensions + //{ + // public static void AssertConfigurationIsValid(this IServiceCollection serviceCollection, + // List typesToIgnore = null) + // { + // ServiceProvider buildServiceProvider = serviceCollection.BuildServiceProvider(); + + // List list = serviceCollection.Where(x => x.ServiceType.Namespace != null && x.ServiceType.Namespace.Contains("Vme")).ToList(); + + // if (typesToIgnore != null) + // { + // list.RemoveAll(listItem => typesToIgnore.Contains(listItem.ServiceType)); + // } + + // foreach (ServiceDescriptor serviceDescriptor in list) + // { + // Type type = serviceDescriptor.ServiceType; + + // //This throws an Exception if the type cannot be instantiated. + // buildServiceProvider.GetService(type); + // } + // } + //} } \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/ClientRegistry.cs b/TransactionProcessor/Bootstrapper/ClientRegistry.cs new file mode 100644 index 00000000..1c89f555 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/ClientRegistry.cs @@ -0,0 +1,49 @@ +namespace TransactionProcessor.Bootstrapper +{ + using System; + using System.Net.Http; + using EstateManagement.Client; + using Lamar; + using MessagingService.Client; + using Microsoft.Extensions.DependencyInjection; + using SecurityService.Client; + using Shared.General; + using VoucherManagement.Client; + + /// + /// + /// + /// + public class ClientRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public ClientRegistry() + { + this.AddSingleton(); + this.AddSingleton(); + this.AddSingleton(); + this.AddSingleton(); + + this.AddSingleton>(container => serviceName => { return ConfigurationReader.GetBaseServerUri(serviceName).OriginalString; }); + + var httpMessageHandler = new SocketsHttpHandler + { + SslOptions = + { + RemoteCertificateValidationCallback = (sender, + certificate, + chain, + errors) => true, + } + }; + HttpClient httpClient = new HttpClient(httpMessageHandler); + this.AddSingleton(httpClient); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs b/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs new file mode 100644 index 00000000..e950fec6 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/DomainEventHandlerRegistry.cs @@ -0,0 +1,50 @@ +namespace TransactionProcessor.Bootstrapper +{ + using System; + using System.Collections.Generic; + using BusinessLogic.EventHandling; + using Lamar; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Shared.EventStore.EventHandling; + + /// + /// + /// + /// + public class DomainEventHandlerRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public DomainEventHandlerRegistry() + { + Dictionary eventHandlersConfiguration = new Dictionary(); + + if (Startup.Configuration != null) + { + IConfigurationSection section = Startup.Configuration.GetSection("AppSettings:EventHandlerConfiguration"); + + if (section != null) + { + Startup.Configuration.GetSection("AppSettings:EventHandlerConfiguration").Bind(eventHandlersConfiguration); + } + } + + this.AddSingleton(eventHandlersConfiguration); + + this.AddSingleton>(container => type => + { + IDomainEventHandler handler = container.GetService(type) as IDomainEventHandler; + return handler; + }); + + this.AddSingleton(); + this.AddSingleton(); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs b/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs new file mode 100644 index 00000000..f7e8ad6e --- /dev/null +++ b/TransactionProcessor/Bootstrapper/DomainServiceRegistry.cs @@ -0,0 +1,26 @@ +namespace TransactionProcessor.Bootstrapper +{ + using BusinessLogic.Services; + using Lamar; + using Microsoft.Extensions.DependencyInjection; + + /// + /// + /// + /// + public class DomainServiceRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public DomainServiceRegistry() + { + this.AddSingleton(); + this.AddSingleton(); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/MediatorRegistry.cs b/TransactionProcessor/Bootstrapper/MediatorRegistry.cs new file mode 100644 index 00000000..22f25fd5 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/MediatorRegistry.cs @@ -0,0 +1,37 @@ +namespace TransactionProcessor.Bootstrapper +{ + using BusinessLogic.RequestHandlers; + using BusinessLogic.Requests; + using Lamar; + using MediatR; + using Microsoft.Extensions.DependencyInjection; + using Models; + + /// + /// + /// + /// + public class MediatorRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public MediatorRegistry() + { + this.AddTransient(); + + // request & notification handlers + this.AddTransient(context => { return t => context.GetService(t); }); + + this.AddSingleton, TransactionRequestHandler>(); + this.AddSingleton, TransactionRequestHandler>(); + this.AddSingleton, TransactionRequestHandler>(); + + this.AddSingleton, SettlementRequestHandler>(); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs b/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs new file mode 100644 index 00000000..0e27e129 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/MiddlewareRegistry.cs @@ -0,0 +1,141 @@ +namespace TransactionProcessor.Bootstrapper +{ + using System; + using System.IO; + using System.Net.Http; + using System.Reflection; + using Common; + using Lamar; + using Microsoft.AspNetCore.Authentication.JwtBearer; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Diagnostics.HealthChecks; + using Microsoft.IdentityModel.Logging; + using Microsoft.IdentityModel.Tokens; + using Microsoft.OpenApi.Models; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + using Shared.EventStore.Extensions; + using Shared.Extensions; + using Shared.General; + using Swashbuckle.AspNetCore.Filters; + + /// + /// + /// + /// + public class MiddlewareRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public MiddlewareRegistry() + { + this.AddHealthChecks() + .AddEventStore(Startup.EventStoreClientSettings, + userCredentials:Startup.EventStoreClientSettings.DefaultCredentials, + name:"Eventstore", + failureStatus:HealthStatus.Unhealthy, + tags:new[] {"db", "eventstore"}).AddSecurityService(this.ApiEndpointHttpHandler).AddEstateManagementService(); + + this.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", + new OpenApiInfo + { + Title = "Transaction Processor API", + Version = "1.0", + Description = "A REST Api to manage the processing and routing of transactions to local and remote operators.", + Contact = new OpenApiContact + { + Name = "Stuart Ferguson", + Email = "golfhandicapping@btinternet.com" + } + }); + // add a custom operation filter which sets default values + c.OperationFilter(); + c.ExampleFilters(); + + //Locate the XML files being generated by ASP.NET... + var directory = new DirectoryInfo(AppContext.BaseDirectory); + var xmlFiles = directory.GetFiles("*.xml"); + + //... and tell Swagger to use those XML comments. + foreach (FileInfo fileInfo in xmlFiles) + { + c.IncludeXmlComments(fileInfo.FullName); + } + }); + + this.AddSwaggerExamplesFromAssemblyOf(); + + IdentityModelEventSource.ShowPII = true; + + this.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.BackchannelHttpHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, + certificate, + chain, + sslPolicyErrors) => true + }; + options.Authority = ConfigurationReader.GetValue("SecurityConfiguration", "Authority"); + options.Audience = ConfigurationReader.GetValue("SecurityConfiguration", "ApiName"); + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidAudience = + ConfigurationReader.GetValue("SecurityConfiguration", "ApiName"), + ValidIssuer = + ConfigurationReader.GetValue("SecurityConfiguration", "Authority"), + }; + options.IncludeErrorDetails = true; + }); + + this.AddControllers().AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + options.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto; + options.SerializerSettings.Formatting = Formatting.Indented; + options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + }); + + Assembly assembly = this.GetType().GetTypeInfo().Assembly; + this.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices(); + } + + #endregion + + #region Methods + + /// + /// APIs the endpoint HTTP handler. + /// + /// The service provider. + /// + private HttpClientHandler ApiEndpointHttpHandler(IServiceProvider serviceProvider) + { + return new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, + cert, + chain, + errors) => + { + return true; + } + }; + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/MiscRegistry.cs b/TransactionProcessor/Bootstrapper/MiscRegistry.cs new file mode 100644 index 00000000..ed4a53a3 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/MiscRegistry.cs @@ -0,0 +1,31 @@ +namespace TransactionProcessor.Bootstrapper +{ + using System.IO.Abstractions; + using BusinessLogic.Manager; + using BusinessLogic.Services; + using Factories; + using Lamar; + using Microsoft.Extensions.DependencyInjection; + + /// + /// + /// + /// + public class MiscRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public MiscRegistry() + { + this.AddSingleton(); + this.AddSingleton(); + this.AddSingleton(); + this.AddSingleton(); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/OperatorRegistry.cs b/TransactionProcessor/Bootstrapper/OperatorRegistry.cs new file mode 100644 index 00000000..c1fe5024 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/OperatorRegistry.cs @@ -0,0 +1,61 @@ +namespace TransactionProcessor.Bootstrapper +{ + using System; + using System.Net.Http; + using BusinessLogic.OperatorInterfaces; + using BusinessLogic.OperatorInterfaces.SafaricomPinless; + using BusinessLogic.OperatorInterfaces.VoucherManagement; + using Lamar; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using VoucherManagement.Client; + + /// + /// + /// + /// + public class OperatorRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public OperatorRegistry() + { + SafaricomConfiguration safaricomConfiguration = new SafaricomConfiguration(); + + if (Startup.Configuration != null) + { + IConfigurationSection section = Startup.Configuration.GetSection("AppSettings:EventHandlerConfiguration"); + + if (section != null) + { + Startup.Configuration.GetSection("OperatorConfiguration:Safaricom").Bind(safaricomConfiguration); + } + } + + this.AddSingleton(safaricomConfiguration); + + this.AddTransient>(context => operatorIdentifier => + { + if (string.Compare(operatorIdentifier, + "Safaricom", + StringComparison.CurrentCultureIgnoreCase) == 0) + { + SafaricomConfiguration + configuration = context.GetRequiredService(); + HttpClient client = context.GetRequiredService(); + return new SafaricomPinlessProxy(configuration, client); + } + + // Voucher + IVoucherManagementClient voucherManagementClient = + context.GetRequiredService(); + return new VoucherManagementProxy(voucherManagementClient); + }); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs new file mode 100644 index 00000000..20ebae61 --- /dev/null +++ b/TransactionProcessor/Bootstrapper/RepositoryRegistry.cs @@ -0,0 +1,60 @@ +namespace TransactionProcessor.Bootstrapper +{ + using System; + using BusinessLogic.Services; + using Lamar; + using Microsoft.Extensions.DependencyInjection; + using ReconciliationAggregate; + using SettlementAggregates; + using Shared.DomainDrivenDesign.EventSourcing; + using Shared.EntityFramework.ConnectionStringConfiguration; + using Shared.EventStore.Aggregate; + using Shared.EventStore.EventStore; + using Shared.General; + using Shared.Repositories; + using TransactionAggregate; + + /// + /// + /// + /// + public class RepositoryRegistry : ServiceRegistry + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + public RepositoryRegistry() + { + Boolean useConnectionStringConfig = bool.Parse(ConfigurationReader.GetValue("AppSettings", "UseConnectionStringConfig")); + + if (useConnectionStringConfig) + { + String connectionStringConfigurationConnString = ConfigurationReader.GetConnectionString("ConnectionStringConfiguration"); + this.AddSingleton(); + this.AddTransient(c => { return new ConnectionStringConfigurationContext(connectionStringConfigurationConnString); }); + + // TODO: Read this from a the database and set + } + else + { + this.AddEventStoreClient(Startup.ConfigureEventStoreSettings); + this.AddEventStoreProjectionManagementClient(Startup.ConfigureEventStoreSettings); + this.AddEventStorePersistentSubscriptionsClient(Startup.ConfigureEventStoreSettings); + } + + this.AddTransient(); + + this.AddSingleton(); + this.AddSingleton, + AggregateRepository>(); + this.AddSingleton, + AggregateRepository>(); + this.AddSingleton, + AggregateRepository>(); + } + + #endregion + } +} \ No newline at end of file diff --git a/TransactionProcessor/Extensions.cs b/TransactionProcessor/Extensions.cs new file mode 100644 index 00000000..4732b6b7 --- /dev/null +++ b/TransactionProcessor/Extensions.cs @@ -0,0 +1,94 @@ +namespace TransactionProcessor +{ + using System; + using System.Diagnostics; + using System.Threading; + using Microsoft.AspNetCore.Builder; + using Microsoft.Extensions.DependencyInjection; + using Shared.EventStore.EventHandling; + using Shared.EventStore.SubscriptionWorker; + using Shared.General; + using Shared.Logger; + + public static class Extensions + { + static Action log = (tt, subType, message) => { + String logMessage = $"{subType} - {message}"; + switch (tt) + { + case TraceEventType.Critical: + Logger.LogCritical(new Exception(logMessage)); + break; + case TraceEventType.Error: + Logger.LogError(new Exception(logMessage)); + break; + case TraceEventType.Warning: + Logger.LogWarning(logMessage); + break; + case TraceEventType.Information: + Logger.LogInformation(logMessage); + break; + case TraceEventType.Verbose: + Logger.LogDebug(logMessage); + break; + } + }; + + static Action concurrentLog = (tt, message) => Extensions.log(tt, "CONCURRENT", message); + + public static void PreWarm(this IApplicationBuilder applicationBuilder) + { + Startup.LoadTypes(); + + //SubscriptionWorker worker = new SubscriptionWorker() + var internalSubscriptionService = Boolean.Parse(ConfigurationReader.GetValue("InternalSubscriptionService")); + + if (internalSubscriptionService) + { + String eventStoreConnectionString = ConfigurationReader.GetValue("EventStoreSettings", "ConnectionString"); + Int32 inflightMessages = Int32.Parse(ConfigurationReader.GetValue("AppSettings", "InflightMessages")); + Int32 persistentSubscriptionPollingInSeconds = Int32.Parse(ConfigurationReader.GetValue("AppSettings", "PersistentSubscriptionPollingInSeconds")); + String filter = ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionServiceFilter"); + String ignore = ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionServiceIgnore"); + String streamName = ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionFilterOnStreamName"); + Int32 cacheDuration = Int32.Parse(ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionServiceCacheDuration")); + + ISubscriptionRepository subscriptionRepository = SubscriptionRepository.Create(eventStoreConnectionString, cacheDuration); + + ((SubscriptionRepository)subscriptionRepository).Trace += (sender, s) => Extensions.log(TraceEventType.Information, "REPOSITORY", s); + + // init our SubscriptionRepository + subscriptionRepository.PreWarm(CancellationToken.None).Wait(); + + var eventHandlerResolver = Startup.ServiceProvider.GetService(); + + SubscriptionWorker concurrentSubscriptions = SubscriptionWorker.CreateConcurrentSubscriptionWorker(eventStoreConnectionString, eventHandlerResolver, subscriptionRepository, inflightMessages, persistentSubscriptionPollingInSeconds); + + concurrentSubscriptions.Trace += (_, args) => Extensions.concurrentLog(TraceEventType.Information, args.Message); + concurrentSubscriptions.Warning += (_, args) => Extensions.concurrentLog(TraceEventType.Warning, args.Message); + concurrentSubscriptions.Error += (_, args) => Extensions.concurrentLog(TraceEventType.Error, args.Message); + + if (!String.IsNullOrEmpty(ignore)) + { + concurrentSubscriptions = concurrentSubscriptions.IgnoreSubscriptions(ignore); + } + + if (!String.IsNullOrEmpty(filter)) + { + //NOTE: Not overly happy with this design, but + //the idea is if we supply a filter, this overrides ignore + concurrentSubscriptions = concurrentSubscriptions.FilterSubscriptions(filter) + .IgnoreSubscriptions(null); + + } + + if (!String.IsNullOrEmpty(streamName)) + { + concurrentSubscriptions = concurrentSubscriptions.FilterByStreamName(streamName); + } + + concurrentSubscriptions.StartAsync(CancellationToken.None).Wait(); + } + } + } +} \ No newline at end of file diff --git a/TransactionProcessor/Program.cs b/TransactionProcessor/Program.cs index da2c7bff..d7a50111 100644 --- a/TransactionProcessor/Program.cs +++ b/TransactionProcessor/Program.cs @@ -13,6 +13,7 @@ namespace TransactionProcessor using System.IO; using System.Net.Http; using EventStore.Client; + using Lamar.Microsoft.DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Reconciliation.DomainEvents; using Settlement.DomainEvents; @@ -41,69 +42,14 @@ public static IHostBuilder CreateHostBuilder(string[] args) .AddEnvironmentVariables().Build(); IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args); + hostBuilder.UseLamar(); hostBuilder.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); webBuilder.UseConfiguration(config); webBuilder.UseKestrel(); }); - //.ConfigureServices(services => - // { - // SettlementCreatedForDateEvent s = - // new SettlementCreatedForDateEvent(Guid.Parse("62CA5BF0-D138-4A19-9970-A4F7D52DE292"), - // Guid.Parse("3E42516B-6C6F-4F86-BF08-3EF0ACDDDD55"), - // DateTime.Now); - - // TransactionHasStartedEvent t = new TransactionHasStartedEvent(Guid.Parse("2AA2D43B-5E24-4327-8029-1135B20F35CE"), Guid.NewGuid(),Guid.NewGuid(), - // DateTime.Now, "","","","",null); - - // ReconciliationHasStartedEvent r = - // new ReconciliationHasStartedEvent(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), DateTime.Now); - - // TypeProvider.LoadDomainEventsTypeDynamically(); - - // services.AddHostedService(provider => - // { - // IDomainEventHandlerResolver r = - // provider.GetRequiredService(); - // EventStorePersistentSubscriptionsClient p = provider.GetRequiredService(); - // HttpClient h = provider.GetRequiredService(); - // SubscriptionWorker worker = new SubscriptionWorker(r, p, h); - // worker.TraceGenerated += Worker_TraceGenerated; - // return worker; - // }); - // }); return hostBuilder; } - - /// - /// Workers the trace generated. - /// - /// The trace. - /// The log level. - private static void Worker_TraceGenerated(string trace, LogLevel logLevel) - { - switch (logLevel) - { - case LogLevel.Trace: - Logger.LogTrace(trace); - break; - case LogLevel.Debug: - Logger.LogDebug(trace); - break; - case LogLevel.Information: - Logger.LogInformation(trace); - break; - case LogLevel.Warning: - Logger.LogWarning(trace); - break; - case LogLevel.Error: - Logger.LogError(new Exception(trace)); - break; - case LogLevel.Critical: - Logger.LogCritical(new Exception(trace)); - break; - } - } } } diff --git a/TransactionProcessor/Startup.cs b/TransactionProcessor/Startup.cs index bf554019..4b96b831 100644 --- a/TransactionProcessor/Startup.cs +++ b/TransactionProcessor/Startup.cs @@ -1,69 +1,29 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - namespace TransactionProcessor { - using System.Diagnostics; + using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; - using System.IO.Abstractions; using System.Net.Http; - using System.Reflection; - using System.Threading; - using BusinessLogic.EventHandling; - using BusinessLogic.Manager; - using BusinessLogic.OperatorInterfaces; - using BusinessLogic.OperatorInterfaces.SafaricomPinless; - using BusinessLogic.OperatorInterfaces.VoucherManagement; - using BusinessLogic.RequestHandlers; - using BusinessLogic.Requests; - using BusinessLogic.Services; - using Common; - using EstateManagement.Client; + using Bootstrapper; using EventStore.Client; using HealthChecks.UI.Client; - using MediatR; - using MessagingService.Client; - using Microsoft.AspNetCore.Authentication.JwtBearer; + using Lamar; + using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; - using Microsoft.Extensions.Diagnostics.HealthChecks; - using Microsoft.Extensions.Options; - using Microsoft.IdentityModel.Logging; - using Microsoft.OpenApi.Models; - using Models; - using Newtonsoft.Json; - using Newtonsoft.Json.Serialization; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using Reconciliation.DomainEvents; - using SecurityService.Client; using Settlement.DomainEvents; - using SettlementAggregates; - using Shared.DomainDrivenDesign.CommandHandling; - using Shared.DomainDrivenDesign.EventSourcing; - using Shared.EntityFramework.ConnectionStringConfiguration; using Shared.EventStore.Aggregate; - using Shared.EventStore.EventHandling; - using Shared.EventStore.EventStore; - using Shared.EventStore.Extensions; - using Shared.EventStore.SubscriptionWorker; using Shared.Extensions; using Shared.General; using Shared.Logger; - using Shared.Repositories; - using Swashbuckle.AspNetCore.Filters; - using Swashbuckle.AspNetCore.SwaggerGen; using Transaction.DomainEvents; - using TransactionAggregate; - using VoucherManagement.Client; using ILogger = Microsoft.Extensions.Logging.ILogger; /// @@ -72,6 +32,19 @@ namespace TransactionProcessor [ExcludeFromCodeCoverage] public class Startup { + #region Fields + + public static Container Container; + + /// + /// The event store client settings + /// + internal static EventStoreClientSettings EventStoreClientSettings; + + #endregion + + #region Constructors + /// /// Initializes a new instance of the class. /// @@ -80,15 +53,20 @@ public Startup(IWebHostEnvironment webHostEnvironment) { IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(webHostEnvironment.ContentRootPath) .AddJsonFile("/home/txnproc/config/appsettings.json", true, true) - .AddJsonFile($"/home/txnproc/config/appsettings.{webHostEnvironment.EnvironmentName}.json", optional: true) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{webHostEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables(); + .AddJsonFile($"/home/txnproc/config/appsettings.{webHostEnvironment.EnvironmentName}.json", + optional:true).AddJsonFile("appsettings.json", optional:true, reloadOnChange:true) + .AddJsonFile($"appsettings.{webHostEnvironment.EnvironmentName}.json", + optional:true, + reloadOnChange:true).AddEnvironmentVariables(); Startup.Configuration = builder.Build(); Startup.WebHostEnvironment = webHostEnvironment; } + #endregion + + #region Properties + /// /// Gets or sets the configuration. /// @@ -97,6 +75,8 @@ public Startup(IWebHostEnvironment webHostEnvironment) /// public static IConfigurationRoot Configuration { get; set; } + public static IServiceProvider ServiceProvider { get; set; } + /// /// Gets or sets the web host environment. /// @@ -105,288 +85,9 @@ public Startup(IWebHostEnvironment webHostEnvironment) /// public static IWebHostEnvironment WebHostEnvironment { get; set; } - public static IServiceProvider ServiceProvider { get; set; } - - // This method gets called by the runtime. Use this method to add services to the container. - /// - /// Configures the services. - /// - /// The services. - public void ConfigureServices(IServiceCollection services) - { - ConfigurationReader.Initialise(Startup.Configuration); - - Startup.ConfigureEventStoreSettings(); - - this.ConfigureMiddlewareServices(services); - - services.AddTransient(); - - ConfigurationReader.Initialise(Startup.Configuration); - String connString = Startup.Configuration.GetValue("EventStoreSettings:ConnectionString"); - String connectionName = Startup.Configuration.GetValue("EventStoreSettings:ConnectionName"); - Int32 httpPort = Startup.Configuration.GetValue("EventStoreSettings:HttpPort"); - - Boolean useConnectionStringConfig = Boolean.Parse(ConfigurationReader.GetValue("AppSettings", "UseConnectionStringConfig")); - - SafaricomConfiguration safaricomConfiguration = new SafaricomConfiguration(); - - if (Startup.Configuration != null) - { - IConfigurationSection section = Startup.Configuration.GetSection("AppSettings:EventHandlerConfiguration"); - - if (section != null) - { - Startup.Configuration.GetSection("OperatorConfiguration:Safaricom").Bind(safaricomConfiguration); - } - } - - services.AddSingleton(safaricomConfiguration); - - if (useConnectionStringConfig) - { - String connectionStringConfigurationConnString = ConfigurationReader.GetConnectionString("ConnectionStringConfiguration"); - services.AddSingleton(); - services.AddTransient(c => - { - return new ConnectionStringConfigurationContext(connectionStringConfigurationConnString); - }); - - // TODO: Read this from a the database and set - } - else - { - services.AddEventStoreClient(Startup.ConfigureEventStoreSettings); - services.AddEventStoreProjectionManagementClient(Startup.ConfigureEventStoreSettings); - services.AddEventStorePersistentSubscriptionsClient(Startup.ConfigureEventStoreSettings); - } - - services.AddTransient(); - services.AddSingleton(); - services.AddSingleton, AggregateRepository>(); - services.AddSingleton, AggregateRepository>(); - services.AddSingleton, AggregateRepository>(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton>(container => (serviceName) => - { - return ConfigurationReader.GetBaseServerUri(serviceName).OriginalString; - }); - - var httpMessageHandler = new SocketsHttpHandler - { - SslOptions = - { - RemoteCertificateValidationCallback = (sender, - certificate, - chain, - errors) => true, - } - }; - HttpClient httpClient = new HttpClient(httpMessageHandler); - services.AddSingleton(httpClient); - - services.AddSingleton(); - services.AddSingleton(); - - // request & notification handlers - services.AddTransient(context => - { - return t => context.GetService(t); - }); - - services.AddSingleton, TransactionRequestHandler>(); - services.AddSingleton, TransactionRequestHandler>(); - services.AddSingleton, TransactionRequestHandler>(); - - services.AddSingleton, SettlementRequestHandler>(); - - services.AddTransient>(context => (operatorIdentifier) => - { - if (String.Compare(operatorIdentifier, "Safaricom", StringComparison.CurrentCultureIgnoreCase) == 0) - { - SafaricomConfiguration configuration = context.GetRequiredService(); - HttpClient client = context.GetRequiredService(); - return new SafaricomPinlessProxy(configuration, client); - } - else - { - // Voucher - IVoucherManagementClient voucherManagementClient = context.GetRequiredService(); - return new VoucherManagementProxy(voucherManagementClient); - - } - }); - - Dictionary eventHandlersConfiguration = new Dictionary(); - - if (Startup.Configuration != null) - { - IConfigurationSection section = Startup.Configuration.GetSection("AppSettings:EventHandlerConfiguration"); - - if (section != null) - { - Startup.Configuration.GetSection("AppSettings:EventHandlerConfiguration").Bind(eventHandlersConfiguration); - } - } - services.AddSingleton>(eventHandlersConfiguration); - - services.AddSingleton>(container => (type) => - { - IDomainEventHandler handler = container.GetService(type) as IDomainEventHandler; - return handler; - }); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - Startup.ServiceProvider = services.BuildServiceProvider(); - } - - /// - /// The event store client settings - /// - private static EventStoreClientSettings EventStoreClientSettings; - - /// - /// Configures the event store settings. - /// - /// The settings. - private static void ConfigureEventStoreSettings(EventStoreClientSettings settings = null) - { - if (settings == null) - { - settings = new EventStoreClientSettings(); - } - - settings.CreateHttpMessageHandler = () => new SocketsHttpHandler - { - SslOptions = - { - RemoteCertificateValidationCallback = (sender, - certificate, - chain, - errors) => true, - } - }; - settings.ConnectionName = Startup.Configuration.GetValue("EventStoreSettings:ConnectionName"); - settings.ConnectivitySettings = new EventStoreClientConnectivitySettings - { - Insecure = Startup.Configuration.GetValue("EventStoreSettings:Insecure"), - Address = new Uri(Startup.Configuration.GetValue("EventStoreSettings:ConnectionString")), - }; - - settings.DefaultCredentials = new UserCredentials(Startup.Configuration.GetValue("EventStoreSettings:UserName"), - Startup.Configuration.GetValue("EventStoreSettings:Password")); + #endregion - Startup.EventStoreClientSettings = settings; - } - - private HttpClientHandler ApiEndpointHttpHandler(IServiceProvider serviceProvider) - { - return new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (message, - cert, - chain, - errors) => - { - return true; - } - }; - } - - /// - /// Configures the middleware services. - /// - /// The services. - private void ConfigureMiddlewareServices(IServiceCollection services) - { - services.AddHealthChecks() - .AddEventStore(Startup.EventStoreClientSettings, - userCredentials: Startup.EventStoreClientSettings.DefaultCredentials, - name: "Eventstore", - failureStatus: HealthStatus.Unhealthy, - tags: new string[] { "db", "eventstore" }) - .AddSecurityService(this.ApiEndpointHttpHandler) - .AddEstateManagementService(); - - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Transaction Processor API", - Version = "1.0", - Description = "A REST Api to manage the processing and routing of transactions to local and remote operators.", - Contact = new OpenApiContact - { - Name = "Stuart Ferguson", - Email = "golfhandicapping@btinternet.com" - } - }); - // add a custom operation filter which sets default values - c.OperationFilter(); - c.ExampleFilters(); - - //Locate the XML files being generated by ASP.NET... - var directory = new DirectoryInfo(AppContext.BaseDirectory); - var xmlFiles = directory.GetFiles("*.xml"); - - //... and tell Swagger to use those XML comments. - foreach (FileInfo fileInfo in xmlFiles) - { - c.IncludeXmlComments(fileInfo.FullName); - } - }); - - services.AddSwaggerExamplesFromAssemblyOf(); - - IdentityModelEventSource.ShowPII = true; - - services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddJwtBearer(options => - { - options.BackchannelHttpHandler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = - (message, certificate, chain, sslPolicyErrors) => true - }; - options.Authority = ConfigurationReader.GetValue("SecurityConfiguration", "Authority"); - options.Audience = ConfigurationReader.GetValue("SecurityConfiguration", "ApiName"); - - options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters() - { - ValidateAudience = false, - ValidAudience = ConfigurationReader.GetValue("SecurityConfiguration", "ApiName"), - ValidIssuer = ConfigurationReader.GetValue("SecurityConfiguration", "Authority"), - }; - options.IncludeErrorDetails = true; - }); - - services.AddControllers().AddNewtonsoftJson(options => - { - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; - options.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto; - options.SerializerSettings.Formatting = Formatting.Indented; - options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - }); - - Assembly assembly = this.GetType().GetTypeInfo().Assembly; - services.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices(); - } + #region Methods // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// @@ -396,7 +97,9 @@ private void ConfigureMiddlewareServices(IServiceCollection services) /// The env. /// The logger factory. /// The provider. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, + IWebHostEnvironment env, + ILoggerFactory loggerFactory) { String nlogConfigFilename = "nlog.config"; @@ -412,10 +115,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF Logger.Initialise(logger); - Action loggerAction = message => - { - Logger.LogInformation(message); - }; + Action loggerAction = message => { Logger.LogInformation(message); }; Startup.Configuration.LogConfiguration(loggerAction); foreach (KeyValuePair type in TypeMap.Map) @@ -435,11 +135,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF app.UseEndpoints(endpoints => { endpoints.MapControllers(); - endpoints.MapHealthChecks("health", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); + endpoints.MapHealthChecks("health", + new HealthCheckOptions + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); }); app.UseSwagger(); @@ -449,102 +150,82 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF app.PreWarm(); } - public static void LoadTypes() + public void ConfigureContainer(ServiceRegistry services) { - SettlementCreatedForDateEvent s = - new SettlementCreatedForDateEvent(Guid.Parse("62CA5BF0-D138-4A19-9970-A4F7D52DE292"), - Guid.Parse("3E42516B-6C6F-4F86-BF08-3EF0ACDDDD55"), - DateTime.Now); - - TransactionHasStartedEvent t = new TransactionHasStartedEvent(Guid.Parse("2AA2D43B-5E24-4327-8029-1135B20F35CE"), Guid.NewGuid(), Guid.NewGuid(), - DateTime.Now, "", "", "", "", null); - - ReconciliationHasStartedEvent r = - new ReconciliationHasStartedEvent(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), DateTime.Now); - - TypeProvider.LoadDomainEventsTypeDynamically(); - } - } + ConfigurationReader.Initialise(Startup.Configuration); - public static class Extensions - { - static Action log = (tt, subType, message) => { - String logMessage = $"{subType} - {message}"; - switch (tt) - { - case TraceEventType.Critical: - Logger.LogCritical(new Exception(logMessage)); - break; - case TraceEventType.Error: - Logger.LogError(new Exception(logMessage)); - break; - case TraceEventType.Warning: - Logger.LogWarning(logMessage); - break; - case TraceEventType.Information: - Logger.LogInformation(logMessage); - break; - case TraceEventType.Verbose: - Logger.LogDebug(logMessage); - break; - } - }; + Startup.ConfigureEventStoreSettings(); - static Action concurrentLog = (tt, message) => log(tt, "CONCURRENT", message); + services.IncludeRegistry(); + services.IncludeRegistry(); + services.IncludeRegistry(); + services.IncludeRegistry(); + services.IncludeRegistry(); + services.IncludeRegistry(); + services.IncludeRegistry(); + services.IncludeRegistry(); - public static void PreWarm(this IApplicationBuilder applicationBuilder) - { Startup.LoadTypes(); - //SubscriptionWorker worker = new SubscriptionWorker() - var internalSubscriptionService = Boolean.Parse(ConfigurationReader.GetValue("InternalSubscriptionService")); + Startup.Container = new Container(services); - if (internalSubscriptionService) - { - String eventStoreConnectionString = ConfigurationReader.GetValue("EventStoreSettings", "ConnectionString"); - Int32 inflightMessages = Int32.Parse(ConfigurationReader.GetValue("AppSettings", "InflightMessages")); - Int32 persistentSubscriptionPollingInSeconds = Int32.Parse(ConfigurationReader.GetValue("AppSettings", "PersistentSubscriptionPollingInSeconds")); - String filter = ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionServiceFilter"); - String ignore = ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionServiceIgnore"); - String streamName = ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionFilterOnStreamName"); - Int32 cacheDuration = Int32.Parse(ConfigurationReader.GetValue("AppSettings", "InternalSubscriptionServiceCacheDuration")); - - ISubscriptionRepository subscriptionRepository = SubscriptionRepository.Create(eventStoreConnectionString, cacheDuration); - - ((SubscriptionRepository)subscriptionRepository).Trace += (sender, s) => Extensions.log(TraceEventType.Information, "REPOSITORY", s); - - // init our SubscriptionRepository - subscriptionRepository.PreWarm(CancellationToken.None).Wait(); + Startup.ServiceProvider = services.BuildServiceProvider(); + } - var eventHandlerResolver = Startup.ServiceProvider.GetService(); + public static void LoadTypes() + { + SettlementCreatedForDateEvent s = + new SettlementCreatedForDateEvent(Guid.Parse("62CA5BF0-D138-4A19-9970-A4F7D52DE292"), Guid.Parse("3E42516B-6C6F-4F86-BF08-3EF0ACDDDD55"), DateTime.Now); - SubscriptionWorker concurrentSubscriptions = SubscriptionWorker.CreateConcurrentSubscriptionWorker(eventStoreConnectionString, eventHandlerResolver, subscriptionRepository, inflightMessages, persistentSubscriptionPollingInSeconds); + TransactionHasStartedEvent t = new TransactionHasStartedEvent(Guid.Parse("2AA2D43B-5E24-4327-8029-1135B20F35CE"), + Guid.NewGuid(), + Guid.NewGuid(), + DateTime.Now, + "", + "", + "", + "", + null); - concurrentSubscriptions.Trace += (_, args) => concurrentLog(TraceEventType.Information, args.Message); - concurrentSubscriptions.Warning += (_, args) => concurrentLog(TraceEventType.Warning, args.Message); - concurrentSubscriptions.Error += (_, args) => concurrentLog(TraceEventType.Error, args.Message); + ReconciliationHasStartedEvent r = new ReconciliationHasStartedEvent(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), DateTime.Now); - if (!String.IsNullOrEmpty(ignore)) - { - concurrentSubscriptions = concurrentSubscriptions.IgnoreSubscriptions(ignore); - } + TypeProvider.LoadDomainEventsTypeDynamically(); + } - if (!String.IsNullOrEmpty(filter)) - { - //NOTE: Not overly happy with this design, but - //the idea is if we supply a filter, this overrides ignore - concurrentSubscriptions = concurrentSubscriptions.FilterSubscriptions(filter) - .IgnoreSubscriptions(null); + /// + /// Configures the event store settings. + /// + /// The settings. + internal static void ConfigureEventStoreSettings(EventStoreClientSettings settings = null) + { + if (settings == null) + { + settings = new EventStoreClientSettings(); + } - } + settings.CreateHttpMessageHandler = () => new SocketsHttpHandler + { + SslOptions = + { + RemoteCertificateValidationCallback = (sender, + certificate, + chain, + errors) => true, + } + }; + settings.ConnectionName = Startup.Configuration.GetValue("EventStoreSettings:ConnectionName"); + settings.ConnectivitySettings = new EventStoreClientConnectivitySettings + { + Insecure = Startup.Configuration.GetValue("EventStoreSettings:Insecure"), + Address = new Uri(Startup.Configuration.GetValue("EventStoreSettings:ConnectionString")), + }; - if (!String.IsNullOrEmpty(streamName)) - { - concurrentSubscriptions = concurrentSubscriptions.FilterByStreamName(streamName); - } + settings.DefaultCredentials = new UserCredentials(Startup.Configuration.GetValue("EventStoreSettings:UserName"), + Startup.Configuration.GetValue("EventStoreSettings:Password")); - concurrentSubscriptions.StartAsync(CancellationToken.None).Wait(); - } + Startup.EventStoreClientSettings = settings; } + + #endregion } -} +} \ No newline at end of file diff --git a/TransactionProcessor/TransactionProcessor.csproj b/TransactionProcessor/TransactionProcessor.csproj index ca38e68c..6b5e3bb7 100644 --- a/TransactionProcessor/TransactionProcessor.csproj +++ b/TransactionProcessor/TransactionProcessor.csproj @@ -10,7 +10,9 @@ - + + +