-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from jacqueskang/feature/caching
feat: provide an abstract aggregate repository which supports distrib…
- Loading branch information
Showing
10 changed files
with
189 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
src/JKang.EventSourcing.Persistence.Caching/CachedAggregateRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
using JKang.EventSourcing.Domain; | ||
using JKang.EventSourcing.Events; | ||
using JKang.EventSourcing.Snapshotting.Persistence; | ||
using Microsoft.Extensions.Caching.Distributed; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Converters; | ||
using Newtonsoft.Json.Serialization; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace JKang.EventSourcing.Persistence | ||
{ | ||
public abstract class CachedAggregateRepository<TAggregate, TKey> | ||
: AggregateRepository<TAggregate, TKey> | ||
where TAggregate : class, IAggregate<TKey> | ||
{ | ||
private readonly IDistributedCache _cache; | ||
private readonly DistributedCacheEntryOptions _cacheOptions; | ||
private static readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings | ||
{ | ||
TypeNameHandling = TypeNameHandling.Objects, | ||
ContractResolver = new CamelCasePropertyNamesContractResolver(), | ||
NullValueHandling = NullValueHandling.Ignore, | ||
Formatting = Formatting.None, | ||
Converters = new[] { new StringEnumConverter() }, | ||
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead, | ||
}; | ||
|
||
protected CachedAggregateRepository( | ||
IEventStore<TAggregate, TKey> eventStore, | ||
ISnapshotStore<TAggregate, TKey> snapshotStore, | ||
IDistributedCache cache, | ||
DistributedCacheEntryOptions cacheOptions) | ||
: base(eventStore, snapshotStore) | ||
{ | ||
_cache = cache; | ||
_cacheOptions = cacheOptions; | ||
} | ||
|
||
protected override async Task<TAggregate> FindAggregateAsync( | ||
TKey id, | ||
bool ignoreSnapshot = false, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
string key = GetCacheKey(id); | ||
string serialized = await _cache.GetStringAsync(key, cancellationToken).ConfigureAwait(false); | ||
if (!string.IsNullOrEmpty(serialized)) | ||
{ | ||
return DeserializeAggregate(serialized); | ||
} | ||
|
||
TAggregate aggregate = await base.FindAggregateAsync(id, ignoreSnapshot, cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
await CacheAsync(aggregate, cancellationToken).ConfigureAwait(false); | ||
|
||
return aggregate; | ||
} | ||
|
||
protected override async Task<IAggregateChangeset<TKey>> SaveAggregateAsync( | ||
TAggregate aggregate, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
if (aggregate is null) | ||
{ | ||
throw new System.ArgumentNullException(nameof(aggregate)); | ||
} | ||
|
||
IAggregateChangeset<TKey> changeset = await base.SaveAggregateAsync(aggregate, cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
await CacheAsync(aggregate, cancellationToken).ConfigureAwait(false); | ||
|
||
return changeset; | ||
} | ||
|
||
private async Task CacheAsync(TAggregate aggregate, | ||
CancellationToken cancellationToken) | ||
{ | ||
string serialized = SerializeAggregate(aggregate); | ||
string key = GetCacheKey(aggregate.Id); | ||
await _cache.SetStringAsync(key, serialized, _cacheOptions, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
protected virtual string GetCacheKey(TKey id) => $"{typeof(TAggregate).FullName}_{id}"; | ||
|
||
protected virtual TAggregate DeserializeAggregate(string serialized) | ||
{ | ||
CachedAggregate cached = JsonConvert.DeserializeObject<CachedAggregate>(serialized, _jsonSerializerSettings); | ||
|
||
if (cached.Snapshot == null) | ||
{ | ||
return Activator.CreateInstance(typeof(TAggregate), cached.Id, cached.Events) as TAggregate; | ||
} | ||
else | ||
{ | ||
return Activator.CreateInstance(typeof(TAggregate), cached.Id, cached.Snapshot, cached.Events) as TAggregate; | ||
} | ||
} | ||
|
||
protected virtual string SerializeAggregate(TAggregate aggregate) | ||
{ | ||
var cached = new CachedAggregate | ||
{ | ||
Id = aggregate.Id, | ||
Snapshot = aggregate.Snapshot, | ||
Events = aggregate.Events | ||
}; | ||
return JsonConvert.SerializeObject(cached, _jsonSerializerSettings); | ||
} | ||
|
||
internal class CachedAggregate | ||
{ | ||
public TKey Id { get; set; } | ||
public IAggregateSnapshot<TKey> Snapshot { get; set; } | ||
public IEnumerable<IAggregateEvent<TKey>> Events { get; set; } | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/JKang.EventSourcing.Persistence.Caching/JKang.EventSourcing.Persistence.Caching.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<RootNamespace>JKang.EventSourcing.Persistence</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.1" /> | ||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\JKang.EventSourcing.Abstractions\JKang.EventSourcing.Abstractions.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
27 changes: 27 additions & 0 deletions
27
src/JKang.EventSourcing.TestingFixtures/CachedGiftCardRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using JKang.EventSourcing.Persistence; | ||
using JKang.EventSourcing.Snapshotting.Persistence; | ||
using Microsoft.Extensions.Caching.Distributed; | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace JKang.EventSourcing.TestingFixtures | ||
{ | ||
public class CachedGiftCardRepository : CachedAggregateRepository<GiftCard, Guid>, IGiftCardRepository | ||
{ | ||
public CachedGiftCardRepository( | ||
IEventStore<GiftCard, Guid> eventStore, | ||
ISnapshotStore<GiftCard, Guid> snapshotStore, | ||
IDistributedCache cache) | ||
: base(eventStore, snapshotStore, cache, new DistributedCacheEntryOptions | ||
{ | ||
SlidingExpiration = TimeSpan.FromSeconds(10) | ||
}) | ||
{ } | ||
|
||
public Task SaveGiftCardAsync(GiftCard giftCard) => SaveAggregateAsync(giftCard); | ||
|
||
public Task<GiftCard> FindGiftCardAsync(Guid id) => FindAggregateAsync(id); | ||
|
||
public Task<Guid[]> GetGiftCardIdsAsync() => GetAggregateIdsAsync(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters