diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 96e34bcf2..4f2e31123 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -83,6 +83,24 @@ services: ports: - "5102:80" + ordering.backgroundtasks: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word} + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - UseCustomizationData=True + - AzureServiceBusEnabled=False + - CheckUpdateTime=30000 + - GracePeriodTime=1 + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5111:80" + marketing.api: environment: - ASPNETCORE_ENVIRONMENT=Development @@ -219,4 +237,5 @@ services: rabbitmq: ports: - "15672:15672" - - "5672:5672" \ No newline at end of file + - "5672:5672" + diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 552350251..fac480f2e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -90,6 +90,24 @@ services: ports: - "5102:80" + ordering.backgroundtasks: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionString=${ESHOP_AZURE_ORDERING_DB:-Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word} + - EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq} + - EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME} + - EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD} + - UseCustomizationData=True + - AzureServiceBusEnabled=False + - CheckUpdateTime=30000 + - GracePeriodTime=1 + - ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY} + - OrchestratorType=${ORCHESTRATOR_TYPE} + - UseLoadTest=${USE_LOADTEST:-False} + ports: + - "5111:80" + marketing.api: environment: - ASPNETCORE_ENVIRONMENT=Development diff --git a/docker-compose.yml b/docker-compose.yml index d8b7318ce..b237ad04f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,15 @@ services: - sql.data - rabbitmq + ordering.backgroundtasks: + image: eshop/ordering.backgroundtasks:${TAG:-latest} + build: + context: . + dockerfile: src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile + depends_on: + - sql.data + - rabbitmq + marketing.api: image: eshop/marketing.api:${TAG:-latest} build: @@ -106,4 +115,4 @@ services: image: redis:alpine rabbitmq: - image: rabbitmq:3-management-alpine + image: rabbitmq:3-management-alpine \ No newline at end of file diff --git a/eShopOnContainers-ServicesAndWebApps.sln b/eShopOnContainers-ServicesAndWebApps.sln index e4ddff1c2..d7d3b059c 100644 --- a/eShopOnContainers-ServicesAndWebApps.sln +++ b/eShopOnContainers-ServicesAndWebApps.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 +VisualStudioVersion = 15.0.27130.2024 MinimumVisualStudioVersion = 10.0.40219.1 Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}" EndProject @@ -97,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebHost", "WebHost", "{1815 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHost.Customization", "src\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj", "{15F4B3AA-89B6-4A0D-9051-414305974781}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ordering.BackgroundTasks", "src\Services\Ordering\Ordering.BackgroundTasks\Ordering.BackgroundTasks.csproj", "{2FF56999-0266-48B2-ACC1-FEBC482A5105}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -1311,6 +1313,54 @@ Global {15F4B3AA-89B6-4A0D-9051-414305974781}.Release|x64.Build.0 = Release|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Release|x86.ActiveCfg = Release|Any CPU {15F4B3AA-89B6-4A0D-9051-414305974781}.Release|x86.Build.0 = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|x64.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|ARM.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|ARM.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|iPhone.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|x64.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|x64.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|x86.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.AppStore|x86.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|ARM.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|ARM.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|iPhone.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|x64.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|x64.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|x86.ActiveCfg = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Debug|x86.Build.0 = Debug|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|Any CPU.Build.0 = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|ARM.ActiveCfg = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|ARM.Build.0 = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|iPhone.ActiveCfg = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|iPhone.Build.0 = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|x64.ActiveCfg = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|x64.Build.0 = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|x86.ActiveCfg = Release|Any CPU + {2FF56999-0266-48B2-ACC1-FEBC482A5105}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1357,6 +1407,7 @@ Global {969E793C-C413-490E-9C9D-B2B46DA5AF32} = {EF0337F2-ED00-4643-89FD-EE10863F1870} {1815B651-941C-466B-AE33-D1D7EEB8F77F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {15F4B3AA-89B6-4A0D-9051-414305974781} = {1815B651-941C-466B-AE33-D1D7EEB8F77F} + {2FF56999-0266-48B2-ACC1-FEBC482A5105} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} diff --git a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs index f2129be69..49a417635 100644 --- a/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs +++ b/src/BuildingBlocks/EventBus/EventBusRabbitMQ/EventBusRabbitMQ.cs @@ -92,9 +92,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ policy.Execute(() => { + var properties = channel.CreateBasicProperties(); + properties.DeliveryMode = 2; // persistent + channel.BasicPublish(exchange: BROKER_NAME, routingKey: eventName, - basicProperties: null, + mandatory:true, + basicProperties: properties, body: body); }); } @@ -184,6 +188,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ var message = Encoding.UTF8.GetString(ea.Body); await ProcessEvent(eventName, message); + + channel.BasicAck(ea.DeliveryTag,multiple:false); }; channel.BasicConsume(queue: _queueName, diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/BackgroundService.cs b/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/BackgroundService.cs index 205b3c75a..7d11b9d90 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/BackgroundService.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/BackgroundService.cs @@ -16,68 +16,68 @@ namespace Ordering.API.Infrastructure.HostedServices /// https://github.com/aspnet/Hosting/blob/712c992ca827576c05923e6a134ca0bec87af4df/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs /// /// - public abstract class BackgroundService : IHostedService, IDisposable - { - private Task _executingTask; - private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); + //public abstract class BackgroundService : IHostedService, IDisposable + //{ + // private Task _executingTask; + // private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); - /// - /// This method is called when the starts. The implementation should return a task that represents - /// the lifetime of the long running operation(s) being performed. - /// - /// Triggered when is called. - /// A that represents the long running operations. - protected abstract Task ExecuteAsync(CancellationToken stoppingToken); + // /// + // /// This method is called when the starts. The implementation should return a task that represents + // /// the lifetime of the long running operation(s) being performed. + // /// + // /// Triggered when is called. + // /// A that represents the long running operations. + // protected abstract Task ExecuteAsync(CancellationToken stoppingToken); - /// - /// Triggered when the application host is ready to start the service. - /// - /// Indicates that the start process has been aborted. - public virtual Task StartAsync(CancellationToken cancellationToken) - { - // Store the task we're executing - _executingTask = ExecuteAsync(_stoppingCts.Token); + // /// + // /// Triggered when the application host is ready to start the service. + // /// + // /// Indicates that the start process has been aborted. + // public virtual Task StartAsync(CancellationToken cancellationToken) + // { + // // Store the task we're executing + // _executingTask = ExecuteAsync(_stoppingCts.Token); - // If the task is completed then return it, this will bubble cancellation and failure to the caller - if (_executingTask.IsCompleted) - { - return _executingTask; - } + // // If the task is completed then return it, this will bubble cancellation and failure to the caller + // if (_executingTask.IsCompleted) + // { + // return _executingTask; + // } - // Otherwise it's running - return Task.CompletedTask; - } + // // Otherwise it's running + // return Task.CompletedTask; + // } - /// - /// Triggered when the application host is performing a graceful shutdown. - /// - /// Indicates that the shutdown process should no longer be graceful. - public virtual async Task StopAsync(CancellationToken cancellationToken) - { - // Stop called without start - if (_executingTask == null) - { - return; - } + // /// + // /// Triggered when the application host is performing a graceful shutdown. + // /// + // /// Indicates that the shutdown process should no longer be graceful. + // public virtual async Task StopAsync(CancellationToken cancellationToken) + // { + // // Stop called without start + // if (_executingTask == null) + // { + // return; + // } - try - { - // Signal cancellation to the executing method - _stoppingCts.Cancel(); - } - finally - { - // Wait until the task completes or the stop token triggers - await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); - } + // try + // { + // // Signal cancellation to the executing method + // _stoppingCts.Cancel(); + // } + // finally + // { + // // Wait until the task completes or the stop token triggers + // await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); + // } - } + // } - public virtual void Dispose() - { - _stoppingCts.Cancel(); - } - } + // public virtual void Dispose() + // { + // _stoppingCts.Cancel(); + // } + //} } diff --git a/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/GracePeriodManagerService.cs b/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/GracePeriodManagerService.cs index 52b1b2da7..8117ce38b 100644 --- a/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/GracePeriodManagerService.cs +++ b/src/Services/Ordering/Ordering.API/Infrastructure/HostedServices/GracePeriodManagerService.cs @@ -12,79 +12,79 @@ using System.Threading; using System.Threading.Tasks; - public class GracePeriodManagerService : BackgroundService - { - private readonly OrderingSettings _settings; - private readonly ILogger _logger; - private readonly IEventBus _eventBus; + //public class GracePeriodManagerService : BackgroundService + //{ + // private readonly OrderingSettings _settings; + // private readonly ILogger _logger; + // private readonly IEventBus _eventBus; - public GracePeriodManagerService(IOptions settings, - IEventBus eventBus, - ILogger logger) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); - _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); - } + // public GracePeriodManagerService(IOptions settings, + // IEventBus eventBus, + // ILogger logger) + // { + // _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + // _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + // _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); + // } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - _logger.LogDebug($"GracePeriod background task is starting."); + // protected override async Task ExecuteAsync(CancellationToken stoppingToken) + // { + // _logger.LogDebug($"GracePeriod background task is starting."); - stoppingToken.Register(() => _logger.LogDebug($"#1 GracePeriod background task is stopping.")); + // stoppingToken.Register(() => _logger.LogDebug($"#1 GracePeriod background task is stopping.")); - while (!stoppingToken.IsCancellationRequested) - { - _logger.LogDebug($"GracePeriod background task is doing background work."); + // while (!stoppingToken.IsCancellationRequested) + // { + // _logger.LogDebug($"GracePeriod background task is doing background work."); - CheckConfirmedGracePeriodOrders(); + // CheckConfirmedGracePeriodOrders(); - await Task.Delay(_settings.CheckUpdateTime, stoppingToken); + // await Task.Delay(_settings.CheckUpdateTime, stoppingToken); - continue; - } + // continue; + // } - _logger.LogDebug($"GracePeriod background task is stopping."); + // _logger.LogDebug($"GracePeriod background task is stopping."); - } - - private void CheckConfirmedGracePeriodOrders() - { - _logger.LogDebug($"Checking confirmed grace period orders"); - - var orderIds = GetConfirmedGracePeriodOrders(); - - _logger.LogDebug($"GracePeriod sent a ."); - foreach (var orderId in orderIds) - { - var gracePeriodConfirmedEvent = new GracePeriodConfirmedIntegrationEvent(orderId); - _eventBus.Publish(gracePeriodConfirmedEvent); - } - } - - private IEnumerable GetConfirmedGracePeriodOrders() - { - IEnumerable orderIds = new List(); - - using (var conn = new SqlConnection(_settings.ConnectionString)) - { - try - { - conn.Open(); - orderIds = conn.Query( - @"SELECT Id FROM [ordering].[orders] - WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime - AND [OrderStatusId] = 1", - new { GracePeriodTime = _settings.GracePeriodTime }); - } - catch (SqlException exception) - { - _logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}"); - } - - } - - return orderIds; - } - } + // } + + // private void CheckConfirmedGracePeriodOrders() + // { + // _logger.LogDebug($"Checking confirmed grace period orders"); + + // var orderIds = GetConfirmedGracePeriodOrders(); + + // _logger.LogDebug($"GracePeriod sent a ."); + // foreach (var orderId in orderIds) + // { + // var gracePeriodConfirmedEvent = new GracePeriodConfirmedIntegrationEvent(orderId); + // _eventBus.Publish(gracePeriodConfirmedEvent); + // } + // } + + // private IEnumerable GetConfirmedGracePeriodOrders() + // { + // IEnumerable orderIds = new List(); + + // using (var conn = new SqlConnection(_settings.ConnectionString)) + // { + // try + // { + // conn.Open(); + // orderIds = conn.Query( + // @"SELECT Id FROM [ordering].[orders] + // WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime + // AND [OrderStatusId] = 1", + // new { GracePeriodTime = _settings.GracePeriodTime }); + // } + // catch (SqlException exception) + // { + // _logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}"); + // } + + // } + + // return orderIds; + // } + //} } diff --git a/src/Services/Ordering/Ordering.API/Startup.cs b/src/Services/Ordering/Ordering.API/Startup.cs index 0c7272f6b..9fdc026fa 100644 --- a/src/Services/Ordering/Ordering.API/Startup.cs +++ b/src/Services/Ordering/Ordering.API/Startup.cs @@ -58,8 +58,8 @@ }).AddControllersAsServices(); //Injecting Controllers themselves thru DI //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services - // Configure GracePeriodManager Hosted Service - services.AddSingleton(); + //// Configure GracePeriodManager Hosted Service + //services.AddSingleton(); services.AddTransient(); diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Configuration/BackgroundTaskSettings.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Configuration/BackgroundTaskSettings.cs new file mode 100644 index 000000000..2b42f6084 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Configuration/BackgroundTaskSettings.cs @@ -0,0 +1,13 @@ +namespace Ordering.BackgroundTasks.Configuration +{ + public class BackgroundTaskSettings + { + public string ConnectionString { get; set; } + + public string EventBusConnection { get; set; } + + public int GracePeriodTime { get; set; } + + public int CheckUpdateTime { get; set; } + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile new file mode 100644 index 000000000..391d8c17b --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Dockerfile @@ -0,0 +1,18 @@ +FROM microsoft/aspnetcore:2.0.3 AS base +WORKDIR /app +EXPOSE 80 + +FROM microsoft/aspnetcore-build:2.0 AS build +WORKDIR /src +COPY . . +RUN dotnet restore -nowarn:msb3202,nu1503 +WORKDIR /src/src/Services/Ordering/Ordering.BackgroundTasks +RUN dotnet build --no-restore -c Release -o /app + +FROM build AS publish +RUN dotnet publish --no-restore -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Ordering.BackgroundTasks.dll"] \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/IntegrationEvents/GracePeriodConfirmedIntegrationEvent.cs b/src/Services/Ordering/Ordering.BackgroundTasks/IntegrationEvents/GracePeriodConfirmedIntegrationEvent.cs new file mode 100644 index 000000000..df008ad90 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/IntegrationEvents/GracePeriodConfirmedIntegrationEvent.cs @@ -0,0 +1,12 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; + +namespace Ordering.BackgroundTasks.IntegrationEvents +{ + public class GracePeriodConfirmedIntegrationEvent : IntegrationEvent + { + public int OrderId { get; } + + public GracePeriodConfirmedIntegrationEvent(int orderId) => + OrderId = orderId; + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj new file mode 100644 index 000000000..0fe3a5b36 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Ordering.BackgroundTasks.csproj @@ -0,0 +1,31 @@ + + + + netcoreapp2.0 + ..\..\..\..\docker-compose.dcproj + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs new file mode 100644 index 000000000..2ce78136f --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Program.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace Ordering.BackgroundTasks +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .ConfigureLogging((hostingContext, builder) => + { + builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + builder.AddDebug(); + builder.AddConsole(); + }).Build(); + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Properties/launchSettings.json b/src/Services/Ordering/Ordering.BackgroundTasks/Properties/launchSettings.json new file mode 100644 index 000000000..6d60a7d28 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5161/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Ordering.BackgroundTasks": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:5162/" + } + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs new file mode 100644 index 000000000..62215d126 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Startup.cs @@ -0,0 +1,158 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Azure.ServiceBus; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.HealthChecks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Ordering.BackgroundTasks.Configuration; +using Ordering.BackgroundTasks.Tasks; +using RabbitMQ.Client; +using System; + +namespace Ordering.BackgroundTasks +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public IServiceProvider ConfigureServices(IServiceCollection services) + { + //add health check for this service + services.AddHealthChecks(checks => + { + var minutes = 1; + + if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) + { + minutes = minutesParsed; + } + checks.AddSqlCheck("OrderingDb", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); + }); + + //configure settings + + services.Configure(Configuration); + services.AddOptions(); + + //configure background task + + services.AddSingleton(); + + //configure event bus related services + + if (Configuration.GetValue("AzureServiceBusEnabled")) + { + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + var serviceBusConnectionString = Configuration["EventBusConnection"]; + var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString); + + return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger); + }); + } + else + { + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + + + var factory = new ConnectionFactory() + { + HostName = Configuration["EventBusConnection"] + }; + + if (!string.IsNullOrEmpty(Configuration["EventBusUserName"])) + { + factory.UserName = Configuration["EventBusUserName"]; + } + + if (!string.IsNullOrEmpty(Configuration["EventBusPassword"])) + { + factory.Password = Configuration["EventBusPassword"]; + } + + var retryCount = 5; + if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) + { + retryCount = int.Parse(Configuration["EventBusRetryCount"]); + } + + return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount); + }); + } + + RegisterEventBus(services); + + //create autofac based service provider + var container = new ContainerBuilder(); + container.Populate(services); + + + return new AutofacServiceProvider(container.Build()); + } + + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + + } + + + private void RegisterEventBus(IServiceCollection services) + { + var subscriptionClientName = Configuration["SubscriptionClientName"]; + + if (Configuration.GetValue("AzureServiceBusEnabled")) + { + services.AddSingleton(sp => + { + var serviceBusPersisterConnection = sp.GetRequiredService(); + var iLifetimeScope = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + return new EventBusServiceBus(serviceBusPersisterConnection, logger, + eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope); + }); + } + else + { + services.AddSingleton(sp => + { + var rabbitMQPersistentConnection = sp.GetRequiredService(); + var iLifetimeScope = sp.GetRequiredService(); + var logger = sp.GetRequiredService>(); + var eventBusSubcriptionsManager = sp.GetRequiredService(); + + var retryCount = 5; + if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"])) + { + retryCount = int.Parse(Configuration["EventBusRetryCount"]); + } + + return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount); + }); + } + + services.AddSingleton(); + } + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/Base/BackgroundTask.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/Base/BackgroundTask.cs new file mode 100644 index 000000000..6611fc3ab --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/Base/BackgroundTask.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.BackgroundTasks.Tasks.Base +{ + // Copyright(c) .NET Foundation.All rights reserved. + // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + /// + /// Base class for implementing a long running . + /// IMPORTANT: This base class is implemented in .NET Core 2.1 - Since this microservice is still in .NET Core 2.0, we're using the class within the project + /// When .NET Core 2.1 is released, this class should be removed and you should use the use implemented by the framework + /// https://github.com/aspnet/Hosting/blob/712c992ca827576c05923e6a134ca0bec87af4df/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs + /// + /// + public abstract class BackgroundService : IHostedService, IDisposable + { + private Task _executingTask; + + private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); + + /// + /// This method is called when the starts. The implementation should return a task that represents + /// the lifetime of the long running operation(s) being performed. + /// + /// Triggered when is called. + /// A that represents the long running operations. + protected abstract Task ExecuteAsync(CancellationToken stoppingToken); + + /// + /// Triggered when the application host is ready to start the service. + /// + /// Indicates that the start process has been aborted. + public virtual Task StartAsync(CancellationToken cancellationToken) + { + // Store the task we're executing + _executingTask = ExecuteAsync(_stoppingCts.Token); + + // If the task is completed then return it, this will bubble cancellation and failure to the caller + if (_executingTask.IsCompleted) + { + return _executingTask; + } + + // Otherwise it's running + return Task.CompletedTask; + } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// + /// Indicates that the shutdown process should no longer be graceful. + public virtual async Task StopAsync(CancellationToken cancellationToken) + { + // Stop called without start + if (_executingTask == null) + { + return; + } + + try + { + // Signal cancellation to the executing method + _stoppingCts.Cancel(); + } + finally + { + // Wait until the task completes or the stop token triggers + await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); + } + + } + + public virtual void Dispose() + { + _stoppingCts.Cancel(); + } + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs b/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs new file mode 100644 index 000000000..c89b8cbf3 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/Tasks/GracePeriodManagerTask.cs @@ -0,0 +1,92 @@ +using Dapper; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Ordering.BackgroundTasks.Configuration; +using Ordering.BackgroundTasks.IntegrationEvents; +using Ordering.BackgroundTasks.Tasks.Base; +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Threading; +using System.Threading.Tasks; + +namespace Ordering.BackgroundTasks.Tasks +{ + public class GracePeriodManagerService + : BackgroundService + { + private readonly ILogger _logger; + private readonly BackgroundTaskSettings _settings; + private readonly IEventBus _eventBus; + + public GracePeriodManagerService(IOptions settings, + IEventBus eventBus, + ILogger logger) + { + _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); + _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogDebug($"GracePeriodManagerService is starting."); + + stoppingToken.Register(() => _logger.LogDebug($"#1 GracePeriodManagerService background task is stopping.")); + + while (!stoppingToken.IsCancellationRequested) + { + _logger.LogDebug($"GracePeriodManagerService background task is doing background work."); + + CheckConfirmedGracePeriodOrders(); + + await Task.Delay(_settings.CheckUpdateTime, stoppingToken); + } + + _logger.LogDebug($"GracePeriodManagerService background task is stopping."); + + await Task.CompletedTask; + } + + private void CheckConfirmedGracePeriodOrders() + { + _logger.LogDebug($"Checking confirmed grace period orders"); + + var orderIds = GetConfirmedGracePeriodOrders(); + + foreach (var orderId in orderIds) + { + var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId); + + _eventBus.Publish(confirmGracePeriodEvent); + } + } + + private IEnumerable GetConfirmedGracePeriodOrders() + { + IEnumerable orderIds = new List(); + + using (var conn = new SqlConnection(_settings.ConnectionString)) + { + try + { + conn.Open(); + orderIds = conn.Query( + @"SELECT Id FROM [ordering].[orders] + WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime + AND [OrderStatusId] = 1", + new { GracePeriodTime = _settings.GracePeriodTime }); + } + catch (SqlException exception) + { + _logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}"); + } + + } + + return orderIds; + } + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json new file mode 100644 index 000000000..fa8ce71a9 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json new file mode 100644 index 000000000..546374598 --- /dev/null +++ b/src/Services/Ordering/Ordering.BackgroundTasks/appsettings.json @@ -0,0 +1,27 @@ +{ + "ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;", + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Debug" + } + }, + "Console": { + "LogLevel": { + "Default": "Debug" + } + } + }, + "SubscriptionClientName": "Ordering", + "GracePeriodTime": "1", + "CheckUpdateTime": "1000", + "ApplicationInsights": { + "InstrumentationKey": "" + }, + "AzureServiceBusEnabled": false, + "EventBusRetryCount": 5, + "EventBusConnection": "", + "EventBusUserName": "", + "EventBusPassword": "" +} \ No newline at end of file diff --git a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs index d2e742b33..428d97580 100644 --- a/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs +++ b/src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs @@ -95,8 +95,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O { if (_orderStatusId == OrderStatus.Submitted.Id) { - AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems)); _orderStatusId = OrderStatus.AwaitingValidation.Id; + + AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems)); } } @@ -104,10 +105,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O { if (_orderStatusId == OrderStatus.AwaitingValidation.Id) { - AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id)); - _orderStatusId = OrderStatus.StockConfirmed.Id; _description = "All the items were confirmed with available stock."; + + AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id)); } } @@ -115,10 +116,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O { if (_orderStatusId == OrderStatus.StockConfirmed.Id) { - AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems)); - _orderStatusId = OrderStatus.Paid.Id; _description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\""; + + AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems)); } }