Skip to content

Commit 2503116

Browse files
authored
ReadThroughAggregateCache indexes by both type and ID (#126)
1 parent a40896b commit 2503116

File tree

4 files changed

+84
-30
lines changed

4 files changed

+84
-30
lines changed

src/ReactiveDomain.Foundation.Tests/when_using_caching_repository.cs

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System;
2-
using System.Threading;
3-
using Newtonsoft.Json;
42
using ReactiveDomain.Foundation.StreamStore;
53
using ReactiveDomain.Messaging;
64
using ReactiveDomain.Testing.EventStore;
@@ -28,7 +26,6 @@ public when_using_caching_repository()
2826
[Fact]
2927
public void can_handle_repeated_and_cross_updates()
3028
{
31-
3229
var id = Guid.NewGuid();
3330
ICorrelatedMessage source = MessageBuilder.New(() => new CreateAccount(id));
3431
var cachedAccount = new Account(id, source);
@@ -82,6 +79,23 @@ public void can_handle_repeated_and_cross_updates()
8279
Assert.Equal(8, cachedAccount3.Balance); //cachedAccount is a pointer to same object as acct2/3/4 due to cache
8380
Assert.Equal(8, cachedAccount4.Balance); //cachedAccount is a pointer to same object as acct2/3/4 due to cache
8481
}
82+
83+
[Fact]
84+
public void can_cache_aggregates_with_different_types_and_same_id()
85+
{
86+
var accountId = Guid.NewGuid();
87+
ICorrelatedMessage source = MessageBuilder.New(() => new CreateAccount(accountId));
88+
var account = new Account(accountId, source);
89+
_cachingRepo.Save(account);
90+
91+
ICorrelatedMessage source2 = MessageBuilder.New(() => new CreateAccount(accountId));
92+
var other = new OtherAggregateType(accountId, source2);
93+
_cachingRepo.Save(other);
94+
95+
var cachedAccount = _cachingRepo.GetById<Account>(accountId);
96+
var cachedOther = _cachingRepo.GetById<OtherAggregateType>(accountId);
97+
}
98+
8599
[Fact]
86100
public void can_remove_by_id_from_the_cache()
87101
{
@@ -98,7 +112,7 @@ public void can_remove_by_id_from_the_cache()
98112
var cacheCopy = _cachingRepo.GetById<Account>(id);
99113
Assert.Equal(12, cacheCopy.Balance);
100114

101-
_cachingRepo.ClearCache(id);
115+
_cachingRepo.ClearCache<Account>(id);
102116

103117
var persistedCopy = _cachingRepo.GetById<Account>(id);
104118
Assert.Equal(5, persistedCopy.Balance);
@@ -130,14 +144,15 @@ private Account()
130144
}
131145
public Account(Guid id, ICorrelatedMessage source) : this()
132146
{
133-
Ensure.NotEmptyGuid(id, "id");
147+
Ensure.NotEmptyGuid(id, nameof(id));
134148
Raise(new AccountCreated(id));
135149
}
136150
public void Credit(uint amount)
137151
{
138152
Raise(new AccountCredited(Id, amount));
139153
}
140154
}
155+
141156
public class CreateAccount : Command
142157
{
143158
public readonly Guid AccountId;
@@ -146,6 +161,7 @@ public CreateAccount(Guid accountId)
146161
AccountId = accountId;
147162
}
148163
}
164+
149165
public class AccountCreated : IMessage
150166
{
151167
public Guid MsgId { get; private set; }
@@ -156,6 +172,7 @@ public AccountCreated(Guid accountId)
156172
AccountId = accountId;
157173
}
158174
}
175+
159176
public class CreditAccount : Command
160177
{
161178
public readonly Guid AccountId;
@@ -166,6 +183,7 @@ public CreditAccount(Guid accountId, uint amount)
166183
Amount = amount;
167184
}
168185
}
186+
169187
public class AccountCredited : IMessage
170188
{
171189
public Guid MsgId { get; private set; }
@@ -179,5 +197,40 @@ public AccountCredited(Guid accountId, uint amount)
179197
Amount = amount;
180198
}
181199
}
200+
201+
public class OtherAggregateType : EventDrivenStateMachine
202+
{
203+
private OtherAggregateType()
204+
{
205+
Register<OtherAggregateCreated>(e => Id = e.AggregateId);
206+
}
207+
public OtherAggregateType(Guid id, ICorrelatedMessage source) : this()
208+
{
209+
Ensure.NotEmptyGuid(id, nameof(id));
210+
Raise(new OtherAggregateCreated(id));
211+
}
212+
}
213+
214+
public class CreateOtherAggregate : Command
215+
{
216+
public readonly Guid AggregateId;
217+
218+
public CreateOtherAggregate(Guid aggregateId)
219+
{
220+
AggregateId = aggregateId;
221+
}
222+
}
223+
224+
public class OtherAggregateCreated : IMessage
225+
{
226+
public Guid MsgId { get; }
227+
public readonly Guid AggregateId;
228+
229+
public OtherAggregateCreated(Guid aggregateId)
230+
{
231+
MsgId = Guid.NewGuid();
232+
AggregateId = aggregateId;
233+
}
234+
}
182235
}
183236
}

src/ReactiveDomain.Foundation/StreamStore/CachingRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ public void Delete(IEventSource aggregate) {
3737
public void HardDelete(IEventSource aggregate) {
3838
_cache.HardDelete(aggregate);
3939
}
40-
public bool ClearCache(Guid id) {
41-
return _cache.Remove(id);
40+
public bool ClearCache<TAggregate>(Guid id) {
41+
return _cache.Remove<TAggregate>(id);
4242
}
4343
public void ClearCache() {
4444
_cache.Clear();
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
using System;
22

3-
namespace ReactiveDomain.Foundation.StreamStore {
3+
namespace ReactiveDomain.Foundation.StreamStore
4+
{
45
/// <summary>
56
/// While it might seem more natural to save and restore the event set behind the aggregate,
67
/// this cache stores only the collapsed state in the aggregate
78
/// </summary>
8-
public interface IAggregateCache:IRepository, IDisposable
9+
public interface IAggregateCache : IRepository, IDisposable
910
{
10-
bool Remove(Guid id);
11+
bool Remove<TAggregate>(Guid id);
1112
void Clear();
1213
}
1314
}

src/ReactiveDomain.Foundation/StreamStore/ReadThroughAggregateCache.cs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections;
32
using System.Collections.Generic;
43

54
namespace ReactiveDomain.Foundation.StreamStore
@@ -20,7 +19,7 @@ public class ReadThroughAggregateCache : IAggregateCache, IDisposable
2019
{
2120

2221
private readonly IRepository _baseRepository;
23-
private readonly Dictionary<Guid, IEventSource> _knownAggregates = new Dictionary<Guid, IEventSource>();
22+
private readonly Dictionary<(Type type, Guid id), IEventSource> _knownAggregates = new Dictionary<(Type type, Guid id), IEventSource>();
2423
public ReadThroughAggregateCache(IRepository baseRepository) {
2524
_baseRepository = baseRepository;
2625
}
@@ -31,32 +30,32 @@ public bool TryGetById<TAggregate>(Guid id, out TAggregate aggregate, int versio
3130
}
3231
catch (Exception) {
3332
aggregate = null;
34-
Remove(id);
33+
Remove<TAggregate>(id);
3534
return false;
3635
}
3736
}
3837

3938
public TAggregate GetById<TAggregate>(Guid id, int version = int.MaxValue) where TAggregate : class, IEventSource {
40-
if (_knownAggregates.TryGetValue(id, out var cached)) {
39+
if (_knownAggregates.TryGetValue((typeof(TAggregate), id), out var cached)) {
4140
var agg = (TAggregate) cached;
4241
Update(ref agg, version);
4342
return (TAggregate)cached;
4443
}
4544

4645
var aggregate = _baseRepository.GetById<TAggregate>(id, version);
47-
_knownAggregates.Add(id, aggregate);
46+
_knownAggregates.Add((typeof(TAggregate), id), aggregate);
4847
return aggregate;
4948
}
5049

5150
public void Update<TAggregate>(ref TAggregate aggregate, int version = int.MaxValue) where TAggregate : class, IEventSource {
5251
if (aggregate == null) throw new ArgumentNullException(nameof(aggregate));
5352
if (aggregate.ExpectedVersion == version) return;
5453

55-
_knownAggregates.TryGetValue(aggregate.Id, out var cached);
54+
_knownAggregates.TryGetValue((typeof(TAggregate), aggregate.Id), out var cached);
5655

5756
if (cached == null) {
5857
_baseRepository.Update(ref aggregate, version);
59-
UpdateCache(aggregate);
58+
UpdateCache<TAggregate>(aggregate);
6059
return;
6160
}
6261

@@ -74,34 +73,35 @@ public void Update<TAggregate>(ref TAggregate aggregate, int version = int.MaxVa
7473
//cache is ahead of item, but behind requested version
7574
aggregate = (TAggregate)cached;
7675
_baseRepository.Update(ref aggregate, version);
77-
UpdateCache(aggregate);
76+
UpdateCache<TAggregate>(aggregate);
7877
}
7978

8079

8180

82-
private void UpdateCache(IEventSource aggregate) {
83-
if (_knownAggregates.TryGetValue(aggregate.Id, out var cached)) {
81+
private void UpdateCache<TAggregate>(IEventSource aggregate) {
82+
if (_knownAggregates.TryGetValue((typeof(TAggregate), aggregate.Id), out var cached)) {
8483
if (cached.ExpectedVersion < aggregate.ExpectedVersion) {
85-
_knownAggregates[aggregate.Id] = aggregate;
84+
_knownAggregates[(typeof(TAggregate), aggregate.Id)] = aggregate;
8685
}
8786
}
8887
else {
89-
_knownAggregates.Add(aggregate.Id, aggregate);
88+
_knownAggregates.Add((typeof(TAggregate), aggregate.Id), aggregate);
9089
}
9190
}
9291

9392
public void Save(IEventSource aggregate) {
93+
var type = aggregate.GetType();
9494
try {
9595
_baseRepository.Save(aggregate);
96-
if (!_knownAggregates.ContainsKey(aggregate.Id)) {
97-
_knownAggregates.Add(aggregate.Id, aggregate);
96+
if (!_knownAggregates.ContainsKey((type, aggregate.Id))) {
97+
_knownAggregates.Add((type, aggregate.Id), aggregate);
9898
}
9999
else {
100-
_knownAggregates[aggregate.Id] = aggregate;
100+
_knownAggregates[(type, aggregate.Id)] = aggregate;
101101
}
102102
}
103103
catch {
104-
_knownAggregates.Remove(aggregate.Id);
104+
_knownAggregates.Remove((type, aggregate.Id));
105105
}
106106

107107

@@ -113,7 +113,7 @@ public void Save(IEventSource aggregate) {
113113
/// <param name="aggregate">The aggregate to be deleted.</param>
114114
public void Delete(IEventSource aggregate) {
115115
_baseRepository.Delete(aggregate);
116-
_knownAggregates.Remove(aggregate.Id);
116+
_knownAggregates.Remove((aggregate.GetType(), aggregate.Id));
117117
}
118118

119119
/// <summary>
@@ -122,11 +122,11 @@ public void Delete(IEventSource aggregate) {
122122
/// <param name="aggregate">The aggregate to be deleted.</param>
123123
public void HardDelete(IEventSource aggregate) {
124124
_baseRepository.HardDelete(aggregate);
125-
_knownAggregates.Remove(aggregate.Id);
125+
_knownAggregates.Remove((aggregate.GetType(), aggregate.Id));
126126
}
127127

128-
public bool Remove(Guid id) {
129-
return _knownAggregates.Remove(id);
128+
public bool Remove<TAggregate>(Guid id) {
129+
return _knownAggregates.Remove((typeof(TAggregate), id));
130130
}
131131
public void Clear() {
132132
_knownAggregates.Clear();

0 commit comments

Comments
 (0)