[FA-4] Adds an event bus infrastructure, a RabbitMQ implementation and rewires existing mutations on NovelService to utilize it.

This commit is contained in:
gamer147
2025-11-19 21:45:33 -05:00
parent 716087e4a4
commit e9423bfa66
42 changed files with 1037 additions and 263 deletions

View File

@@ -1,6 +1,8 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.TranslationService.Models;
using FictionArchive.Service.TranslationService.Models.Database;
using FictionArchive.Service.TranslationService.Models.Enums;
using FictionArchive.Service.TranslationService.Services;
using FictionArchive.Service.TranslationService.Services.Database;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
@@ -8,23 +10,10 @@ namespace FictionArchive.Service.TranslationService.GraphQL;
public class Mutation
{
public async Task<string> TranslateText(string text, Language from, Language to, string translationEngineKey, IEnumerable<ITranslationEngine> translationEngines, TranslationServiceDbContext dbContext)
public async Task<TranslationResult> TranslateText(string text, Language from, Language to, string translationEngineKey, TranslationEngineService translationEngineService)
{
var engine = translationEngines.FirstOrDefault(engine => engine.Descriptor.Key == translationEngineKey);
var translation = await engine.GetTranslation(text, from, to);
dbContext.TranslationRequests.Add(new TranslationRequest()
{
OriginalText = text,
BilledCharacterCount = 0, // FILL ME
From = from,
To = to,
Status = translation != null ? TranslationRequestStatus.Success : TranslationRequestStatus.Failed,
TranslatedText = translation,
TranslationEngineKey = translationEngineKey
});
await dbContext.SaveChangesAsync();
var result = await translationEngineService.Translate(from, to, text, translationEngineKey);
return translation;
return result;
}
}

View File

@@ -0,0 +1,18 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.Shared.Services.EventBus;
using FictionArchive.Service.TranslationService.Models.Enums;
namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents;
public class TranslationRequestCompletedEvent : IntegrationEvent
{
/// <summary>
/// Maps this event back to a triggering request.
/// </summary>
public Guid? TranslationRequestId { get; set; }
/// <summary>
/// The resulting text.
/// </summary>
public string? TranslatedText { get; set; }
}

View File

@@ -0,0 +1,13 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.Shared.Services.EventBus;
namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents;
public class TranslationRequestCreatedEvent : IntegrationEvent
{
public Guid TranslationRequestId { get; set; }
public Language From { get; set; }
public Language To { get; set; }
public string Body { get; set; }
public string TranslationEngineKey { get; set; }
}

View File

@@ -0,0 +1,15 @@
using FictionArchive.Common.Enums;
using FictionArchive.Service.TranslationService.Models.Enums;
namespace FictionArchive.Service.TranslationService.Models;
public class TranslationResult
{
public required string OriginalText { get; set; }
public string? TranslatedText { get; set; }
public Language From { get; set; }
public Language To { get; set; }
public required string TranslationEngineKey { get; set; }
public TranslationRequestStatus Status { get; set; }
public uint BilledCharacterCount { get; set; }
}

View File

@@ -1,11 +1,16 @@
using DeepL;
using FictionArchive.Common.Extensions;
using FictionArchive.Service.Shared.Extensions;
using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using FictionArchive.Service.Shared.Services.GraphQL;
using FictionArchive.Service.TranslationService.GraphQL;
using FictionArchive.Service.TranslationService.Models.IntegrationEvents;
using FictionArchive.Service.TranslationService.Services;
using FictionArchive.Service.TranslationService.Services.Database;
using FictionArchive.Service.TranslationService.Services.EventHandlers;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
using FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
using RabbitMQ.Client;
namespace FictionArchive.Service.TranslationService;
@@ -18,6 +23,17 @@ public class Program
builder.Services.AddHealthChecks();
#region Event Bus
builder.Services.AddRabbitMQ(opt =>
{
builder.Configuration.GetSection("RabbitMQ").Bind(opt);
})
.Subscribe<TranslationRequestCreatedEvent, TranslationRequestCreatedEventHandler>();
#endregion
#region Database
builder.Services.RegisterDbContext<TranslationServiceDbContext>(builder.Configuration.GetConnectionString("DefaultConnection"));
@@ -37,6 +53,8 @@ public class Program
return new DeepLClient(builder.Configuration["DeepL:ApiKey"]);
});
builder.Services.AddTransient<ITranslationEngine, DeepLTranslationEngine>();
builder.Services.AddTransient<TranslationEngineService>();
#endregion

View File

@@ -0,0 +1,31 @@
using FictionArchive.Service.Shared.Services.EventBus;
using FictionArchive.Service.TranslationService.Models.Enums;
using FictionArchive.Service.TranslationService.Models.IntegrationEvents;
namespace FictionArchive.Service.TranslationService.Services.EventHandlers;
public class TranslationRequestCreatedEventHandler : IIntegrationEventHandler<TranslationRequestCreatedEvent>
{
private readonly ILogger<TranslationRequestCreatedEventHandler> _logger;
private readonly TranslationEngineService _translationEngineService;
private readonly IEventBus _eventBus;
public TranslationRequestCreatedEventHandler(ILogger<TranslationRequestCreatedEventHandler> logger, TranslationEngineService translationEngineService)
{
_logger = logger;
_translationEngineService = translationEngineService;
}
public async Task Handle(TranslationRequestCreatedEvent @event)
{
var result = await _translationEngineService.Translate(@event.From, @event.To, @event.Body, @event.TranslationEngineKey);
if (result.Status == TranslationRequestStatus.Success)
{
await _eventBus.Publish(new TranslationRequestCompletedEvent()
{
TranslatedText = result.TranslatedText,
TranslationRequestId = @event.TranslationRequestId,
});
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Text;
using FictionArchive.Common.Enums;
using FictionArchive.Service.Shared.Services.EventBus;
using FictionArchive.Service.Shared.Services.EventBus.Implementations;
using FictionArchive.Service.TranslationService.Models;
using FictionArchive.Service.TranslationService.Models.Database;
using FictionArchive.Service.TranslationService.Models.Enums;
using FictionArchive.Service.TranslationService.Models.IntegrationEvents;
using FictionArchive.Service.TranslationService.Services.Database;
using FictionArchive.Service.TranslationService.Services.TranslationEngines;
using RabbitMQ.Client;
namespace FictionArchive.Service.TranslationService.Services;
public class TranslationEngineService
{
private readonly IEnumerable<ITranslationEngine> _translationEngines;
private readonly IEventBus _eventBus;
private readonly TranslationServiceDbContext _dbContext;
public TranslationEngineService(IEnumerable<ITranslationEngine> translationEngines, TranslationServiceDbContext dbContext, IEventBus eventBus)
{
_translationEngines = translationEngines;
_dbContext = dbContext;
_eventBus = eventBus;
}
public async Task<TranslationResult> Translate(Language from, Language to, string text, string translationEngineKey)
{
var engine = _translationEngines.FirstOrDefault(engine => engine.Descriptor.Key == translationEngineKey);
var translation = await engine.GetTranslation(text, from, to);
_dbContext.TranslationRequests.Add(new TranslationRequest()
{
OriginalText = text,
BilledCharacterCount = translation.BilledCharacterCount, // FILL ME
From = from,
To = to,
Status = translation != null ? TranslationRequestStatus.Success : TranslationRequestStatus.Failed,
TranslatedText = translation.TranslatedText,
TranslationEngineKey = translationEngineKey
});
await _dbContext.SaveChangesAsync();
return translation;
}
}

View File

@@ -1,6 +1,7 @@
using DeepL;
using DeepL.Model;
using FictionArchive.Service.TranslationService.Models;
using FictionArchive.Service.TranslationService.Models.Enums;
using Language = FictionArchive.Common.Enums.Language;
namespace FictionArchive.Service.TranslationService.Services.TranslationEngines.DeepLTranslate;
@@ -31,11 +32,20 @@ public class DeepLTranslationEngine : ITranslationEngine
}
}
public async Task<string?> GetTranslation(string body, Language from, Language to)
public async Task<TranslationResult> GetTranslation(string body, Language from, Language to)
{
TextResult translationResult = await _deepLClient.TranslateTextAsync(body, GetLanguageCode(from), GetLanguageCode(to));
_logger.LogInformation("Translated text. Usage statistics: CHARACTERS BILLED {TranslationResultBilledCharacters}", translationResult.BilledCharacters);
return translationResult.Text;
return new TranslationResult()
{
OriginalText = body,
From = from,
To = to,
TranslationEngineKey = Key,
BilledCharacterCount = (uint)translationResult.BilledCharacters,
Status = TranslationRequestStatus.Success,
TranslatedText = translationResult.Text
};
}
private string GetLanguageCode(Language language)

View File

@@ -6,5 +6,5 @@ namespace FictionArchive.Service.TranslationService.Services.TranslationEngines;
public interface ITranslationEngine
{
public TranslationEngineDescriptor Descriptor { get; }
public Task<string?> GetTranslation(string body, Language from, Language to);
public Task<TranslationResult> GetTranslation(string body, Language from, Language to);
}

View File

@@ -11,5 +11,9 @@
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=FictionArchive_NovelService;Username=postgres;password=postgres"
},
"RabbitMQ": {
"ConnectionString": "amqp://localhost",
"ClientIdentifier": "TranslationService"
},
"AllowedHosts": "*"
}