Skip to content

Commit bd4b735

Browse files
authored
feat: Annotated agent signatures (#36)
1 parent f6a4ddb commit bd4b735

File tree

10 files changed

+178
-3
lines changed

10 files changed

+178
-3
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using EntityDb.Abstractions.ValueObjects;
2+
3+
namespace EntityDb.Abstractions.Annotations;
4+
5+
/// <summary>
6+
/// Represents data for multiple entities that have already been committed, along with relevant information not contained
7+
/// in the data.
8+
/// </summary>
9+
/// <typeparam name="TData">The type of data.</typeparam>
10+
public interface IEntitiesAnnotation<out TData>
11+
{
12+
/// <summary>
13+
/// The transaction id associated with the data.
14+
/// </summary>
15+
Id TransactionId { get; }
16+
17+
/// <summary>
18+
/// The transaction timestamp associated with the data.
19+
/// </summary>
20+
TimeStamp TransactionTimeStamp { get; }
21+
22+
/// <summary>
23+
/// The entity ids associated with the data.
24+
/// </summary>
25+
Id[] EntityIds { get; }
26+
27+
/// <summary>
28+
/// The data.
29+
/// </summary>
30+
TData Data { get; }
31+
}

src/EntityDb.Abstractions/Transactions/ITransactionRepository.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ public interface ITransactionRepository : IDisposableResource
110110
/// <returns>The tags which are found by <paramref name="tagQuery" />.</returns>
111111
Task<ITag[]> GetTags(ITagQuery tagQuery, CancellationToken cancellationToken = default);
112112

113+
/// <summary>
114+
/// Returns the annotated agent signatures which are found by an agent signature query.
115+
/// </summary>
116+
/// <param name="agentSignatureQuery">The agent signature query.</param>
117+
/// <param name="cancellationToken">A cancellation token.</param>
118+
/// <returns>The annotated agent signatures which are found by <paramref name="agentSignatureQuery" />.</returns>
119+
Task<IEntitiesAnnotation<object>[]> GetAnnotatedAgentSignatures(IAgentSignatureQuery agentSignatureQuery, CancellationToken cancellationToken = default);
120+
113121
/// <summary>
114122
/// Returns the annotated commands which are found by a command query.
115123
/// </summary>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using EntityDb.Abstractions.Annotations;
2+
using EntityDb.Abstractions.ValueObjects;
3+
using System;
4+
5+
namespace EntityDb.Common.Annotations;
6+
7+
internal record EntitiesAnnotation<TData>
8+
(
9+
Id TransactionId,
10+
TimeStamp TransactionTimeStamp,
11+
Id[] EntityIds,
12+
TData Data
13+
) : IEntitiesAnnotation<TData>
14+
{
15+
public static IEntitiesAnnotation<TData> CreateFromBoxedData
16+
(
17+
Id transactionId,
18+
TimeStamp transactionTimeStamp,
19+
Id[] entityIds,
20+
object boxedData
21+
)
22+
{
23+
var dataAnnotationType = typeof(EntitiesAnnotation<>).MakeGenericType(boxedData.GetType());
24+
25+
return (IEntitiesAnnotation<TData>)Activator.CreateInstance
26+
(
27+
dataAnnotationType,
28+
transactionId,
29+
transactionTimeStamp,
30+
entityIds,
31+
boxedData
32+
)!;
33+
}
34+
}

src/EntityDb.Common/Annotations/EntityAnnotation.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using EntityDb.Abstractions.Annotations;
2-
using EntityDb.Abstractions.Transactions;
3-
using EntityDb.Abstractions.Transactions.Steps;
1+
using EntityDb.Abstractions.Annotations;
42
using EntityDb.Abstractions.ValueObjects;
53
using System;
64

src/EntityDb.Common/Transactions/TransactionRepositoryWrapper.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public Task<ITag[]> GetTags(ITagQuery tagQuery, CancellationToken cancellationTo
8080
return WrapQuery(() => _transactionRepository.GetTags(tagQuery, cancellationToken));
8181
}
8282

83+
public Task<IEntitiesAnnotation<object>[]> GetAnnotatedAgentSignatures(IAgentSignatureQuery agentSignatureQuery, CancellationToken cancellationToken = default)
84+
{
85+
return WrapQuery(() => _transactionRepository.GetAnnotatedAgentSignatures(agentSignatureQuery, cancellationToken));
86+
}
87+
8388
public Task<IEntityAnnotation<object>[]> GetAnnotatedCommands(ICommandQuery commandQuery, CancellationToken cancellationToken = default)
8489
{
8590
return WrapQuery(() => _transactionRepository.GetAnnotatedCommands(commandQuery, cancellationToken));

src/EntityDb.MongoDb/Extensions/DocumentQueryExtensions.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,29 @@ CancellationToken cancellationToken
121121
.ToArray();
122122
}
123123

124+
public static async Task<IEntitiesAnnotation<TData>[]> GetEntitiesAnnotation<TDocument, TData>
125+
(
126+
this DocumentQuery<TDocument> documentQuery,
127+
IMongoSession mongoSession,
128+
IEnvelopeService<BsonDocument> envelopeService,
129+
CancellationToken cancellationToken
130+
)
131+
where TDocument : IEntitiesDocument
132+
where TData : notnull
133+
{
134+
var documents = await documentQuery.Execute(mongoSession, NoDocumentIdProjection, cancellationToken);
135+
136+
return documents
137+
.Select(document => EntitiesAnnotation<TData>.CreateFromBoxedData
138+
(
139+
document.TransactionId,
140+
document.TransactionTimeStamp,
141+
document.EntityIds,
142+
envelopeService.Reconstruct<TData>(document.Data)
143+
))
144+
.ToArray();
145+
}
146+
124147
public static async Task<TData[]> GetData<TDocument, TData>
125148
(
126149
this DocumentQuery<TDocument> documentQuery,

src/EntityDb.MongoDb/Transactions/MongoDbTransactionRepository.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ public Task<ITag[]> GetTags(ITagQuery tagQuery, CancellationToken cancellationTo
117117
.GetData<TagDocument, ITag>(_mongoSession, _envelopeService, cancellationToken);
118118
}
119119

120+
public Task<IEntitiesAnnotation<object>[]> GetAnnotatedAgentSignatures(IAgentSignatureQuery agentSignatureQuery, CancellationToken cancellationToken = default)
121+
{
122+
return AgentSignatureDocument
123+
.GetQuery(agentSignatureQuery)
124+
.GetEntitiesAnnotation<AgentSignatureDocument, object>(_mongoSession, _envelopeService, cancellationToken);
125+
}
126+
120127
public Task<IEntityAnnotation<object>[]> GetAnnotatedCommands(ICommandQuery commandQuery, CancellationToken cancellationToken = default)
121128
{
122129
return CommandDocument

src/EntityDb.Void/Transactions/VoidTransactionRepository.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ internal sealed class VoidTransactionRepository : DisposableResourceBaseClass, I
1717
private static readonly Task<object[]> EmptyObjectArrayTask = Task.FromResult(Array.Empty<object>());
1818
private static readonly Task<ILease[]> EmptyLeaseArrayTask = Task.FromResult(Array.Empty<ILease>());
1919
private static readonly Task<ITag[]> EmptyTagArrayTask = Task.FromResult(Array.Empty<ITag>());
20+
private static readonly Task<IEntitiesAnnotation<object>[]> EmptyEntitiesAnnotationArrayTask = Task.FromResult(Array.Empty<IEntitiesAnnotation<object>>());
2021
private static readonly Task<IEntityAnnotation<object>[]> EmptyEntityAnnotationArrayTask = Task.FromResult(Array.Empty<IEntityAnnotation<object>>());
2122
private static readonly Task<bool> TrueBoolTask = Task.FromResult(true);
2223

@@ -80,6 +81,11 @@ public Task<ITag[]> GetTags(ITagQuery tagQuery, CancellationToken cancellationTo
8081
return EmptyTagArrayTask.WaitAsync(cancellationToken);
8182
}
8283

84+
public Task<IEntitiesAnnotation<object>[]> GetAnnotatedAgentSignatures(IAgentSignatureQuery agentSignatureQuery, CancellationToken cancellationToken = default)
85+
{
86+
return EmptyEntitiesAnnotationArrayTask.WaitAsync(cancellationToken);
87+
}
88+
8389
public Task<IEntityAnnotation<object>[]> GetAnnotatedCommands(ICommandQuery commandQuery, CancellationToken cancellationToken = default)
8490
{
8591
return EmptyEntityAnnotationArrayTask.WaitAsync(cancellationToken);

test/EntityDb.Common.Tests/Transactions/TransactionTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,65 @@ public async Task GivenNonUniqueLeases_WhenInsertingLeaseDocuments_ThenReturnFal
755755
transactionInserted.ShouldBeFalse();
756756
}
757757

758+
[Theory]
759+
[MemberData(nameof(AddTransactions))]
760+
public async Task GivenCommandInserted_WhenGettingAnnotatedAgentSignature_ThenReturnAnnotatedAgentSignature(TransactionsAdder transactionsAdder)
761+
{
762+
// ARRANGE
763+
764+
const ulong expectedCount = 5;
765+
766+
using var serviceScope = CreateServiceScope(serviceCollection =>
767+
{
768+
transactionsAdder.Add(serviceCollection);
769+
});
770+
771+
var transactionTimeStamp = TimeStamp.UtcNow;
772+
773+
var expectedTransactionId = Id.NewId();
774+
var expectedEntityId = Id.NewId();
775+
var expectedTransactionTimeStamps = new[]
776+
{
777+
transactionTimeStamp,
778+
779+
// A TimeStamp can be more precise than milliseconds.
780+
// This allows for database types that cannot be more precise than milliseconds.
781+
transactionTimeStamp.WithMillisecondPrecision()
782+
};
783+
784+
var agentSignature = new CounterAgentSignature(123);
785+
786+
var transaction = BuildTransaction(serviceScope, expectedTransactionId, expectedEntityId,
787+
new[] { expectedCount }, transactionTimeStamp, agentSignature);
788+
789+
await using var transactionRepository = await serviceScope.ServiceProvider
790+
.GetRequiredService<ITransactionRepositoryFactory>()
791+
.CreateRepository(TestSessionOptions.Write);
792+
793+
var transactionInserted = await transactionRepository.PutTransaction(transaction);
794+
795+
var agentSignatureQuery = new EntityIdQuery(expectedEntityId);
796+
797+
// ARRANGE ASSERTIONS
798+
799+
transactionInserted.ShouldBeTrue();
800+
801+
// ACT
802+
803+
var annotatedAgentSignatures = await transactionRepository.GetAnnotatedAgentSignatures(agentSignatureQuery);
804+
805+
// ASSERT
806+
807+
annotatedAgentSignatures.Length.ShouldBe(1);
808+
809+
annotatedAgentSignatures[0].TransactionId.ShouldBe(expectedTransactionId);
810+
annotatedAgentSignatures[0].EntityIds.Length.ShouldBe(1);
811+
annotatedAgentSignatures[0].EntityIds[0].ShouldBe(expectedEntityId);
812+
annotatedAgentSignatures[0].Data.ShouldBe(agentSignature);
813+
814+
expectedTransactionTimeStamps.Contains(annotatedAgentSignatures[0].TransactionTimeStamp).ShouldBeTrue();
815+
}
816+
758817
[Theory]
759818
[MemberData(nameof(AddTransactions))]
760819
public async Task GivenCommandInserted_WhenGettingAnnotatedCommand_ThenReturnAnnotatedCommand(TransactionsAdder transactionsAdder)

test/EntityDb.Common.Tests/Transactions/TryCatchTransactionRepositoryTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public async Task GivenRepositoryAlwaysThrows_WhenExecutingAnyMethod_ThenExcepti
7474
.Setup(repository => repository.GetTags(It.IsAny<ITagQuery>(), It.IsAny<CancellationToken>()))
7575
.ThrowsAsync(new NotImplementedException());
7676

77+
transactionRepositoryMock
78+
.Setup(repository => repository.GetAnnotatedAgentSignatures(It.IsAny<IAgentSignatureQuery>(), It.IsAny<CancellationToken>()))
79+
.ThrowsAsync(new NotImplementedException());
80+
7781
transactionRepositoryMock
7882
.Setup(repository => repository.GetAnnotatedCommands(It.IsAny<ICommandQuery>(), It.IsAny<CancellationToken>()))
7983
.ThrowsAsync(new NotImplementedException());

0 commit comments

Comments
 (0)