-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
373 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
backend/tools/Faktur.Relocation.Worker/Commands/ExtractChangesCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Logitar.EventSourcing; | ||
using Logitar.EventSourcing.EntityFrameworkCore.Relational; | ||
using Logitar.EventSourcing.Infrastructure; | ||
using MediatR; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace Faktur.Relocation.Worker.Commands; | ||
|
||
internal record ExtractChangesCommand : IRequest<IReadOnlyCollection<DomainEvent>>; | ||
|
||
internal class ExtractChangesCommandHandler : IRequestHandler<ExtractChangesCommand, IReadOnlyCollection<DomainEvent>> | ||
{ | ||
private readonly IEventSerializer _eventSerializer; | ||
private readonly ILogger<ExtractChangesCommandHandler> _logger; | ||
private readonly SourceContext _source; | ||
|
||
public ExtractChangesCommandHandler(IEventSerializer eventSerializer, ILogger<ExtractChangesCommandHandler> logger, SourceContext source) | ||
{ | ||
_eventSerializer = eventSerializer; | ||
_logger = logger; | ||
_source = source; | ||
} | ||
|
||
public async Task<IReadOnlyCollection<DomainEvent>> Handle(ExtractChangesCommand _, CancellationToken cancellationToken) | ||
{ | ||
EventEntity[] events = await _source.Events.AsNoTracking().ToArrayAsync(cancellationToken); | ||
_logger.LogInformation("Extracted {EventCount} {EventText} from source database.", events.Length, events.Length > 1 ? "events" : "event"); | ||
|
||
return events.Select(_eventSerializer.Deserialize).ToArray().AsReadOnly(); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
backend/tools/Faktur.Relocation.Worker/Commands/LoadChangesCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
using Logitar.EventSourcing; | ||
using Logitar.EventSourcing.Infrastructure; | ||
using MediatR; | ||
|
||
namespace Faktur.Relocation.Worker.Commands; | ||
|
||
internal record LoadChangesCommand(IEnumerable<DomainEvent> Changes) : IRequest; | ||
|
||
internal class LoadChangesCommandHandler : IRequestHandler<LoadChangesCommand> | ||
{ | ||
private readonly IEventBus _bus; | ||
private readonly ILogger<LoadChangesCommandHandler> _logger; | ||
|
||
public LoadChangesCommandHandler(IEventBus bus, ILogger<LoadChangesCommandHandler> logger) | ||
{ | ||
_bus = bus; | ||
_logger = logger; | ||
} | ||
|
||
public async Task Handle(LoadChangesCommand command, CancellationToken cancellationToken) | ||
{ | ||
// TODO(fpion): upsert events | ||
|
||
int count = command.Changes.Count(); | ||
int index = 0; | ||
double percentage; | ||
foreach (DomainEvent change in command.Changes) | ||
{ | ||
await _bus.PublishAsync(change, cancellationToken); | ||
|
||
index++; | ||
percentage = index / (double)count; | ||
_logger.LogInformation( | ||
"[{Index}/{Count}] ({Percentage}) Handled '{EventType}'.", | ||
index, | ||
count, | ||
percentage.ToString("P2"), | ||
change.GetType()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. | ||
|
||
# This stage is used when running from VS in fast mode (Default for Debug configuration) | ||
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base | ||
USER app | ||
WORKDIR /app | ||
|
||
|
||
# This stage is used to build the service project | ||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build | ||
ARG BUILD_CONFIGURATION=Release | ||
WORKDIR /src | ||
COPY ["tools/Faktur.Relocation.Worker/Faktur.Relocation.Worker.csproj", "tools/Faktur.Relocation.Worker/"] | ||
RUN dotnet restore "./tools/Faktur.Relocation.Worker/Faktur.Relocation.Worker.csproj" | ||
COPY . . | ||
WORKDIR "/src/tools/Faktur.Relocation.Worker" | ||
RUN dotnet build "./Faktur.Relocation.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/build | ||
|
||
# This stage is used to publish the service project to be copied to the final stage | ||
FROM build AS publish | ||
ARG BUILD_CONFIGURATION=Release | ||
RUN dotnet publish "./Faktur.Relocation.Worker.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false | ||
|
||
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) | ||
FROM base AS final | ||
WORKDIR /app | ||
COPY --from=publish /app/publish . | ||
ENTRYPOINT ["dotnet", "Faktur.Relocation.Worker.dll"] |
43 changes: 43 additions & 0 deletions
43
backend/tools/Faktur.Relocation.Worker/Faktur.Relocation.Worker.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Worker"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<UserSecretsId>dotnet-Faktur.Relocation.Worker-134ea5cb-41d2-4c77-9686-2f758cb3cdec</UserSecretsId> | ||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> | ||
<DockerfileContext>..\..</DockerfileContext> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | ||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors> | ||
</PropertyGroup> | ||
|
||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | ||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<Content Remove="secrets.sample.json" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Include="secrets.sample.json" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Logitar" Version="6.3.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" /> | ||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\Faktur.EntityFrameworkCore.PostgreSQL\Faktur.EntityFrameworkCore.PostgreSQL.csproj" /> | ||
<ProjectReference Include="..\..\src\Faktur.EntityFrameworkCore.SqlServer\Faktur.EntityFrameworkCore.SqlServer.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Using Include="System.Diagnostics" /> | ||
<Using Include="System.Reflection" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
namespace Faktur.Relocation.Worker; | ||
|
||
internal class Program | ||
{ | ||
private static void Main(string[] args) | ||
{ | ||
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); | ||
|
||
Startup startup = new(builder.Configuration); | ||
startup.ConfigureServices(builder.Services); | ||
|
||
IHost host = builder.Build(); | ||
host.Run(); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
backend/tools/Faktur.Relocation.Worker/Properties/launchSettings.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"profiles": { | ||
"Faktur.Relocation.Worker": { | ||
"commandName": "Project", | ||
"environmentVariables": { | ||
"DOTNET_ENVIRONMENT": "Development" | ||
}, | ||
"dotnetRunMessages": true | ||
}, | ||
"Container (Dockerfile)": { | ||
"commandName": "Docker" | ||
} | ||
}, | ||
"$schema": "http://json.schemastore.org/launchsettings.json" | ||
} |
9 changes: 9 additions & 0 deletions
9
backend/tools/Faktur.Relocation.Worker/Settings/DatabaseSettings.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using Faktur.Infrastructure; | ||
|
||
namespace Faktur.Relocation.Worker.Settings; | ||
|
||
internal record DatabaseSettings | ||
{ | ||
public string ConnectionString { get; set; } = string.Empty; | ||
public DatabaseProvider DatabaseProvider { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Logitar.EventSourcing.EntityFrameworkCore.Relational; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace Faktur.Relocation.Worker; | ||
|
||
internal class SourceContext : DbContext | ||
{ | ||
public SourceContext(DbContextOptions<SourceContext> options) : base(options) | ||
{ | ||
} | ||
|
||
internal DbSet<EventEntity> Events { get; private set; } | ||
|
||
protected override void OnModelCreating(ModelBuilder modelBuilder) | ||
{ | ||
modelBuilder.ApplyConfiguration(new EventConfiguration()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using Faktur.EntityFrameworkCore.PostgreSQL; | ||
using Faktur.EntityFrameworkCore.SqlServer; | ||
using Faktur.Infrastructure; | ||
using Faktur.Relocation.Worker.Settings; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace Faktur.Relocation.Worker; | ||
|
||
internal class Startup | ||
{ | ||
private readonly IConfiguration _configuration; | ||
|
||
public Startup(IConfiguration configuration) | ||
{ | ||
_configuration = configuration; | ||
} | ||
|
||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddHostedService<Worker>(); | ||
services.AddMediatR(config => config.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); | ||
|
||
AddDestination(services); | ||
AddSource(services); | ||
} | ||
|
||
private void AddDestination(IServiceCollection services) | ||
{ | ||
DatabaseSettings settings = _configuration.GetSection("Destination").Get<DatabaseSettings>() ?? new(); | ||
switch (settings.DatabaseProvider) | ||
{ | ||
case DatabaseProvider.EntityFrameworkCorePostgreSQL: | ||
services.AddFakturWithEntityFrameworkCorePostgreSQL(settings.ConnectionString); | ||
break; | ||
case DatabaseProvider.EntityFrameworkCoreSqlServer: | ||
services.AddFakturWithEntityFrameworkCoreSqlServer(settings.ConnectionString); | ||
break; | ||
} | ||
} | ||
|
||
private void AddSource(IServiceCollection services) | ||
{ | ||
DatabaseSettings settings = _configuration.GetSection("Source").Get<DatabaseSettings>() ?? new(); | ||
switch (settings.DatabaseProvider) | ||
{ | ||
case DatabaseProvider.EntityFrameworkCorePostgreSQL: | ||
services.AddDbContext<SourceContext>(options => options.UseNpgsql(settings.ConnectionString)); | ||
break; | ||
case DatabaseProvider.EntityFrameworkCoreSqlServer: | ||
services.AddDbContext<SourceContext>(options => options.UseSqlServer(settings.ConnectionString)); | ||
break; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
using Faktur.Relocation.Worker.Commands; | ||
using Logitar.EventSourcing; | ||
using MediatR; | ||
|
||
namespace Faktur.Relocation.Worker; | ||
|
||
internal class Worker : BackgroundService | ||
{ | ||
private const string ActorIdKey = "ActorId"; | ||
private const string GenericErrorMessage = "An unhandled exception occurred."; | ||
|
||
private readonly IConfiguration _configuration; | ||
private readonly IHostApplicationLifetime _hostApplicationLifetime; | ||
private readonly ILogger<Worker> _logger; | ||
private readonly IServiceProvider _serviceProvider; | ||
|
||
private LogLevel _result = LogLevel.Information; // NOTE(fpion): "Information" means success. | ||
|
||
public Worker(IConfiguration configuration, IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger, IServiceProvider serviceProvider) | ||
{ | ||
_configuration = configuration; | ||
_hostApplicationLifetime = hostApplicationLifetime; | ||
_logger = logger; | ||
_serviceProvider = serviceProvider; | ||
} | ||
|
||
protected override async Task ExecuteAsync(CancellationToken cancellationToken) | ||
{ | ||
Stopwatch chrono = Stopwatch.StartNew(); | ||
_logger.LogInformation("Worker executing at {Timestamp}.", DateTimeOffset.Now); | ||
|
||
using IServiceScope scope = _serviceProvider.CreateScope(); | ||
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>(); | ||
|
||
try | ||
{ | ||
IReadOnlyCollection<DomainEvent> changes = await mediator.Send(new ExtractChangesCommand(), cancellationToken); | ||
|
||
ActorId actorId = GetActorId(); | ||
foreach (DomainEvent change in changes) | ||
{ | ||
change.ActorId = actorId; | ||
} | ||
|
||
await mediator.Send(new LoadChangesCommand(changes), cancellationToken); | ||
} | ||
catch (Exception exception) | ||
{ | ||
_logger.LogError(exception, GenericErrorMessage); | ||
_result = LogLevel.Error; | ||
|
||
Environment.ExitCode = exception.HResult; | ||
} | ||
finally | ||
{ | ||
chrono.Stop(); | ||
|
||
long seconds = chrono.ElapsedMilliseconds / 1000; | ||
string secondText = seconds <= 1 ? "second" : "seconds"; | ||
switch (_result) | ||
{ | ||
case LogLevel.Error: | ||
_logger.LogError("Worker failed after {Elapsed}ms ({Seconds} {SecondText}).", chrono.ElapsedMilliseconds, seconds, secondText); | ||
break; | ||
case LogLevel.Warning: | ||
_logger.LogWarning("Worker completed with warnings in {Elapsed}ms ({Seconds} {SecondText}).", chrono.ElapsedMilliseconds, seconds, secondText); | ||
break; | ||
default: | ||
_logger.LogInformation("Worker succeeded in {Elapsed}ms ({Seconds} {SecondText}).", chrono.ElapsedMilliseconds, seconds, secondText); | ||
break; | ||
} | ||
|
||
_hostApplicationLifetime.StopApplication(); | ||
} | ||
} | ||
private ActorId GetActorId() | ||
{ | ||
string? value = _configuration.GetValue<string>(ActorIdKey); | ||
return string.IsNullOrWhiteSpace(value) | ||
? throw new InvalidOperationException($"The configuration '{ActorIdKey}' is required.") | ||
: new(value); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
backend/tools/Faktur.Relocation.Worker/appsettings.Development.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"Logging": { | ||
"LogLevel": { | ||
"Default": "Information", | ||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning", | ||
"Microsoft.Hosting.Lifetime": "Information" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"Logging": { | ||
"LogLevel": { | ||
"Default": "Information", | ||
"Microsoft.Hosting.Lifetime": "Information" | ||
} | ||
} | ||
} |
Oops, something went wrong.