Create Generic Host prototype for background task in order to be reviewed

This commit is contained in:
Unai Zorrilla Castro 2018-05-21 15:01:29 +02:00
Родитель 094ba7c3c6
Коммит 460fc997af
9 изменённых файлов: 493 добавлений и 1 удалений

Просмотреть файл

@ -128,6 +128,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.BackgroundTasks",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ordering.SignalrHub", "src\Services\Ordering\Ordering.SignalrHub\Ordering.SignalrHub.csproj", "{E1D2B260-4E7F-4A88-BC13-9910F7C44623}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ordering.BackgroundTasksHost", "src\Services\Ordering\Ordering.BackgroundTasksHost\Ordering.BackgroundTasksHost.csproj", "{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1534,6 +1536,54 @@ Global
{E1D2B260-4E7F-4A88-BC13-9910F7C44623}.Release|x64.Build.0 = Release|Any CPU
{E1D2B260-4E7F-4A88-BC13-9910F7C44623}.Release|x86.ActiveCfg = Release|Any CPU
{E1D2B260-4E7F-4A88-BC13-9910F7C44623}.Release|x86.Build.0 = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|ARM.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|x64.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|x64.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|x86.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.AppStore|x86.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|ARM.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|ARM.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|iPhone.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|x64.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|x64.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|x86.ActiveCfg = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Debug|x86.Build.0 = Debug|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|Any CPU.Build.0 = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|ARM.ActiveCfg = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|ARM.Build.0 = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|iPhone.ActiveCfg = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|iPhone.Build.0 = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|x64.ActiveCfg = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|x64.Build.0 = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|x86.ActiveCfg = Release|Any CPU
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1589,6 +1639,7 @@ Global
{AF0828DB-8BDD-411A-AEEF-B780FBB8D8C1} = {28C0F5C8-4849-4035-80AB-45639424E73F}
{7D63ED4A-3EDA-4BBA-8BBA-F46BD6430931} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B}
{E1D2B260-4E7F-4A88-BC13-9910F7C44623} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B}
{CA566CD5-A49A-47F7-BDC9-592E36DAF74E} = {0BD0DB92-2D98-44D9-9AC0-C59186D59B0B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {25728519-5F0F-4973-8A64-0A81EB4EA8D9}

Просмотреть файл

@ -7,7 +7,10 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
<Compile Remove="wwwroot\**" />
<Content Remove="wwwroot\**" />
<EmbeddedResource Remove="wwwroot\**" />
<None Remove="wwwroot\**" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ordering.BackgroundTasksHost.Configuration
{
public class BackgroundTaskSettings
{
public string ConnectionString { get; set; }
public string EventBusConnection { get; set; }
public int GracePeriodTime { get; set; }
public int CheckUpdateTime { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
namespace Ordering.BackgroundTasksHost.IntegrationEvents
{
public class GracePeriodConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public GracePeriodConfirmedIntegrationEvent(int orderId) =>
OrderId = orderId;
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.2" />
<PackageReference Include="Dapper" Version="1.50.4" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-rc1-final" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusServiceBus\EventBusServiceBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Tasks\" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,177 @@
using Autofac.Extensions.DependencyInjection;
using Autofac;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using Ordering.BackgroundTasksHost.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusServiceBus;
using Microsoft.Azure.ServiceBus;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using RabbitMQ.Client;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Ordering.BackgroundTasksHost.Tasks;
namespace Ordering.BackgroundTasksHost
{
class Program
{
static void Main(string[] args)
{
using (var host = CreateHost(args))
{
host.Start();
host.WaitForShutdown();
}
}
static IHost CreateHost(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.AddEnvironmentVariables();
configApp.AddJsonFile("appsettings.json", optional: true);
configApp.AddJsonFile(
$"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json",
optional: true);
configApp.AddCommandLine(args);
})
.ConfigureServices(services =>
{
var configuration = services.BuildServiceProvider()
.GetRequiredService<IConfiguration>();
services.AddOptions()
.Configure<BackgroundTaskSettings>(configuration)
.RegisterBus(configuration)
.RegisterHostedServices();
})
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.Build();
return host;
}
}
class AutofacServiceProviderFactory
: IServiceProviderFactory<ContainerBuilder>
{
public ContainerBuilder CreateBuilder(IServiceCollection services)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);
return containerBuilder;
}
public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
{
return new AutofacServiceProvider(containerBuilder.Build());
}
}
static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterBus(this IServiceCollection services, IConfiguration configuration)
{
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);
});
}
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>();
return services;
}
public static IServiceCollection RegisterHostedServices(this IServiceCollection services)
{
services.AddSingleton<IHostedService, GracePeriodManagerService>();
return services;
}
}
}

Просмотреть файл

@ -0,0 +1,81 @@
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.BackgroundTasksHost.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();
}
}
}

Просмотреть файл

@ -0,0 +1,92 @@
using Dapper;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Ordering.BackgroundTasksHost.Configuration;
using Ordering.BackgroundTasksHost.IntegrationEvents;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
namespace Ordering.BackgroundTasksHost.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;
}
}
}

Просмотреть файл

@ -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": "BackgroundTasks",
"GracePeriodTime": "1",
"CheckUpdateTime": "1000",
"ApplicationInsights": {
"InstrumentationKey": ""
},
"AzureServiceBusEnabled": false,
"EventBusRetryCount": 5,
"EventBusConnection": "",
"EventBusUserName": "",
"EventBusPassword": ""
}