Skip to content

Commit f747c1e

Browse files
committed
refactor: Use NCronJob for background services
1 parent 55cd446 commit f747c1e

File tree

6 files changed

+41
-64
lines changed

6 files changed

+41
-64
lines changed

src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,36 @@
55
using LinkDotNet.Blog.Infrastructure;
66
using LinkDotNet.Blog.Infrastructure.Persistence;
77
using LinkDotNet.Blog.Web.Features.Services;
8-
using Microsoft.Extensions.DependencyInjection;
9-
using Microsoft.Extensions.Hosting;
8+
using LinkDotNet.NCronJob;
109
using Microsoft.Extensions.Logging;
1110

1211
namespace LinkDotNet.Blog.Web.Features;
1312

14-
public sealed partial class BlogPostPublisher : BackgroundService
13+
public sealed partial class BlogPostPublisher : IJob
1514
{
16-
private readonly IServiceProvider serviceProvider;
1715
private readonly ILogger<BlogPostPublisher> logger;
16+
private readonly IRepository<BlogPost> repository;
1817
private readonly ICacheInvalidator cacheInvalidator;
1918

20-
public BlogPostPublisher(IServiceProvider serviceProvider, ICacheInvalidator cacheInvalidator, ILogger<BlogPostPublisher> logger)
19+
public BlogPostPublisher(IRepository<BlogPost> repository, ICacheInvalidator cacheInvalidator, ILogger<BlogPostPublisher> logger)
2120
{
22-
this.serviceProvider = serviceProvider;
21+
this.repository = repository;
2322
this.cacheInvalidator = cacheInvalidator;
2423
this.logger = logger;
2524
}
2625

27-
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
26+
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
2827
{
2928
LogPublishStarting();
30-
31-
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
32-
33-
while (!stoppingToken.IsCancellationRequested)
34-
{
35-
await PublishScheduledBlogPostsAsync();
36-
37-
await timer.WaitForNextTickAsync(stoppingToken);
38-
}
39-
29+
await PublishScheduledBlogPostsAsync();
4030
LogPublishStopping();
4131
}
4232

4333
private async Task PublishScheduledBlogPostsAsync()
4434
{
4535
LogCheckingForScheduledBlogPosts();
4636

47-
using var scope = serviceProvider.CreateScope();
48-
var repository = scope.ServiceProvider.GetRequiredService<IRepository<BlogPost>>();
49-
50-
var blogPostsToPublish = await GetScheduledBlogPostsAsync(repository);
37+
var blogPostsToPublish = await GetScheduledBlogPostsAsync();
5138
foreach (var blogPost in blogPostsToPublish)
5239
{
5340
blogPost.Publish();
@@ -61,7 +48,7 @@ private async Task PublishScheduledBlogPostsAsync()
6148
}
6249
}
6350

64-
private async Task<IPagedList<BlogPost>> GetScheduledBlogPostsAsync(IRepository<BlogPost> repository)
51+
private async Task<IPagedList<BlogPost>> GetScheduledBlogPostsAsync()
6552
{
6653
var now = DateTime.UtcNow;
6754
var scheduledBlogPosts = await repository.GetAllAsync(

src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsService.cs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,34 @@
55
using System.Threading.Tasks;
66
using LinkDotNet.Blog.Domain;
77
using LinkDotNet.Blog.Infrastructure.Persistence;
8-
using Microsoft.Extensions.DependencyInjection;
9-
using Microsoft.Extensions.Hosting;
8+
using LinkDotNet.NCronJob;
109
using Microsoft.Extensions.Logging;
1110

1211
namespace LinkDotNet.Blog.Web.Features;
1312

14-
public sealed partial class TransformBlogPostRecordsService : BackgroundService
13+
public sealed partial class TransformBlogPostRecordsService : IJob
1514
{
16-
private readonly IServiceProvider services;
15+
private readonly IRepository<BlogPost> blogPostRepository;
16+
private readonly IRepository<UserRecord> userRecordRepository;
17+
private readonly IRepository<BlogPostRecord> blogPostRecordRepository;
1718
private readonly ILogger<TransformBlogPostRecordsService> logger;
1819

19-
public TransformBlogPostRecordsService(IServiceProvider services, ILogger<TransformBlogPostRecordsService> logger)
20+
public TransformBlogPostRecordsService(
21+
IRepository<BlogPost> blogPostRepository,
22+
IRepository<UserRecord> userRecordRepository,
23+
IRepository<BlogPostRecord> blogPostRecordRepository,
24+
ILogger<TransformBlogPostRecordsService> logger)
2025
{
21-
this.services = services;
26+
this.blogPostRepository = blogPostRepository;
27+
this.userRecordRepository = userRecordRepository;
28+
this.blogPostRecordRepository = blogPostRecordRepository;
2229
this.logger = logger;
2330
}
2431

25-
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
32+
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
2633
{
2734
LogTransformStarted();
28-
29-
using var timer = new PeriodicTimer(TimeSpan.FromHours(1));
30-
while (!stoppingToken.IsCancellationRequested)
31-
{
32-
await TransformRecordsAsync();
33-
34-
await timer.WaitForNextTickAsync(stoppingToken);
35-
}
36-
35+
await TransformRecordsAsync();
3736
LogTransformStopped();
3837
}
3938

@@ -85,11 +84,6 @@ private static IEnumerable<BlogPostRecord> MergeRecords(
8584

8685
private async Task TransformRecordsAsync()
8786
{
88-
using var scope = services.CreateScope();
89-
var blogPostRepository = scope.ServiceProvider.GetRequiredService<IRepository<BlogPost>>();
90-
var userRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<UserRecord>>();
91-
var blogPostRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<BlogPostRecord>>();
92-
9387
var blogPosts = await blogPostRepository.GetAllAsync();
9488
var userRecords = await userRecordRepository.GetAllAsync(
9589
filter: r => r.UrlClicked.StartsWith("blogPost/"));

src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
99
<PackageReference Include="Blazored.Toast" Version="4.2.1" />
1010
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" PrivateAssets="all" />
11+
<PackageReference Include="LinkDotNet.NCronJob" Version="0.12.0" />
1112
<PackageReference Include="Markdig" Version="0.36.2" />
1213
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.3" />
1314
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />

src/LinkDotNet.Blog.Web/RegistrationExtensions/BackgroundServiceRegistrationExtensions.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using LinkDotNet.Blog.Web.Features;
2+
using LinkDotNet.NCronJob;
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.Extensions.Hosting;
45

@@ -13,7 +14,9 @@ public static void AddBackgroundServices(this IServiceCollection services)
1314
options.ServicesStartConcurrently = true;
1415
options.ServicesStopConcurrently = true;
1516
});
16-
services.AddHostedService<BlogPostPublisher>();
17-
services.AddHostedService<TransformBlogPostRecordsService>();
17+
18+
services.AddNCronJob();
19+
services.AddCronJob<BlogPostPublisher>(p => p.CronExpression = "* * * * *");
20+
services.AddCronJob<TransformBlogPostRecordsService>(p => p.CronExpression = "0 * * * *");
1821
}
1922
}

tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,20 @@
55
using LinkDotNet.Blog.TestUtilities;
66
using LinkDotNet.Blog.Web.Features;
77
using LinkDotNet.Blog.Web.Features.Services;
8-
using Microsoft.Extensions.DependencyInjection;
98
using Microsoft.Extensions.Logging;
109

1110
namespace LinkDotNet.Blog.IntegrationTests.Web.Features;
1211

13-
public sealed class BlogPostPublisherTests : SqlDatabaseTestBase<BlogPost>, IDisposable
12+
public sealed class BlogPostPublisherTests : SqlDatabaseTestBase<BlogPost>
1413
{
1514
private readonly BlogPostPublisher sut;
1615
private readonly ICacheInvalidator cacheInvalidator;
1716

1817
public BlogPostPublisherTests()
1918
{
20-
var serviceProvider = new ServiceCollection()
21-
.AddScoped(_ => Repository)
22-
.BuildServiceProvider();
23-
2419
cacheInvalidator = Substitute.For<ICacheInvalidator>();
2520

26-
sut = new BlogPostPublisher(serviceProvider, cacheInvalidator, Substitute.For<ILogger<BlogPostPublisher>>());
21+
sut = new BlogPostPublisher(Repository, cacheInvalidator, Substitute.For<ILogger<BlogPostPublisher>>());
2722
}
2823

2924
[Fact]
@@ -37,7 +32,7 @@ public async Task ShouldPublishScheduledBlogPosts()
3732
await Repository.StoreAsync(bp2);
3833
await Repository.StoreAsync(bp3);
3934

40-
await sut.StartAsync(CancellationToken.None);
35+
await sut.RunAsync(new(null), CancellationToken.None);
4136

4237
(await Repository.GetByIdAsync(bp1.Id)).IsPublished.Should().BeTrue();
4338
(await Repository.GetByIdAsync(bp2.Id)).IsPublished.Should().BeTrue();
@@ -51,18 +46,16 @@ public async Task ShouldInvalidateCacheWhenPublishing()
5146
var bp1 = new BlogPostBuilder().WithScheduledPublishDate(now.AddHours(-3)).IsPublished(false).Build();
5247
await Repository.StoreAsync(bp1);
5348

54-
await sut.StartAsync(CancellationToken.None);
49+
await sut.RunAsync(new(null), CancellationToken.None);
5550

5651
cacheInvalidator.Received().Cancel();
5752
}
5853

5954
[Fact]
6055
public async Task ShouldNotInvalidateCacheWhenThereIsNothingToPublish()
6156
{
62-
await sut.StartAsync(CancellationToken.None);
57+
await sut.RunAsync(new(null), CancellationToken.None);
6358

6459
cacheInvalidator.DidNotReceive().Cancel();
6560
}
66-
67-
public void Dispose() => sut?.Dispose();
6861
}

tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsServiceTests.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ public TransformBlogPostRecordsServiceTests()
2424
new Repository<BlogPostRecord>(DbContextFactory, Substitute.For<ILogger<Repository<BlogPostRecord>>>());
2525
userRecordRepository =
2626
new Repository<UserRecord>(DbContextFactory, Substitute.For<ILogger<Repository<UserRecord>>>());
27-
28-
var serviceCollection = new ServiceCollection();
29-
serviceCollection.AddScoped(_ => Repository);
30-
serviceCollection.AddScoped(_ => blogPostRecordRepository);
31-
serviceCollection.AddScoped(_ => userRecordRepository);
3227

33-
sut = new TransformBlogPostRecordsService(serviceCollection.BuildServiceProvider(), Substitute.For<ILogger<TransformBlogPostRecordsService>>());
28+
sut = new TransformBlogPostRecordsService(
29+
Repository,
30+
userRecordRepository,
31+
blogPostRecordRepository,
32+
Substitute.For<ILogger<TransformBlogPostRecordsService>>());
3433
}
3534

3635
[Fact]
@@ -58,7 +57,7 @@ public async Task ShouldTransformRecords()
5857
await userRecordRepository.StoreBulkAsync(userRecords);
5958

6059
// Act
61-
await sut.StartAsync(default);
60+
await sut.RunAsync(new(null), default);
6261

6362
// Assert
6463
var afterUserRecords = await userRecordRepository.GetAllAsync();

0 commit comments

Comments
 (0)