Skip to content

Commit 322ccf5

Browse files
breaking-change: refactor way version number is handled
pass the version of the command to the transaction, not the expected previous version
1 parent fb6ee86 commit 322ccf5

File tree

11 files changed

+119
-40
lines changed

11 files changed

+119
-40
lines changed

src/EntityDb.Abstractions/Transactions/ITransactionCommand.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public interface ITransactionCommand<TEntity>
1414
/// <summary>
1515
/// A snapshot of the entity after the command.
1616
/// </summary>
17-
TEntity NextSnapshot { get; }
17+
TEntity EntitySnapshot { get; }
1818

1919
/// <summary>
2020
/// The id of the entity.
@@ -25,9 +25,9 @@ public interface ITransactionCommand<TEntity>
2525
/// The previous version number of the entity.
2626
/// </summary>
2727
/// <remarks>
28-
/// The repository must use a VersionNumber equal to <see cref="ExpectedPreviousVersionNumber" /> + 1.
28+
/// The repository must expect the last command committed to have VersionNumber equal to <see cref="EntityVersionNumber" /> - 1.
2929
/// </remarks>
30-
ulong ExpectedPreviousVersionNumber { get; }
30+
ulong EntityVersionNumber { get; }
3131

3232
/// <summary>
3333
/// The intent.

src/EntityDb.Common.Tests/Transactions/TransactionBuilderTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@ public void GivenNonExistingEntityId_WhenUsingValidVersioningStrategy_ThenVersio
293293

294294
// ASSERT
295295

296-
for (ulong i = 0; i < NumberOfVersionsToTest; i++)
296+
for (ulong i = 1; i <= NumberOfVersionsToTest; i++)
297297
{
298-
transaction.Commands[(int)i].ExpectedPreviousVersionNumber.ShouldBe(i);
298+
transaction.Commands[(int)(i-1)].EntityVersionNumber.ShouldBe(i);
299299
}
300300
}
301301

src/EntityDb.Common.Tests/Transactions/TransactionTestsBase.cs

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,9 @@ public async Task GivenReadOnlyMode_WhenPuttingTransaction_ThenThrow()
420420
{
421421
new TransactionCommand<TransactionEntity>
422422
{
423-
NextSnapshot = default!,
423+
EntitySnapshot = default!,
424424
EntityId = Guid.NewGuid(),
425-
ExpectedPreviousVersionNumber = 0,
425+
EntityVersionNumber = 1,
426426
Command = new DoNothing(),
427427
Leases = new TransactionMetaData<ILease>(),
428428
Tags = new TransactionMetaData<ITag>()
@@ -456,9 +456,9 @@ static ITransaction<TransactionEntity> NewTransaction(Guid transactionId)
456456
{
457457
new TransactionCommand<TransactionEntity>
458458
{
459-
NextSnapshot = default!,
459+
EntitySnapshot = default!,
460460
EntityId = Guid.NewGuid(),
461-
ExpectedPreviousVersionNumber = 0,
461+
EntityVersionNumber = 1,
462462
Command = new DoNothing(),
463463
Leases = new TransactionMetaData<ILease>(),
464464
Tags = new TransactionMetaData<ITag>()
@@ -486,7 +486,7 @@ public async Task GivenNonUniqueVersionNumbers_WhenInsertingCommands_ThenReturnF
486486
// ARRANGE
487487

488488
var entityId = Guid.NewGuid();
489-
ulong previousVersionNumber = 0;
489+
const ulong entityVersionNumber = 1;
490490

491491
var transaction = new Transaction<TransactionEntity>
492492
{
@@ -497,18 +497,18 @@ public async Task GivenNonUniqueVersionNumbers_WhenInsertingCommands_ThenReturnF
497497
{
498498
new TransactionCommand<TransactionEntity>
499499
{
500-
NextSnapshot = default!,
500+
EntitySnapshot = default!,
501501
EntityId = entityId,
502-
ExpectedPreviousVersionNumber = previousVersionNumber,
502+
EntityVersionNumber = entityVersionNumber,
503503
Command = new DoNothing(),
504504
Leases = new TransactionMetaData<ILease>(),
505505
Tags = new TransactionMetaData<ITag>()
506506
},
507507
new TransactionCommand<TransactionEntity>
508508
{
509-
NextSnapshot = default!,
509+
EntitySnapshot = default!,
510510
EntityId = entityId,
511-
ExpectedPreviousVersionNumber = previousVersionNumber,
511+
EntityVersionNumber = entityVersionNumber,
512512
Command = new DoNothing(),
513513
Leases = new TransactionMetaData<ILease>(),
514514
Tags = new TransactionMetaData<ITag>()
@@ -527,17 +527,66 @@ public async Task GivenNonUniqueVersionNumbers_WhenInsertingCommands_ThenReturnF
527527
transactionInserted.ShouldBeFalse();
528528
}
529529

530+
[Fact]
531+
public async Task
532+
GivenVersionNumberZero_WhenInsertingCommands_ThenVersionZeroReservedExceptionIsLogged()
533+
{
534+
// ARRANGE
535+
536+
const ulong entityVersionNumber = 0;
537+
538+
var entityId = Guid.NewGuid();
539+
540+
var loggerMock = new Mock<ILogger>(MockBehavior.Strict);
541+
542+
loggerMock
543+
.Setup(logger => logger.LogError(It.IsAny<VersionZeroReservedException>(), It.IsAny<string>()))
544+
.Verifiable();
545+
546+
var transaction = new Transaction<TransactionEntity>
547+
{
548+
Id = Guid.NewGuid(),
549+
TimeStamp = DateTime.UtcNow,
550+
Source = new NoSource(),
551+
Commands = new[]
552+
{
553+
new TransactionCommand<TransactionEntity>
554+
{
555+
EntitySnapshot = default!,
556+
EntityId = entityId,
557+
EntityVersionNumber = entityVersionNumber,
558+
Command = new DoNothing(),
559+
Leases = new TransactionMetaData<ILease>(),
560+
Tags = new TransactionMetaData<ITag>()
561+
}
562+
}.ToImmutableArray<ITransactionCommand<TransactionEntity>>()
563+
};
564+
565+
await using var transactionRepository = await CreateRepository(loggerOverride: loggerMock.Object);
566+
567+
// ACT
568+
569+
var transactionInserted =
570+
await transactionRepository.PutTransaction(transaction);
571+
572+
// ASSERT
573+
574+
transactionInserted.ShouldBeTrue();
575+
576+
loggerMock.Verify();
577+
}
578+
530579
[Fact]
531580
public async Task
532581
GivenNonUniqueVersionNumbers_WhenInsertingCommands_ThenOptimisticConcurrencyExceptionIsLogged()
533582
{
534583
// ARRANGE
535584

536-
const ulong previousVersionNumber = 0;
585+
const ulong entityVersionNumber = 1;
537586

538587
var entityId = Guid.NewGuid();
539588

540-
static ITransaction<TransactionEntity> NewTransaction(Guid entityId, ulong previousVersionNumber)
589+
static ITransaction<TransactionEntity> NewTransaction(Guid entityId, ulong entityVersionNumber)
541590
{
542591
return new Transaction<TransactionEntity>
543592
{
@@ -548,9 +597,9 @@ static ITransaction<TransactionEntity> NewTransaction(Guid entityId, ulong previ
548597
{
549598
new TransactionCommand<TransactionEntity>
550599
{
551-
NextSnapshot = default!,
600+
EntitySnapshot = default!,
552601
EntityId = entityId,
553-
ExpectedPreviousVersionNumber = previousVersionNumber,
602+
EntityVersionNumber = entityVersionNumber,
554603
Command = new DoNothing(),
555604
Leases = new TransactionMetaData<ILease>(),
556605
Tags = new TransactionMetaData<ITag>()
@@ -570,9 +619,9 @@ static ITransaction<TransactionEntity> NewTransaction(Guid entityId, ulong previ
570619
// ACT
571620

572621
var firstTransactionInserted =
573-
await transactionRepository.PutTransaction(NewTransaction(entityId, previousVersionNumber));
622+
await transactionRepository.PutTransaction(NewTransaction(entityId, entityVersionNumber));
574623
var secondTransactionInserted =
575-
await transactionRepository.PutTransaction(NewTransaction(entityId, previousVersionNumber));
624+
await transactionRepository.PutTransaction(NewTransaction(entityId, entityVersionNumber));
576625

577626
// ASSERT
578627

@@ -598,9 +647,9 @@ public async Task GivenNonUniqueTags_WhenInsertingTagDocuments_ThenReturnTrue()
598647
{
599648
new TransactionCommand<TransactionEntity>
600649
{
601-
NextSnapshot = default!,
650+
EntitySnapshot = default!,
602651
EntityId = Guid.NewGuid(),
603-
ExpectedPreviousVersionNumber = 0,
652+
EntityVersionNumber = 1,
604653
Command = new DoNothing(),
605654
Leases = new TransactionMetaData<ILease>(),
606655
Tags = new TransactionMetaData<ITag>
@@ -610,9 +659,9 @@ public async Task GivenNonUniqueTags_WhenInsertingTagDocuments_ThenReturnTrue()
610659
},
611660
new TransactionCommand<TransactionEntity>
612661
{
613-
NextSnapshot = default!,
662+
EntitySnapshot = default!,
614663
EntityId = Guid.NewGuid(),
615-
ExpectedPreviousVersionNumber = 0,
664+
EntityVersionNumber = 1,
616665
Command = new DoNothing(),
617666
Leases = new TransactionMetaData<ILease>(),
618667
Tags = new TransactionMetaData<ITag>
@@ -650,9 +699,9 @@ public async Task GivenNonUniqueLeases_WhenInsertingLeaseDocuments_ThenReturnFal
650699
{
651700
new TransactionCommand<TransactionEntity>
652701
{
653-
NextSnapshot = default!,
702+
EntitySnapshot = default!,
654703
EntityId = Guid.NewGuid(),
655-
ExpectedPreviousVersionNumber = 0,
704+
EntityVersionNumber = 1,
656705
Command = new DoNothing(),
657706
Leases = new TransactionMetaData<ILease>
658707
{
@@ -662,9 +711,9 @@ public async Task GivenNonUniqueLeases_WhenInsertingLeaseDocuments_ThenReturnFal
662711
},
663712
new TransactionCommand<TransactionEntity>
664713
{
665-
NextSnapshot = default!,
714+
EntitySnapshot = default!,
666715
EntityId = Guid.NewGuid(),
667-
ExpectedPreviousVersionNumber = 0,
716+
EntityVersionNumber = 1,
668717
Command = new DoNothing(),
669718
Leases = new TransactionMetaData<ILease>
670719
{
@@ -819,7 +868,7 @@ public async Task GivenTransactionCreatesEntity_WhenQueryingForVersionOne_ThenRe
819868

820869
transaction.Commands.Length.ShouldBe(1);
821870

822-
transaction.Commands[0].ExpectedPreviousVersionNumber.ShouldBe(default);
871+
transaction.Commands[0].EntityVersionNumber.ShouldBe(1ul);
823872

824873
newCommands.Length.ShouldBe(1);
825874

@@ -862,7 +911,7 @@ public async Task
862911

863912
secondTransaction.Commands.Length.ShouldBe(1);
864913

865-
secondTransaction.Commands[0].ExpectedPreviousVersionNumber.ShouldBe(1ul);
914+
secondTransaction.Commands[0].EntityVersionNumber.ShouldBe(2ul);
866915

867916
newCommands.Length.ShouldBe(1);
868917

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using EntityDb.Abstractions.Transactions;
2+
using System;
3+
4+
namespace EntityDb.Common.Exceptions
5+
{
6+
/// <summary>
7+
/// The exception that is thrown when an actor passes an <see cref="ITransaction{TEntity}" /> to
8+
/// <see cref="ITransactionRepository{TEntity}.PutTransaction(ITransaction{TEntity})" /> with any
9+
/// <see cref="ITransactionCommand{TEntity}.EntityVersionNumber"/> equal to zero.
10+
/// </summary>
11+
/// <remarks>
12+
/// Version Zero is reserved for an entity that has not yet been created/persisted.
13+
/// </remarks>
14+
public class VersionZeroReservedException : Exception
15+
{
16+
/// <summary>
17+
/// Throws a new <see cref="VersionZeroReservedException" /> if <paramref name="versionNumber" /> is
18+
/// equal to zero.
19+
/// </summary>
20+
/// <param name="versionNumber"></param>
21+
public static void ThrowIfZero(ulong versionNumber)
22+
{
23+
if (versionNumber == 0)
24+
{
25+
throw new VersionZeroReservedException();
26+
}
27+
}
28+
}
29+
}

src/EntityDb.Common/Transactions/SnapshottingTransactionSubscriber.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ protected override async Task NotifyAsync(ITransaction<TEntity> transaction)
3030
foreach (var commandGroup in commandGroups)
3131
{
3232
var entityId = commandGroup.Key;
33-
var nextSnapshot = commandGroup.Last().NextSnapshot;
33+
var nextSnapshot = commandGroup.Last().EntitySnapshot;
3434

3535
await using var snapshotRepository =
3636
await _snapshotRepositoryFactory.CreateRepository(_snapshotSessionOptions);

src/EntityDb.Common/Transactions/TransactionBuilder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,17 @@ private ITag[] GetTags(TEntity entity)
7979
private void AddTransactionCommand(Guid entityId, ICommand<TEntity> command)
8080
{
8181
var previousEntity = _knownEntities[entityId];
82-
var previousVersionNumber = _versioningStrategy.GetVersionNumber(previousEntity);
8382

8483
CommandNotAuthorizedException.ThrowIfFalse(IsAuthorized(previousEntity, command));
8584

8685
var nextEntity = previousEntity.Reduce(command);
86+
var nextEntityVersionNumber = _versioningStrategy.GetVersionNumber(nextEntity);
8787

8888
_transactionCommands.Add(new TransactionCommand<TEntity>
8989
{
90-
NextSnapshot = nextEntity,
90+
EntitySnapshot = nextEntity,
9191
EntityId = entityId,
92-
ExpectedPreviousVersionNumber = previousVersionNumber,
92+
EntityVersionNumber = nextEntityVersionNumber,
9393
Command = command,
9494
Leases = GetTransactionMetaData(previousEntity, nextEntity, GetLeases),
9595
Tags = GetTransactionMetaData(previousEntity, nextEntity, GetTags)

src/EntityDb.Common/Transactions/TransactionCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace EntityDb.Common.Transactions
88
{
99
internal sealed record TransactionCommand<TEntity> : ITransactionCommand<TEntity>
1010
{
11-
public TEntity NextSnapshot { get; init; } = default!;
11+
public TEntity EntitySnapshot { get; init; } = default!;
1212
public Guid EntityId { get; init; }
13-
public ulong ExpectedPreviousVersionNumber { get; init; }
13+
public ulong EntityVersionNumber { get; init; }
1414
public ICommand<TEntity> Command { get; init; } = default!;
1515
public ITransactionMetaData<ILease> Leases { get; init; } = default!;
1616
public ITransactionMetaData<ITag> Tags { get; init; } = default!;

src/EntityDb.MongoDb/Documents/CommandDocument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ ITransactionCommand<TEntity> transactionCommand
3636
TransactionTimeStamp = transaction.TimeStamp,
3737
TransactionId = transaction.Id,
3838
EntityId = transactionCommand.EntityId,
39-
EntityVersionNumber = transactionCommand.ExpectedPreviousVersionNumber + 1,
39+
EntityVersionNumber = transactionCommand.EntityVersionNumber,
4040
Data = BsonDocumentEnvelope.Deconstruct(transactionCommand.Command, logger)
4141
};
4242
}

src/EntityDb.MongoDb/Documents/LeaseDocument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ ITransactionCommand<TEntity> transactionCommand
4444
TransactionTimeStamp = transaction.TimeStamp,
4545
TransactionId = transaction.Id,
4646
EntityId = transactionCommand.EntityId,
47-
EntityVersionNumber = transactionCommand.ExpectedPreviousVersionNumber + 1,
47+
EntityVersionNumber = transactionCommand.EntityVersionNumber,
4848
Scope = insertLease.Scope,
4949
Label = insertLease.Label,
5050
Value = insertLease.Value,

src/EntityDb.MongoDb/Documents/TagDocument.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ ITransactionCommand<TEntity> transactionCommand
4343
TransactionTimeStamp = transaction.TimeStamp,
4444
TransactionId = transaction.Id,
4545
EntityId = transactionCommand.EntityId,
46-
EntityVersionNumber = transactionCommand.ExpectedPreviousVersionNumber + 1,
46+
EntityVersionNumber = transactionCommand.EntityVersionNumber,
4747
Label = insertTag.Label,
4848
Value = insertTag.Value,
4949
Data = BsonDocumentEnvelope.Deconstruct(insertTag, logger)

0 commit comments

Comments
 (0)