WIP
This commit is contained in:
parent
54b057886c
commit
7fc1ae0650
204 changed files with 4345 additions and 134 deletions
13
PlanTempus.Core/Outbox/IOutboxService.cs
Normal file
13
PlanTempus.Core/Outbox/IOutboxService.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Data;
|
||||
|
||||
namespace PlanTempus.Core.Outbox;
|
||||
|
||||
public interface IOutboxService
|
||||
{
|
||||
Task EnqueueAsync(string type, object payload, IDbConnection? connection = null, IDbTransaction? transaction = null);
|
||||
Task<List<OutboxMessage>> GetPendingAsync(int batchSize = 10);
|
||||
Task MarkAsSentAsync(Guid id);
|
||||
Task MarkAsFailedAsync(Guid id, string errorMessage);
|
||||
}
|
||||
27
PlanTempus.Core/Outbox/OutboxMessage.cs
Normal file
27
PlanTempus.Core/Outbox/OutboxMessage.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#nullable enable
|
||||
|
||||
namespace PlanTempus.Core.Outbox;
|
||||
|
||||
public class OutboxMessage
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public required string Type { get; set; }
|
||||
public required object Payload { get; set; }
|
||||
public string Status { get; set; } = "pending";
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ProcessedAt { get; set; }
|
||||
public int RetryCount { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
public static class OutboxMessageTypes
|
||||
{
|
||||
public const string VerificationEmail = "verification_email";
|
||||
}
|
||||
|
||||
public class VerificationEmailPayload
|
||||
{
|
||||
public required string Email { get; set; }
|
||||
public required string UserName { get; set; }
|
||||
public required string Token { get; set; }
|
||||
}
|
||||
11
PlanTempus.Core/Outbox/OutboxModule.cs
Normal file
11
PlanTempus.Core/Outbox/OutboxModule.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Autofac;
|
||||
|
||||
namespace PlanTempus.Core.Outbox;
|
||||
|
||||
public class OutboxModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterType<OutboxService>().As<IOutboxService>().InstancePerLifetimeScope();
|
||||
}
|
||||
}
|
||||
104
PlanTempus.Core/Outbox/OutboxService.cs
Normal file
104
PlanTempus.Core/Outbox/OutboxService.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#nullable enable
|
||||
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using Insight.Database;
|
||||
using PlanTempus.Core.Database;
|
||||
|
||||
namespace PlanTempus.Core.Outbox;
|
||||
|
||||
public class OutboxService(IDatabaseOperations databaseOperations) : IOutboxService
|
||||
{
|
||||
public async Task EnqueueAsync(string type, object payload, IDbConnection? connection = null, IDbTransaction? transaction = null)
|
||||
{
|
||||
var sql = @"
|
||||
INSERT INTO system.outbox (type, payload)
|
||||
VALUES (@Type, @Payload::jsonb)";
|
||||
|
||||
var parameters = new
|
||||
{
|
||||
Type = type,
|
||||
Payload = JsonSerializer.Serialize(payload)
|
||||
};
|
||||
|
||||
if (connection != null)
|
||||
{
|
||||
await connection.ExecuteSqlAsync(sql, parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var db = databaseOperations.CreateScope(nameof(OutboxService));
|
||||
await db.Connection.ExecuteSqlAsync(sql, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<OutboxMessage>> GetPendingAsync(int batchSize = 10)
|
||||
{
|
||||
using var db = databaseOperations.CreateScope(nameof(OutboxService));
|
||||
|
||||
var sql = @"
|
||||
UPDATE system.outbox
|
||||
SET status = 'processing'
|
||||
WHERE id IN (
|
||||
SELECT id FROM system.outbox
|
||||
WHERE status = 'pending'
|
||||
ORDER BY created_at
|
||||
LIMIT @BatchSize
|
||||
FOR UPDATE SKIP LOCKED
|
||||
)
|
||||
RETURNING id, type, payload, status, created_at, processed_at, retry_count, error_message";
|
||||
|
||||
var results = await db.Connection.QuerySqlAsync<OutboxMessageDto>(sql, new { BatchSize = batchSize });
|
||||
|
||||
return results.Select(r => new OutboxMessage
|
||||
{
|
||||
Id = r.Id,
|
||||
Type = r.Type,
|
||||
Payload = JsonSerializer.Deserialize<JsonElement>(r.Payload),
|
||||
Status = r.Status,
|
||||
CreatedAt = r.CreatedAt,
|
||||
ProcessedAt = r.ProcessedAt,
|
||||
RetryCount = r.RetryCount,
|
||||
ErrorMessage = r.ErrorMessage
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task MarkAsSentAsync(Guid id)
|
||||
{
|
||||
using var db = databaseOperations.CreateScope(nameof(OutboxService));
|
||||
|
||||
var sql = @"
|
||||
UPDATE system.outbox
|
||||
SET status = 'sent', processed_at = NOW()
|
||||
WHERE id = @Id";
|
||||
|
||||
await db.Connection.ExecuteSqlAsync(sql, new { Id = id });
|
||||
}
|
||||
|
||||
public async Task MarkAsFailedAsync(Guid id, string errorMessage)
|
||||
{
|
||||
using var db = databaseOperations.CreateScope(nameof(OutboxService));
|
||||
|
||||
var sql = @"
|
||||
UPDATE system.outbox
|
||||
SET status = 'failed',
|
||||
processed_at = NOW(),
|
||||
retry_count = retry_count + 1,
|
||||
error_message = @ErrorMessage
|
||||
WHERE id = @Id";
|
||||
|
||||
await db.Connection.ExecuteSqlAsync(sql, new { Id = id, ErrorMessage = errorMessage });
|
||||
}
|
||||
|
||||
private class OutboxMessageDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Type { get; set; } = "";
|
||||
public string Payload { get; set; } = "";
|
||||
public string Status { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ProcessedAt { get; set; }
|
||||
public int RetryCount { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue