Skip to content

Commit b54b081

Browse files
feature: entity repository, which encapsulates transactions and snapshots
absorbs the extension method on `IServiceCollection`, replaces the parameters in any method that previously requested transaction repository and an optional snapshot repository
1 parent a857420 commit b54b081

File tree

9 files changed

+181
-77
lines changed

9 files changed

+181
-77
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using EntityDb.Abstractions.Snapshots;
2+
using EntityDb.Abstractions.Transactions;
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
namespace EntityDb.Abstractions.Entities
7+
{
8+
/// <summary>
9+
/// Encapsulates the transaction repository and the snapshot repository of an entity.
10+
/// </summary>
11+
/// <typeparam name="TEntity">The type of the entity.</typeparam>
12+
public interface IEntityRepository<TEntity> : IDisposable, IAsyncDisposable
13+
{
14+
/// <inheritdoc cref="ITransactionRepository{TEntity}"/>
15+
ITransactionRepository<TEntity> TransactionRepository { get; }
16+
17+
/// <inheritdoc cref="ISnapshotRepository{TEntity}" />
18+
ISnapshotRepository<TEntity>? SnapshotRepository { get; }
19+
20+
/// <summary>
21+
/// Returns a <typeparamref name="TEntity"/> snapshot or <c>default(<typeparamref name="TEntity"/>)</c>.
22+
/// </summary>
23+
/// <param name="entityId">The id of the entity.</param>
24+
/// <returns>A <typeparamref name="TEntity"/> snapshot or <c>default(<typeparamref name="TEntity"/>)</c>.</returns>
25+
Task<TEntity> Get(Guid entityId);
26+
27+
/// <summary>
28+
/// Inserts a single transaction with an atomic commit.
29+
/// </summary>
30+
/// <param name="transaction">The transaction.</param>
31+
/// <returns><c>true</c> if the insert suceeded, or <c>false</c> if the insert failed.</returns>
32+
Task<bool> Put(ITransaction<TEntity> transaction);
33+
}
34+
}

EntityDb.Common.Tests/SnapshotTransactions/SnapshotTransactionsTestsBase.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using EntityDb.Abstractions.Snapshots;
1+
using EntityDb.Abstractions.Entities;
22
using EntityDb.Abstractions.Strategies;
33
using EntityDb.Abstractions.Transactions;
44
using EntityDb.Common.Extensions;
@@ -22,13 +22,13 @@ protected SnapshotTransactionsTestsBase(IServiceProvider serviceProvider) : base
2222
{
2323
}
2424

25-
private static async Task<ITransaction<TransactionEntity>> BuildTransaction(Guid entityId, ulong from, ulong to, IServiceProvider serviceProvider, ITransactionRepository<TransactionEntity>? transactionRepository = null, ISnapshotRepository<TransactionEntity>? snapshotRepository = null)
25+
private static async Task<ITransaction<TransactionEntity>> BuildTransaction(Guid entityId, ulong from, ulong to, IServiceProvider serviceProvider, IEntityRepository<TransactionEntity>? entityRepository = null)
2626
{
2727
var transactionBuilder = serviceProvider.GetTransactionBuilder<TransactionEntity>();
2828

29-
if (transactionRepository != null)
29+
if (entityRepository != null)
3030
{
31-
await transactionBuilder.Load(entityId, transactionRepository, snapshotRepository);
31+
await transactionBuilder.Load(entityId, entityRepository);
3232
}
3333
else
3434
{
@@ -62,23 +62,21 @@ public async Task GivenCachingOnNthVersion_WhenPuttingTransactionWithNthVersion_
6262

6363
var entityId = Guid.NewGuid();
6464

65-
await using var transactionRepository = await serviceProvider.CreateTransactionRepository<TransactionEntity>(new TransactionSessionOptions());
66-
67-
await using var snapshotRepository = await serviceProvider.CreateSnapshotRepository<TransactionEntity>(new SnapshotSessionOptions());
65+
await using var entityRepository = await serviceProvider.CreateEntityRepository<TransactionEntity>(new TransactionSessionOptions(), new SnapshotSessionOptions());
6866

6967
var firstTransaction = await BuildTransaction(entityId, 1, expectedSnapshotVersion, serviceProvider);
7068

71-
await transactionRepository.PutTransaction(firstTransaction);
69+
await entityRepository.Put(firstTransaction);
7270

73-
var secondTransaction = await BuildTransaction(entityId, expectedSnapshotVersion, expectedCurrentVersion, serviceProvider, transactionRepository, snapshotRepository);
71+
var secondTransaction = await BuildTransaction(entityId, expectedSnapshotVersion, expectedCurrentVersion, serviceProvider, entityRepository);
7472

75-
await transactionRepository.PutTransaction(secondTransaction);
73+
await entityRepository.Put(secondTransaction);
7674

7775
// ACT
7876

79-
var current = await serviceProvider.GetEntity(entityId, transactionRepository);
77+
var current = await entityRepository.Get(entityId);
8078

81-
var snapshot = await snapshotRepository.GetSnapshot(entityId);
79+
var snapshot = await entityRepository.SnapshotRepository!.GetSnapshot(entityId);
8280

8381
// ASSERT
8482

EntityDb.Common.Tests/Transactions/TransactionBuilderTests.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using EntityDb.Common.Exceptions;
66
using EntityDb.Common.Extensions;
77
using EntityDb.Common.Facts;
8-
using EntityDb.Common.Transactions;
98
using EntityDb.TestImplementations.Commands;
109
using EntityDb.TestImplementations.Entities;
1110
using EntityDb.TestImplementations.Facts;
@@ -48,11 +47,11 @@ public async Task GivenExistingEntityId_WhenExecutingUnauthorizedCommand_ThenApp
4847

4948
var transactionBuilder = serviceProvider.GetTransactionBuilder<TransactionEntity>();
5049

51-
await using var transactionRepository = await serviceProvider.CreateTransactionRepository<TransactionEntity>(new TransactionSessionOptions());
50+
await using var entityRepository = await serviceProvider.CreateEntityRepository<TransactionEntity>(default!);
5251

5352
// ACT
5453

55-
await transactionBuilder.Load(entityId, transactionRepository);
54+
await transactionBuilder.Load(entityId, entityRepository);
5655

5756
// ASSERT
5857

@@ -156,17 +155,17 @@ public async Task GivenExistingEntityId_WhenUsingEntityIdForLoadTwice_ThenLoadTh
156155

157156
var transactionBuilder = serviceProvider.GetTransactionBuilder<TransactionEntity>();
158157

159-
await using var transactionRepository = await serviceProvider.CreateTransactionRepository<TransactionEntity>(default!);
158+
await using var entityRepository = await serviceProvider.CreateEntityRepository<TransactionEntity>(default!);
160159

161160
// ACT
162161

163-
await transactionBuilder.Load(entityId, transactionRepository);
162+
await transactionBuilder.Load(entityId, entityRepository);
164163

165164
// ASSERT
166165

167166
await Should.ThrowAsync<EntityAlreadyLoadedException>(async () =>
168167
{
169-
await transactionBuilder.Load(entityId, transactionRepository);
168+
await transactionBuilder.Load(entityId, entityRepository);
170169
});
171170
}
172171

@@ -184,13 +183,13 @@ public async Task GivenNonExistentEntityId_WhenLoadingEntity_ThenLoadThrows()
184183

185184
var transactionBuilder = serviceProvider.GetTransactionBuilder<TransactionEntity>();
186185

187-
await using var transactionRepository = await serviceProvider.CreateTransactionRepository<TransactionEntity>(default!);
186+
await using var entityRepository = await serviceProvider.CreateEntityRepository<TransactionEntity>(default!);
188187

189188
// ASSERT
190189

191190
await Should.ThrowAsync<EntityNotCreatedException>(async () =>
192191
{
193-
await transactionBuilder.Load(Guid.NewGuid(), transactionRepository);
192+
await transactionBuilder.Load(Guid.NewGuid(), entityRepository);
194193
});
195194
}
196195

@@ -243,13 +242,13 @@ public async Task GivenExistingEntity_WhenAppendingNewCommand_ThenTransactionBui
243242

244243
var transactionBuilder = serviceProvider.GetTransactionBuilder<TransactionEntity>();
245244

246-
await using var transactionRepository = await serviceProvider.CreateTransactionRepository<TransactionEntity>(default!);
245+
await using var entityRepository = await serviceProvider.CreateEntityRepository<TransactionEntity>(default!);
247246

248247
// ACT
249248

250-
var entity = await serviceProvider.GetEntity(entityId, transactionRepository);
249+
var entity = await entityRepository.Get(entityId);
251250

252-
await transactionBuilder.Load(entityId, transactionRepository);
251+
await transactionBuilder.Load(entityId, entityRepository);
253252

254253
transactionBuilder.Append(entityId, new DoNothing());
255254

EntityDb.Common.Tests/Transactions/TransactionTestsBase.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@
33
using EntityDb.Abstractions.Leases;
44
using EntityDb.Abstractions.Loggers;
55
using EntityDb.Abstractions.Queries;
6-
using EntityDb.Abstractions.Queries.FilterBuilders;
7-
using EntityDb.Abstractions.Queries.SortBuilders;
86
using EntityDb.Abstractions.Transactions;
7+
using EntityDb.Common.Entities;
98
using EntityDb.Common.Exceptions;
109
using EntityDb.Common.Extensions;
1110
using EntityDb.Common.Leases;
@@ -681,13 +680,15 @@ public async Task GivenEntityInserted_WhenGettingEntity_ThenReturnEntity()
681680

682681
await using var transactionRepository = await CreateRepository();
683682

683+
var entityRepository = new EntityRepository<TransactionEntity>(_serviceProvider, transactionRepository);
684+
684685
var transaction = BuildTransaction(Guid.NewGuid(), entityId, new NoSource(), new[] { new DoNothing() });
685686

686687
await transactionRepository.PutTransaction(transaction);
687688

688689
// ACT
689690

690-
var actualEntity = await _serviceProvider.GetEntity(entityId, transactionRepository);
691+
var actualEntity = await entityRepository.Get(entityId);
691692

692693
// ASSERT
693694

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using EntityDb.Abstractions.Entities;
2+
using EntityDb.Abstractions.Snapshots;
3+
using EntityDb.Abstractions.Transactions;
4+
using EntityDb.Common.Extensions;
5+
using EntityDb.Common.Queries;
6+
using System;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Threading.Tasks;
9+
10+
namespace EntityDb.Common.Entities
11+
{
12+
internal class EntityRepository<TEntity> : IEntityRepository<TEntity>
13+
{
14+
private readonly IServiceProvider _serviceProvider;
15+
private readonly ITransactionRepository<TEntity> _transactionRepository;
16+
private readonly ISnapshotRepository<TEntity>? _snapshotRepository;
17+
18+
public ITransactionRepository<TEntity> TransactionRepository => _transactionRepository;
19+
public ISnapshotRepository<TEntity>? SnapshotRepository => _snapshotRepository;
20+
21+
public EntityRepository
22+
(
23+
IServiceProvider serviceProvider,
24+
ITransactionRepository<TEntity> transactionRepository,
25+
ISnapshotRepository<TEntity>? snapshotRepository = null
26+
)
27+
{
28+
_serviceProvider = serviceProvider;
29+
_transactionRepository = transactionRepository;
30+
_snapshotRepository = snapshotRepository;
31+
}
32+
33+
public async Task<TEntity> Get(Guid entityId)
34+
{
35+
TEntity? snapshot = default;
36+
37+
if (_snapshotRepository != null)
38+
{
39+
snapshot = await _snapshotRepository.GetSnapshot(entityId);
40+
}
41+
42+
var entity = snapshot ?? _serviceProvider.Construct<TEntity>(entityId);
43+
44+
var versionNumber = _serviceProvider.GetVersionNumber(entity);
45+
46+
var factQuery = new GetEntityQuery(entityId, versionNumber);
47+
48+
var facts = await _transactionRepository.GetFacts(factQuery);
49+
50+
entity = entity.Reduce(facts);
51+
52+
if (_serviceProvider.ShouldCache(snapshot, entity) && _snapshotRepository != null)
53+
{
54+
await _snapshotRepository.PutSnapshot(entityId, entity);
55+
}
56+
57+
return entity;
58+
}
59+
60+
public Task<bool> Put(ITransaction<TEntity> transaction)
61+
{
62+
return _transactionRepository.PutTransaction(transaction);
63+
}
64+
65+
[ExcludeFromCodeCoverage]
66+
public void Dispose()
67+
{
68+
DisposeAsync().AsTask().Wait();
69+
}
70+
71+
public async ValueTask DisposeAsync()
72+
{
73+
await _transactionRepository.DisposeAsync();
74+
75+
if (_snapshotRepository != null)
76+
{
77+
await _snapshotRepository.DisposeAsync();
78+
}
79+
}
80+
}
81+
}

EntityDb.Common/Exceptions/EntityAlreadyLoadedException.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using EntityDb.Common.Transactions;
1+
using EntityDb.Abstractions.Entities;
2+
using EntityDb.Common.Transactions;
23
using System;
34

45
namespace EntityDb.Common.Exceptions
56
{
67
/// <summary>
7-
/// The exception that is thrown when an actor passes an entity id to <see cref="TransactionBuilder{TEntity}.Load(Guid, Abstractions.Transactions.ITransactionRepository{TEntity}, Abstractions.Snapshots.ISnapshotRepository{TEntity}?)"/> with an entity id that has already been loaded.
8+
/// The exception that is thrown when an actor passes an entity id to <see cref="TransactionBuilder{TEntity}.Load(Guid, IEntityRepository{TEntity})"/> with an entity id that has already been loaded.
89
/// </summary>
910
public sealed class EntityAlreadyLoadedException : Exception
1011
{

EntityDb.Common/Exceptions/EntityNotCreatedException.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using EntityDb.Common.Transactions;
1+
using EntityDb.Abstractions.Entities;
2+
using EntityDb.Common.Transactions;
23
using System;
34

45
namespace EntityDb.Common.Exceptions
56
{
67
/// <summary>
7-
/// The exception that is thrown when an actor passes an entity id to <see cref="TransactionBuilder{TEntity}.Load(Guid, Abstractions.Transactions.ITransactionRepository{TEntity}, Abstractions.Snapshots.ISnapshotRepository{TEntity}?)"/> with an entity id that loads with a version number of zero.
8+
/// The exception that is thrown when an actor passes an entity id to <see cref="TransactionBuilder{TEntity}.Load(Guid, IEntityRepository{TEntity})"/> with an entity id that loads with a version number of zero.
89
/// </summary>
910
public sealed class EntityNotCreatedException : Exception
1011
{

0 commit comments

Comments
 (0)