using Dapper; using KArtSell.BuildingBlocks.Data; namespace KArtSell.BuildingBlocks.Reliability; public sealed class DapperOutboxMessageReader(IDbConnectionFactory connectionFactory) { private const string SelectUnpublishedSql = """ select message_id as MessageId, event_type as EventType, schema_version as SchemaVersion, payload_json::text as PayloadJson, correlation_id as CorrelationId, occurred_at as OccurredAt, payload_hash as PayloadHash, attempt as Attempt from building_blocks.outbox_message where published_at is null order by occurred_at asc limit @BatchSize """; private const string MarkPublishedSql = """ update building_blocks.outbox_message set published_at = @PublishedAt, attempt = attempt + 1 where message_id = @MessageId """; private const string InsertInboxSql = """ insert into building_blocks.inbox_message (consumer, message_id, received_at, payload_hash) values (@Consumer, @MessageId, @ReceivedAt, @PayloadHash) on conflict (consumer, message_id) do nothing """; public async Task> GetUnpublishedAsync( int batchSize, CancellationToken cancellationToken) { await using var connection = await connectionFactory.OpenAsync(cancellationToken); var messages = await connection.QueryAsync( new CommandDefinition( SelectUnpublishedSql, new { BatchSize = batchSize }, cancellationToken: cancellationToken)); return messages.ToList(); } public async Task MarkPublishedAsync( Guid messageId, DateTimeOffset publishedAt, CancellationToken cancellationToken) { await using var connection = await connectionFactory.OpenAsync(cancellationToken); await connection.ExecuteAsync( new CommandDefinition( MarkPublishedSql, new { MessageId = messageId, PublishedAt = publishedAt }, cancellationToken: cancellationToken)); } /// /// Insert inbox record to mark message as published/delivery-ready. /// Consumer parameter identifies the delivery mechanism (e.g., 'outbox-poller' = ready marker). /// Actual downstream consumers poll inbox_message to retrieve and deliver to end recipients. /// public async Task InsertInboxAsync( string consumer, Guid messageId, DateTimeOffset receivedAt, string payloadHash, CancellationToken cancellationToken) { await using var connection = await connectionFactory.OpenAsync(cancellationToken); await connection.ExecuteAsync( new CommandDefinition( InsertInboxSql, new { Consumer = consumer, MessageId = messageId, ReceivedAt = receivedAt, PayloadHash = payloadHash }, cancellationToken: cancellationToken)); } public sealed record OutboxMessageRow( Guid MessageId, string EventType, int SchemaVersion, string PayloadJson, string CorrelationId, DateTime OccurredAt, string PayloadHash, int Attempt); }