From 9ddbf4e3969044975245ddecbcb80c548bace092 Mon Sep 17 00:00:00 2001 From: adimiko Date: Wed, 12 Mar 2025 07:33:51 +0100 Subject: [PATCH] Changed implementation ConcurrencyConflictValidator --- .../Commands/SampleCommandHandler.cs | 4 +- .../EasyWay/ConcurrencyConflictValidator.cs | 43 +++++++++ .../EasyWay/IConcurrencyConflictValidator.cs | 8 -- ...CommandWihtoutConcurrencyTokenException.cs | 8 ++ .../Commands/ConcurrencyConflictValidator.cs | 18 ---- .../EasyWay/Internals/Commands/Extensions.cs | 2 +- ...ncurrencyConflictValidatorCommandTests.cs} | 5 +- ...ConflictValidatorCommandWithResultTests.cs | 87 +++++++++++++++++++ 8 files changed, 143 insertions(+), 32 deletions(-) create mode 100644 source/EasyWay/ConcurrencyConflictValidator.cs delete mode 100644 source/EasyWay/IConcurrencyConflictValidator.cs create mode 100644 source/EasyWay/Internals/Commands/ConcurrencyConflict/CommandWihtoutConcurrencyTokenException.cs delete mode 100644 source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs rename tests/EasyWay.Tests/Internals/Commands/{ConcurrencyConflictValidatorTests.cs => ConcurrencyConflictValidatorCommandTests.cs} (90%) create mode 100644 tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandWithResultTests.cs diff --git a/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs b/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs index 91e0b7a..860bc95 100644 --- a/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs +++ b/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs @@ -13,7 +13,7 @@ internal sealed class SampleCommandHandler : CommandHandler private readonly SampleDomainService _domainService; - private readonly IConcurrencyConflictValidator _concurrencyTokenValidator; + private readonly ConcurrencyConflictValidator _concurrencyTokenValidator; private readonly IEnumerable _policies; @@ -24,7 +24,7 @@ public SampleCommandHandler( ISampleAggragateRootRepository repository, SampleAggregateRootFactory factory, SampleDomainService domainService, - IConcurrencyConflictValidator concurrencyTokenValidator, + ConcurrencyConflictValidator concurrencyTokenValidator, IEnumerable policies, IUserContext userContext) { diff --git a/source/EasyWay/ConcurrencyConflictValidator.cs b/source/EasyWay/ConcurrencyConflictValidator.cs new file mode 100644 index 0000000..8d613c6 --- /dev/null +++ b/source/EasyWay/ConcurrencyConflictValidator.cs @@ -0,0 +1,43 @@ +using EasyWay.Internals; +using EasyWay.Internals.Commands.ConcurrencyConflict; + +namespace EasyWay +{ + public sealed class ConcurrencyConflictValidator + { + internal ConcurrencyConflictValidator() { } + + public void Validate(AggregateRoot aggregateRoot, TCommand command) + where TCommand : Command, IWithConcurrencyToken + { + if (aggregateRoot.ConcurrencyToken == command.ConcurrencyToken) + { + return; + } + + var message = $"{aggregateRoot.GetType().Name} with id {aggregateRoot.Id} has different concurrency token ({aggregateRoot.ConcurrencyToken}) that command ({command.ConcurrencyToken})"; + + throw new ConcurrencyException(message); + } + + public void Validate(AggregateRoot aggregateRoot, Command command) + where T : OperationResult + { + var commandWithConcurrencyToken = command as IWithConcurrencyToken; + + if (commandWithConcurrencyToken is null) + { + throw new CommandWihtoutConcurrencyTokenException(command.GetType()); + } + + if (aggregateRoot.ConcurrencyToken == commandWithConcurrencyToken.ConcurrencyToken) + { + return; + } + + var message = $"{aggregateRoot.GetType().Name} with id {aggregateRoot.Id} has different concurrency token ({aggregateRoot.ConcurrencyToken}) that command ({commandWithConcurrencyToken.ConcurrencyToken})"; + + throw new ConcurrencyException(message); + } + } +} diff --git a/source/EasyWay/IConcurrencyConflictValidator.cs b/source/EasyWay/IConcurrencyConflictValidator.cs deleted file mode 100644 index 4f12e41..0000000 --- a/source/EasyWay/IConcurrencyConflictValidator.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace EasyWay -{ - public interface IConcurrencyConflictValidator - { - void Validate(AggregateRoot aggregateRoot, TCommand command) - where TCommand : Command, IWithConcurrencyToken; - } -} diff --git a/source/EasyWay/Internals/Commands/ConcurrencyConflict/CommandWihtoutConcurrencyTokenException.cs b/source/EasyWay/Internals/Commands/ConcurrencyConflict/CommandWihtoutConcurrencyTokenException.cs new file mode 100644 index 0000000..53b6b44 --- /dev/null +++ b/source/EasyWay/Internals/Commands/ConcurrencyConflict/CommandWihtoutConcurrencyTokenException.cs @@ -0,0 +1,8 @@ +namespace EasyWay.Internals.Commands.ConcurrencyConflict +{ + internal sealed class CommandWihtoutConcurrencyTokenException : EasyWayException + { + internal CommandWihtoutConcurrencyTokenException(Type commandType) + : base($"Add {nameof(IWithConcurrencyToken)} interface to {commandType.Name}"){ } + } +} diff --git a/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs b/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs deleted file mode 100644 index 1c3eb6c..0000000 --- a/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace EasyWay.Internals.Commands -{ - internal sealed class ConcurrencyConflictValidator : IConcurrencyConflictValidator - { - public void Validate(AggregateRoot aggregateRoot, TCommand command) - where TCommand : Command, IWithConcurrencyToken - { - if (aggregateRoot.ConcurrencyToken == command.ConcurrencyToken) - { - return; - } - - var message = $"{aggregateRoot.GetType().Name} with id {aggregateRoot.Id} has different concurrency token ({aggregateRoot.ConcurrencyToken}) that command ({command.ConcurrencyToken})"; - - throw new ConcurrencyException(message); - } - } -} diff --git a/source/EasyWay/Internals/Commands/Extensions.cs b/source/EasyWay/Internals/Commands/Extensions.cs index c0ec9d1..d64d080 100644 --- a/source/EasyWay/Internals/Commands/Extensions.cs +++ b/source/EasyWay/Internals/Commands/Extensions.cs @@ -19,7 +19,7 @@ internal static IServiceCollection AddCommands( services.AddScoped(); - services.AddSingleton(); + services.AddSingleton(new ConcurrencyConflictValidator()); return services; } diff --git a/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs b/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandTests.cs similarity index 90% rename from tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs rename to tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandTests.cs index 9a48d45..3d120b7 100644 --- a/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs +++ b/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandTests.cs @@ -1,12 +1,11 @@ using EasyWay.Internals; -using EasyWay.Internals.Commands; using EasyWay.Internals.GuidGenerators; namespace EasyWay.Tests.Internals.Commands { - public sealed class ConcurrencyConflictValidatorTests + public sealed class ConcurrencyConflictValidatorCommandTests { - private readonly IConcurrencyConflictValidator _validator = new ConcurrencyConflictValidator(); + private readonly ConcurrencyConflictValidator _validator = new ConcurrencyConflictValidator(); internal sealed class TestAggragate : AggregateRoot; diff --git a/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandWithResultTests.cs b/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandWithResultTests.cs new file mode 100644 index 0000000..98b74de --- /dev/null +++ b/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorCommandWithResultTests.cs @@ -0,0 +1,87 @@ +using EasyWay.Internals.GuidGenerators; +using EasyWay.Internals; +using EasyWay.Internals.Commands.ConcurrencyConflict; + +namespace EasyWay.Tests.Internals.Commands +{ + public sealed class ConcurrencyConflictValidatorCommandWithResultTests + { + private readonly ConcurrencyConflictValidator _validator = new ConcurrencyConflictValidator(); + + internal sealed class TestAggragate : AggregateRoot; + + internal sealed class CommandResult : OperationResult; + + internal sealed class TestClassWithResult : Command, IWithConcurrencyToken + { + public short ConcurrencyToken { get; } + + public TestClassWithResult(short concurrencyToken) + { + ConcurrencyToken = concurrencyToken; + } + } + + internal sealed class TestClassWithoutConcurrencyToken : Command; + + [Fact(DisplayName = $"When concurrency tokens are different validator should throw {nameof(ConcurrencyException)}")] + public void WhenConcurrencyTokensAreDifferent() + { + // Arrange + var id = new Guid("56bc4845-b8c4-4c33-a20d-67e14a486f90"); + + GuidGenerator.Set(id); + + var aggregateRoot = new TestAggragate(); + + var commandWithToken = new TestClassWithResult(128); + + var expectedMessage = $"{typeof(TestAggragate).Name} with id {id} has different concurrency token ({aggregateRoot.ConcurrencyToken}) that command ({commandWithToken.ConcurrencyToken})"; + + // Assert + var ex = Assert.Throws(() => + { + // Act + _validator.Validate(aggregateRoot, commandWithToken); + }); + + Assert.Equal(expectedMessage, ex.Message); + + GuidGenerator.Reset(); + } + + [Fact(DisplayName = $"When concurrency tokens are the same validator should not throw exception")] + public void WhenConcurrencyTokensAreTheSame() + { + // Arrange + var aggregateRoot = new TestAggragate(); + + var commandWithToken = new TestClassWithResult(0); + + // Act & Assert + _validator.Validate(aggregateRoot, commandWithToken); + } + + [Fact(DisplayName = $"When command with result don't implement {nameof(IWithConcurrencyToken)} interface")] + public void WhenCommandIsWithoutConcurrencyToken() + { + // Arrange + var aggregateRoot = new TestAggragate(); + + var commandWithToken = new TestClassWithoutConcurrencyToken(); + + // Act & Assert + + // Assert + var ex = Assert.Throws(() => + { + // Act + _validator.Validate(aggregateRoot, commandWithToken); + }); + + string expectedMessage = $"Add {nameof(IWithConcurrencyToken)} interface to {nameof(TestClassWithoutConcurrencyToken)}"; + + Assert.Equal(expectedMessage, ex.Message); + } + } +}