From f747c1e5faac112ec394f43b98f81f5a3a877563 Mon Sep 17 00:00:00 2001 From: Steven Giesel Date: Fri, 22 Mar 2024 12:38:57 +0100 Subject: [PATCH] refactor: Use NCronJob for background services --- .../Features/BlogPostPublisher.cs | 31 +++++----------- .../TransformBlogPostRecordsService.cs | 36 ++++++++----------- .../LinkDotNet.Blog.Web.csproj | 1 + ...BackgroundServiceRegistrationExtensions.cs | 7 ++-- .../Web/Features/BlogPostPublisherTests.cs | 17 +++------ .../TransformBlogPostRecordsServiceTests.cs | 13 ++++--- 6 files changed, 41 insertions(+), 64 deletions(-) diff --git a/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs b/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs index d74eb2bb..c68f41c0 100644 --- a/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs +++ b/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs @@ -5,38 +5,28 @@ using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.Web.Features.Services; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using LinkDotNet.NCronJob; using Microsoft.Extensions.Logging; namespace LinkDotNet.Blog.Web.Features; -public sealed partial class BlogPostPublisher : BackgroundService +public sealed partial class BlogPostPublisher : IJob { - private readonly IServiceProvider serviceProvider; private readonly ILogger logger; + private readonly IRepository repository; private readonly ICacheInvalidator cacheInvalidator; - public BlogPostPublisher(IServiceProvider serviceProvider, ICacheInvalidator cacheInvalidator, ILogger logger) + public BlogPostPublisher(IRepository repository, ICacheInvalidator cacheInvalidator, ILogger logger) { - this.serviceProvider = serviceProvider; + this.repository = repository; this.cacheInvalidator = cacheInvalidator; this.logger = logger; } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + public async Task RunAsync(JobExecutionContext context, CancellationToken token) { LogPublishStarting(); - - using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); - - while (!stoppingToken.IsCancellationRequested) - { - await PublishScheduledBlogPostsAsync(); - - await timer.WaitForNextTickAsync(stoppingToken); - } - + await PublishScheduledBlogPostsAsync(); LogPublishStopping(); } @@ -44,10 +34,7 @@ private async Task PublishScheduledBlogPostsAsync() { LogCheckingForScheduledBlogPosts(); - using var scope = serviceProvider.CreateScope(); - var repository = scope.ServiceProvider.GetRequiredService>(); - - var blogPostsToPublish = await GetScheduledBlogPostsAsync(repository); + var blogPostsToPublish = await GetScheduledBlogPostsAsync(); foreach (var blogPost in blogPostsToPublish) { blogPost.Publish(); @@ -61,7 +48,7 @@ private async Task PublishScheduledBlogPostsAsync() } } - private async Task> GetScheduledBlogPostsAsync(IRepository repository) + private async Task> GetScheduledBlogPostsAsync() { var now = DateTime.UtcNow; var scheduledBlogPosts = await repository.GetAllAsync( diff --git a/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsService.cs b/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsService.cs index f8e5a1e0..59ec79bb 100644 --- a/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsService.cs +++ b/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsService.cs @@ -5,35 +5,34 @@ using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using LinkDotNet.NCronJob; using Microsoft.Extensions.Logging; namespace LinkDotNet.Blog.Web.Features; -public sealed partial class TransformBlogPostRecordsService : BackgroundService +public sealed partial class TransformBlogPostRecordsService : IJob { - private readonly IServiceProvider services; + private readonly IRepository blogPostRepository; + private readonly IRepository userRecordRepository; + private readonly IRepository blogPostRecordRepository; private readonly ILogger logger; - public TransformBlogPostRecordsService(IServiceProvider services, ILogger logger) + public TransformBlogPostRecordsService( + IRepository blogPostRepository, + IRepository userRecordRepository, + IRepository blogPostRecordRepository, + ILogger logger) { - this.services = services; + this.blogPostRepository = blogPostRepository; + this.userRecordRepository = userRecordRepository; + this.blogPostRecordRepository = blogPostRecordRepository; this.logger = logger; } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + public async Task RunAsync(JobExecutionContext context, CancellationToken token) { LogTransformStarted(); - - using var timer = new PeriodicTimer(TimeSpan.FromHours(1)); - while (!stoppingToken.IsCancellationRequested) - { - await TransformRecordsAsync(); - - await timer.WaitForNextTickAsync(stoppingToken); - } - + await TransformRecordsAsync(); LogTransformStopped(); } @@ -85,11 +84,6 @@ private static IEnumerable MergeRecords( private async Task TransformRecordsAsync() { - using var scope = services.CreateScope(); - var blogPostRepository = scope.ServiceProvider.GetRequiredService>(); - var userRecordRepository = scope.ServiceProvider.GetRequiredService>(); - var blogPostRecordRepository = scope.ServiceProvider.GetRequiredService>(); - var blogPosts = await blogPostRepository.GetAllAsync(); var userRecords = await userRecordRepository.GetAllAsync( filter: r => r.UrlClicked.StartsWith("blogPost/")); diff --git a/src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj b/src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj index 503eceb2..e66be4e0 100644 --- a/src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj +++ b/src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj @@ -8,6 +8,7 @@ + diff --git a/src/LinkDotNet.Blog.Web/RegistrationExtensions/BackgroundServiceRegistrationExtensions.cs b/src/LinkDotNet.Blog.Web/RegistrationExtensions/BackgroundServiceRegistrationExtensions.cs index 2d417ea8..5bc997da 100644 --- a/src/LinkDotNet.Blog.Web/RegistrationExtensions/BackgroundServiceRegistrationExtensions.cs +++ b/src/LinkDotNet.Blog.Web/RegistrationExtensions/BackgroundServiceRegistrationExtensions.cs @@ -1,4 +1,5 @@ using LinkDotNet.Blog.Web.Features; +using LinkDotNet.NCronJob; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -13,7 +14,9 @@ public static void AddBackgroundServices(this IServiceCollection services) options.ServicesStartConcurrently = true; options.ServicesStopConcurrently = true; }); - services.AddHostedService(); - services.AddHostedService(); + + services.AddNCronJob(); + services.AddCronJob(p => p.CronExpression = "* * * * *"); + services.AddCronJob(p => p.CronExpression = "0 * * * *"); } } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs index aba8e73e..390c7c2d 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs @@ -5,25 +5,20 @@ using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features; using LinkDotNet.Blog.Web.Features.Services; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace LinkDotNet.Blog.IntegrationTests.Web.Features; -public sealed class BlogPostPublisherTests : SqlDatabaseTestBase, IDisposable +public sealed class BlogPostPublisherTests : SqlDatabaseTestBase { private readonly BlogPostPublisher sut; private readonly ICacheInvalidator cacheInvalidator; public BlogPostPublisherTests() { - var serviceProvider = new ServiceCollection() - .AddScoped(_ => Repository) - .BuildServiceProvider(); - cacheInvalidator = Substitute.For(); - sut = new BlogPostPublisher(serviceProvider, cacheInvalidator, Substitute.For>()); + sut = new BlogPostPublisher(Repository, cacheInvalidator, Substitute.For>()); } [Fact] @@ -37,7 +32,7 @@ public async Task ShouldPublishScheduledBlogPosts() await Repository.StoreAsync(bp2); await Repository.StoreAsync(bp3); - await sut.StartAsync(CancellationToken.None); + await sut.RunAsync(new(null), CancellationToken.None); (await Repository.GetByIdAsync(bp1.Id)).IsPublished.Should().BeTrue(); (await Repository.GetByIdAsync(bp2.Id)).IsPublished.Should().BeTrue(); @@ -51,7 +46,7 @@ public async Task ShouldInvalidateCacheWhenPublishing() var bp1 = new BlogPostBuilder().WithScheduledPublishDate(now.AddHours(-3)).IsPublished(false).Build(); await Repository.StoreAsync(bp1); - await sut.StartAsync(CancellationToken.None); + await sut.RunAsync(new(null), CancellationToken.None); cacheInvalidator.Received().Cancel(); } @@ -59,10 +54,8 @@ public async Task ShouldInvalidateCacheWhenPublishing() [Fact] public async Task ShouldNotInvalidateCacheWhenThereIsNothingToPublish() { - await sut.StartAsync(CancellationToken.None); + await sut.RunAsync(new(null), CancellationToken.None); cacheInvalidator.DidNotReceive().Cancel(); } - - public void Dispose() => sut?.Dispose(); } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsServiceTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsServiceTests.cs index a991b9f2..42f52a32 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsServiceTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsServiceTests.cs @@ -24,13 +24,12 @@ public TransformBlogPostRecordsServiceTests() new Repository(DbContextFactory, Substitute.For>>()); userRecordRepository = new Repository(DbContextFactory, Substitute.For>>()); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddScoped(_ => Repository); - serviceCollection.AddScoped(_ => blogPostRecordRepository); - serviceCollection.AddScoped(_ => userRecordRepository); - sut = new TransformBlogPostRecordsService(serviceCollection.BuildServiceProvider(), Substitute.For>()); + sut = new TransformBlogPostRecordsService( + Repository, + userRecordRepository, + blogPostRecordRepository, + Substitute.For>()); } [Fact] @@ -58,7 +57,7 @@ public async Task ShouldTransformRecords() await userRecordRepository.StoreBulkAsync(userRecords); // Act - await sut.StartAsync(default); + await sut.RunAsync(new(null), default); // Assert var afterUserRecords = await userRecordRepository.GetAllAsync();