Implemented HostedService in different service

This commit is contained in:
Unai Zorrilla Castro 2018-01-18 17:29:23 +01:00
parent e869b67c6d
commit 025ab0e14c
20 changed files with 723 additions and 124 deletions

View File

@ -83,6 +83,24 @@ services:
ports: ports:
- "5102:80" - "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: marketing.api:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development
@ -219,4 +237,5 @@ services:
rabbitmq: rabbitmq:
ports: ports:
- "15672:15672" - "15672:15672"
- "5672:5672" - "5672:5672"

View File

@ -90,6 +90,24 @@ services:
ports: ports:
- "5102:80" - "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: marketing.api:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Development

View File

@ -38,6 +38,15 @@ services:
- sql.data - sql.data
- rabbitmq - 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: marketing.api:
image: eshop/marketing.api:${TAG:-latest} image: eshop/marketing.api:${TAG:-latest}
build: build:
@ -106,4 +115,4 @@ services:
image: redis:alpine image: redis:alpine
rabbitmq: rabbitmq:
image: rabbitmq:3-management-alpine image: rabbitmq:3-management-alpine

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27004.2009 VisualStudioVersion = 15.0.27130.2024
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}" Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{FEA0C318-FFED-4D39-8781-265718CA43DD}"
EndProject EndProject
@ -97,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebHost", "WebHost", "{1815
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHost.Customization", "src\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj", "{15F4B3AA-89B6-4A0D-9051-414305974781}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHost.Customization", "src\BuildingBlocks\WebHostCustomization\WebHost.Customization\WebHost.Customization.csproj", "{15F4B3AA-89B6-4A0D-9051-414305974781}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ordering.BackgroundTasks", "src\Services\Ordering\Ordering.BackgroundTasks\Ordering.BackgroundTasks.csproj", "{2FF56999-0266-48B2-ACC1-FEBC482A5105}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU 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|x64.Build.0 = Release|Any CPU
{15F4B3AA-89B6-4A0D-9051-414305974781}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1357,6 +1407,7 @@ Global
{969E793C-C413-490E-9C9D-B2B46DA5AF32} = {EF0337F2-ED00-4643-89FD-EE10863F1870} {969E793C-C413-490E-9C9D-B2B46DA5AF32} = {EF0337F2-ED00-4643-89FD-EE10863F1870}
{1815B651-941C-466B-AE33-D1D7EEB8F77F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25} {1815B651-941C-466B-AE33-D1D7EEB8F77F} = {DB0EFB20-B024-4E5E-A75C-52143C131D25}
{15F4B3AA-89B6-4A0D-9051-414305974781} = {1815B651-941C-466B-AE33-D1D7EEB8F77F} {15F4B3AA-89B6-4A0D-9051-414305974781} = {1815B651-941C-466B-AE33-D1D7EEB8F77F}
{2FF56999-0266-48B2-ACC1-FEBC482A5105} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9} SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}

View File

@ -92,9 +92,13 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
policy.Execute(() => policy.Execute(() =>
{ {
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent
channel.BasicPublish(exchange: BROKER_NAME, channel.BasicPublish(exchange: BROKER_NAME,
routingKey: eventName, routingKey: eventName,
basicProperties: null, mandatory:true,
basicProperties: properties,
body: body); body: body);
}); });
} }
@ -184,6 +188,8 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ
var message = Encoding.UTF8.GetString(ea.Body); var message = Encoding.UTF8.GetString(ea.Body);
await ProcessEvent(eventName, message); await ProcessEvent(eventName, message);
channel.BasicAck(ea.DeliveryTag,multiple:false);
}; };
channel.BasicConsume(queue: _queueName, channel.BasicConsume(queue: _queueName,

View File

@ -16,68 +16,68 @@ namespace Ordering.API.Infrastructure.HostedServices
/// https://github.com/aspnet/Hosting/blob/712c992ca827576c05923e6a134ca0bec87af4df/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs /// https://github.com/aspnet/Hosting/blob/712c992ca827576c05923e6a134ca0bec87af4df/src/Microsoft.Extensions.Hosting.Abstractions/BackgroundService.cs
/// ///
/// </summary> /// </summary>
public abstract class BackgroundService : IHostedService, IDisposable //public abstract class BackgroundService : IHostedService, IDisposable
{ //{
private Task _executingTask; // private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); // private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
/// <summary> // /// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents // /// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
/// the lifetime of the long running operation(s) being performed. // /// the lifetime of the long running operation(s) being performed.
/// </summary> // /// </summary>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param> // /// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns> // /// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
protected abstract Task ExecuteAsync(CancellationToken stoppingToken); // protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
/// <summary> // /// <summary>
/// Triggered when the application host is ready to start the service. // /// Triggered when the application host is ready to start the service.
/// </summary> // /// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param> // /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
public virtual Task StartAsync(CancellationToken cancellationToken) // public virtual Task StartAsync(CancellationToken cancellationToken)
{ // {
// Store the task we're executing // // Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token); // _executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller // // If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted) // if (_executingTask.IsCompleted)
{ // {
return _executingTask; // return _executingTask;
} // }
// Otherwise it's running // // Otherwise it's running
return Task.CompletedTask; // return Task.CompletedTask;
} // }
/// <summary> // /// <summary>
/// Triggered when the application host is performing a graceful shutdown. // /// Triggered when the application host is performing a graceful shutdown.
/// </summary> // /// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param> // /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
public virtual async Task StopAsync(CancellationToken cancellationToken) // public virtual async Task StopAsync(CancellationToken cancellationToken)
{ // {
// Stop called without start // // Stop called without start
if (_executingTask == null) // if (_executingTask == null)
{ // {
return; // return;
} // }
try // try
{ // {
// Signal cancellation to the executing method // // Signal cancellation to the executing method
_stoppingCts.Cancel(); // _stoppingCts.Cancel();
} // }
finally // finally
{ // {
// Wait until the task completes or the stop token triggers // // Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); // await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
} // }
} // }
public virtual void Dispose() // public virtual void Dispose()
{ // {
_stoppingCts.Cancel(); // _stoppingCts.Cancel();
} // }
} //}
} }

View File

@ -12,79 +12,79 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
public class GracePeriodManagerService : BackgroundService //public class GracePeriodManagerService : BackgroundService
{ //{
private readonly OrderingSettings _settings; // private readonly OrderingSettings _settings;
private readonly ILogger<GracePeriodManagerService> _logger; // private readonly ILogger<GracePeriodManagerService> _logger;
private readonly IEventBus _eventBus; // private readonly IEventBus _eventBus;
public GracePeriodManagerService(IOptions<OrderingSettings> settings, // public GracePeriodManagerService(IOptions<OrderingSettings> settings,
IEventBus eventBus, // IEventBus eventBus,
ILogger<GracePeriodManagerService> logger) // ILogger<GracePeriodManagerService> logger)
{ // {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); // _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); // _eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings)); // _settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
} // }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) // protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ // {
_logger.LogDebug($"GracePeriod background task is starting."); // _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) // while (!stoppingToken.IsCancellationRequested)
{ // {
_logger.LogDebug($"GracePeriod background task is doing background work."); // _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() // private void CheckConfirmedGracePeriodOrders()
{ // {
_logger.LogDebug($"Checking confirmed grace period orders"); // _logger.LogDebug($"Checking confirmed grace period orders");
var orderIds = GetConfirmedGracePeriodOrders(); // var orderIds = GetConfirmedGracePeriodOrders();
_logger.LogDebug($"GracePeriod sent a ."); // _logger.LogDebug($"GracePeriod sent a .");
foreach (var orderId in orderIds) // foreach (var orderId in orderIds)
{ // {
var gracePeriodConfirmedEvent = new GracePeriodConfirmedIntegrationEvent(orderId); // var gracePeriodConfirmedEvent = new GracePeriodConfirmedIntegrationEvent(orderId);
_eventBus.Publish(gracePeriodConfirmedEvent); // _eventBus.Publish(gracePeriodConfirmedEvent);
} // }
} // }
private IEnumerable<int> GetConfirmedGracePeriodOrders() // private IEnumerable<int> GetConfirmedGracePeriodOrders()
{ // {
IEnumerable<int> orderIds = new List<int>(); // IEnumerable<int> orderIds = new List<int>();
using (var conn = new SqlConnection(_settings.ConnectionString)) // using (var conn = new SqlConnection(_settings.ConnectionString))
{ // {
try // try
{ // {
conn.Open(); // conn.Open();
orderIds = conn.Query<int>( // orderIds = conn.Query<int>(
@"SELECT Id FROM [ordering].[orders] // @"SELECT Id FROM [ordering].[orders]
WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime // WHERE DATEDIFF(minute, [OrderDate], GETDATE()) >= @GracePeriodTime
AND [OrderStatusId] = 1", // AND [OrderStatusId] = 1",
new { GracePeriodTime = _settings.GracePeriodTime }); // new { GracePeriodTime = _settings.GracePeriodTime });
} // }
catch (SqlException exception) // catch (SqlException exception)
{ // {
_logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}"); // _logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}");
} // }
} // }
return orderIds; // return orderIds;
} // }
} //}
} }

View File

@ -58,8 +58,8 @@
}).AddControllersAsServices(); //Injecting Controllers themselves thru DI }).AddControllersAsServices(); //Injecting Controllers themselves thru DI
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services //For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
// Configure GracePeriodManager Hosted Service //// Configure GracePeriodManager Hosted Service
services.AddSingleton<IHostedService, GracePeriodManagerService>(); //services.AddSingleton<IHostedService, GracePeriodManagerService>();
services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>(); services.AddTransient<IOrderingIntegrationEventService, OrderingIntegrationEventService>();

View File

@ -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; }
}
}

View File

@ -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"]

View File

@ -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;
}
}

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<DockerComposeProjectPath>..\..\..\..\docker-compose.dcproj</DockerComposeProjectPath>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.0" />
<PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.AspNetCore.HealthChecks\Microsoft.AspNetCore.HealthChecks.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks.SqlServer\Microsoft.Extensions.HealthChecks.SqlServer.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\HealthChecks\src\Microsoft.Extensions.HealthChecks\Microsoft.Extensions.HealthChecks.csproj" />
</ItemGroup>
</Project>

View File

@ -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<Startup>()
.ConfigureLogging((hostingContext, builder) =>
{
builder.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
builder.AddDebug();
builder.AddConsole();
}).Build();
}
}

View File

@ -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/"
}
}
}

View File

@ -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<BackgroundTaskSettings>(Configuration);
services.AddOptions();
//configure background task
services.AddSingleton<IHostedService, GracePeriodManagerService>();
//configure event bus related services
if (Configuration.GetValue<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IServiceBusPersisterConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultServiceBusPersisterConnection>>();
var serviceBusConnectionString = Configuration["EventBusConnection"];
var serviceBusConnection = new ServiceBusConnectionStringBuilder(serviceBusConnectionString);
return new DefaultServiceBusPersisterConnection(serviceBusConnection, logger);
});
}
else
{
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
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<bool>("AzureServiceBusEnabled"))
{
services.AddSingleton<IEventBus, EventBusServiceBus>(sp =>
{
var serviceBusPersisterConnection = sp.GetRequiredService<IServiceBusPersisterConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusServiceBus>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
return new EventBusServiceBus(serviceBusPersisterConnection, logger,
eventBusSubcriptionsManager, subscriptionClientName, iLifetimeScope);
});
}
else
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
{
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var retryCount = 5;
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
}
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
}
}
}

View File

@ -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.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// 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
///
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
/// the lifetime of the long running operation(s) being performed.
/// </summary>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
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;
}
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
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();
}
}
}

View File

@ -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<GracePeriodManagerService> _logger;
private readonly BackgroundTaskSettings _settings;
private readonly IEventBus _eventBus;
public GracePeriodManagerService(IOptions<BackgroundTaskSettings> settings,
IEventBus eventBus,
ILogger<GracePeriodManagerService> 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<int> GetConfirmedGracePeriodOrders()
{
IEnumerable<int> orderIds = new List<int>();
using (var conn = new SqlConnection(_settings.ConnectionString))
{
try
{
conn.Open();
orderIds = conn.Query<int>(
@"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;
}
}
}

View File

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@ -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": ""
}

View File

@ -95,8 +95,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
{ {
if (_orderStatusId == OrderStatus.Submitted.Id) if (_orderStatusId == OrderStatus.Submitted.Id)
{ {
AddDomainEvent(new OrderStatusChangedToAwaitingValidationDomainEvent(Id, _orderItems));
_orderStatusId = OrderStatus.AwaitingValidation.Id; _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) if (_orderStatusId == OrderStatus.AwaitingValidation.Id)
{ {
AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id));
_orderStatusId = OrderStatus.StockConfirmed.Id; _orderStatusId = OrderStatus.StockConfirmed.Id;
_description = "All the items were confirmed with available stock."; _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) if (_orderStatusId == OrderStatus.StockConfirmed.Id)
{ {
AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));
_orderStatusId = OrderStatus.Paid.Id; _orderStatusId = OrderStatus.Paid.Id;
_description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\""; _description = "The payment was performed at a simulated \"American Bank checking bank account endinf on XX35071\"";
AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));
} }
} }