Skip to content

Commit

Permalink
feat: add IIdempotentCommandHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
jvandaal authored and ArneD committed Nov 29, 2023
1 parent 514831b commit dd9b7e2
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Be.Vlaanderen.Basisregisters.CommandHandling\Be.Vlaanderen.Basisregisters.CommandHandling.csproj" />
</ItemGroup>

<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Be.Vlaanderen.Basisregisters.CommandHandling.Idempotency
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public interface IIdempotentCommandHandler
{
Task<long> Dispatch(
Guid? commandId,
object command,
IDictionary<string, object> metadata,
CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Be.Vlaanderen.Basisregisters.CommandHandling.Idempotency
{
using System;
using System.Runtime.Serialization;

[Serializable]
public sealed class IdempotencyException : Exception
{
public IdempotencyException(string? message) : base(message)
{ }

private IdempotencyException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
namespace Be.Vlaanderen.Basisregisters.CommandHandling.Idempotency
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Utilities.HexByteConvertor;

public sealed class IdempotentCommandHandler : IIdempotentCommandHandler
{
private readonly ICommandHandlerResolver _bus;
private readonly IdempotencyContext _idempotencyContext;

public IdempotentCommandHandler(
ICommandHandlerResolver bus,
IdempotencyContext idempotencyContext)
{
_bus = bus;
_idempotencyContext = idempotencyContext;
}

public async Task<long> Dispatch(
Guid? commandId,
object command,
IDictionary<string, object> metadata,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(commandId);
ArgumentNullException.ThrowIfNull(command);

// First check if the command id already has been processed
var possibleProcessedCommand = await _idempotencyContext
.ProcessedCommands
.Where(x => x.CommandId == commandId)
.ToDictionaryAsync(x => x.CommandContentHash, x => x, cancellationToken);

var contentHash = SHA512
.Create()
.ComputeHash(Encoding.UTF8.GetBytes(command.ToString()))
.ToHexString();

// It is possible we have a GUID collision, check the SHA-512 hash as well to see if it is really the same one.
// Do nothing if commandId with contenthash exists
if (possibleProcessedCommand.Any() && possibleProcessedCommand.ContainsKey(contentHash))
{
throw new IdempotencyException("Already processed");
}

var processedCommand = new ProcessedCommand(commandId.Value, contentHash);
try
{
// Store commandId in Command Store if it does not exist
await _idempotencyContext.ProcessedCommands.AddAsync(processedCommand, cancellationToken);
await _idempotencyContext.SaveChangesAsync(cancellationToken);

// Do work
return await _bus.Dispatch(commandId.Value,
command,
metadata,
cancellationToken);
}
catch
{
// On exception, remove commandId from Command Store
_idempotencyContext.ProcessedCommands.Remove(processedCommand);
await _idempotencyContext.SaveChangesAsync(cancellationToken);
throw;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Microsoft.EntityFrameworkCore.SqlServer
Newtonsoft.Json

Be.Vlaanderen.Basisregisters.Converters.Timestamp
Be.Vlaanderen.Basisregisters.Utilities.HexByteConvertor

SourceLink.Embed.AllSourceFiles
SourceLink.Copy.PdbFiles
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ description Lightweight infrastructure for doing command handling and eventsourc

dependencies
framework: net6.0
Be.Vlaanderen.Basisregisters.CommandHandling CURRENTVERSION

Be.Vlaanderen.Basisregisters.Converters.Timestamp >= LOCKEDVERSION
Be.Vlaanderen.Basisregisters.Utilities.HexByteConvertor >= LOCKEDVERSION

Microsoft.Data.SqlClient >= LOCKEDVERSION
Microsoft.Extensions.Configuration >= LOCKEDVERSION
Expand Down

0 comments on commit dd9b7e2

Please sign in to comment.