Skip to content

Commit

Permalink
refactor: Use NCronJob for background services
Browse files Browse the repository at this point in the history
  • Loading branch information
linkdotnet committed Mar 22, 2024
1 parent 55cd446 commit f747c1e
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 64 deletions.
31 changes: 9 additions & 22 deletions src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,36 @@
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<BlogPostPublisher> logger;
private readonly IRepository<BlogPost> repository;
private readonly ICacheInvalidator cacheInvalidator;

public BlogPostPublisher(IServiceProvider serviceProvider, ICacheInvalidator cacheInvalidator, ILogger<BlogPostPublisher> logger)
public BlogPostPublisher(IRepository<BlogPost> repository, ICacheInvalidator cacheInvalidator, ILogger<BlogPostPublisher> 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();
}

private async Task PublishScheduledBlogPostsAsync()
{
LogCheckingForScheduledBlogPosts();

using var scope = serviceProvider.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<IRepository<BlogPost>>();

var blogPostsToPublish = await GetScheduledBlogPostsAsync(repository);
var blogPostsToPublish = await GetScheduledBlogPostsAsync();
foreach (var blogPost in blogPostsToPublish)
{
blogPost.Publish();
Expand All @@ -61,7 +48,7 @@ private async Task PublishScheduledBlogPostsAsync()
}
}

private async Task<IPagedList<BlogPost>> GetScheduledBlogPostsAsync(IRepository<BlogPost> repository)
private async Task<IPagedList<BlogPost>> GetScheduledBlogPostsAsync()
{
var now = DateTime.UtcNow;
var scheduledBlogPosts = await repository.GetAllAsync(
Expand Down
36 changes: 15 additions & 21 deletions src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlogPost> blogPostRepository;
private readonly IRepository<UserRecord> userRecordRepository;
private readonly IRepository<BlogPostRecord> blogPostRecordRepository;
private readonly ILogger<TransformBlogPostRecordsService> logger;

public TransformBlogPostRecordsService(IServiceProvider services, ILogger<TransformBlogPostRecordsService> logger)
public TransformBlogPostRecordsService(
IRepository<BlogPost> blogPostRepository,
IRepository<UserRecord> userRecordRepository,
IRepository<BlogPostRecord> blogPostRecordRepository,
ILogger<TransformBlogPostRecordsService> 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();
}

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

private async Task TransformRecordsAsync()
{
using var scope = services.CreateScope();
var blogPostRepository = scope.ServiceProvider.GetRequiredService<IRepository<BlogPost>>();
var userRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<UserRecord>>();
var blogPostRecordRepository = scope.ServiceProvider.GetRequiredService<IRepository<BlogPostRecord>>();

var blogPosts = await blogPostRepository.GetAllAsync();
var userRecords = await userRecordRepository.GetAllAsync(
filter: r => r.UrlClicked.StartsWith("blogPost/"));
Expand Down
1 change: 1 addition & 0 deletions src/LinkDotNet.Blog.Web/LinkDotNet.Blog.Web.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
<PackageReference Include="Blazored.Toast" Version="4.2.1" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" PrivateAssets="all" />
<PackageReference Include="LinkDotNet.NCronJob" Version="0.12.0" />
<PackageReference Include="Markdig" Version="0.36.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LinkDotNet.Blog.Web.Features;
using LinkDotNet.NCronJob;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

Expand All @@ -13,7 +14,9 @@ public static void AddBackgroundServices(this IServiceCollection services)
options.ServicesStartConcurrently = true;
options.ServicesStopConcurrently = true;
});
services.AddHostedService<BlogPostPublisher>();
services.AddHostedService<TransformBlogPostRecordsService>();

services.AddNCronJob();
services.AddCronJob<BlogPostPublisher>(p => p.CronExpression = "* * * * *");
services.AddCronJob<TransformBlogPostRecordsService>(p => p.CronExpression = "0 * * * *");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlogPost>, IDisposable
public sealed class BlogPostPublisherTests : SqlDatabaseTestBase<BlogPost>
{
private readonly BlogPostPublisher sut;
private readonly ICacheInvalidator cacheInvalidator;

public BlogPostPublisherTests()
{
var serviceProvider = new ServiceCollection()
.AddScoped(_ => Repository)
.BuildServiceProvider();

cacheInvalidator = Substitute.For<ICacheInvalidator>();

sut = new BlogPostPublisher(serviceProvider, cacheInvalidator, Substitute.For<ILogger<BlogPostPublisher>>());
sut = new BlogPostPublisher(Repository, cacheInvalidator, Substitute.For<ILogger<BlogPostPublisher>>());
}

[Fact]
Expand All @@ -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();
Expand All @@ -51,18 +46,16 @@ 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();
}

[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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@ public TransformBlogPostRecordsServiceTests()
new Repository<BlogPostRecord>(DbContextFactory, Substitute.For<ILogger<Repository<BlogPostRecord>>>());
userRecordRepository =
new Repository<UserRecord>(DbContextFactory, Substitute.For<ILogger<Repository<UserRecord>>>());

var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped(_ => Repository);
serviceCollection.AddScoped(_ => blogPostRecordRepository);
serviceCollection.AddScoped(_ => userRecordRepository);

sut = new TransformBlogPostRecordsService(serviceCollection.BuildServiceProvider(), Substitute.For<ILogger<TransformBlogPostRecordsService>>());
sut = new TransformBlogPostRecordsService(
Repository,
userRecordRepository,
blogPostRecordRepository,
Substitute.For<ILogger<TransformBlogPostRecordsService>>());
}

[Fact]
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit f747c1e

Please sign in to comment.