This repository has been archived by the owner on Jan 16, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Support for persistence to EventStoreDB. Fixes #3 #4
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
27a3bc5
Start on persistence
jonclare 7cce8f8
More work on deserializing events
jonclare 208f764
Add some custom converters as a hack to get serialization/deserializa…
jonclare 1bd70bd
Upgrade test assemblies to .NET 5.0 to take advantage of serializati…
jonclare 69bf6cb
Add a custom attribute that the library uses to determine event names…
jonclare ae9f572
Simplify repository by pulling all the logic to get event names into …
jonclare 2dfd3c0
Add some tests for EventNameMapping
jonclare c3a5251
Allow proper serialization of exceptions
jonclare d3dde80
Test to ensure that building the event routes also wires up the event…
jonclare 06d0aac
Simplify EventNameMap name and ensure it's consistent everywhere
jonclare File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
src/DomainLib.Persistence.EventStore/DomainLib.Persistence.EventStore.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,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="EventStore.Client" Version="20.6.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\DomainLib.Persistence\DomainLib.Serialization.csproj" /> | ||
<ProjectReference Include="..\DomainLib\DomainLib.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
13 changes: 13 additions & 0 deletions
13
src/DomainLib.Persistence.EventStore/EventStoreEventDataExtensions.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,13 @@ | ||
using EventStore.ClientAPI; | ||
|
||
namespace DomainLib.Persistence.EventStore | ||
{ | ||
public static class EventStoreEventDataExtensions | ||
{ | ||
public static EventData ToEventData(this IEventSerializer @eventSerializer, object @event) | ||
{ | ||
var eventPersistenceData = eventSerializer.GetPersistenceData(@event); | ||
return new EventData(eventPersistenceData.EventId, eventPersistenceData.EventName, eventPersistenceData.IsJsonBytes, eventPersistenceData.EventData, eventPersistenceData.EventMetadata); | ||
} | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/DomainLib.Persistence.EventStore/EventStoreEventsRepository.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,54 @@ | ||
using EventStore.ClientAPI; | ||
using EventStore.ClientAPI.Exceptions; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace DomainLib.Persistence.EventStore | ||
{ | ||
public class EventStoreEventsRepository : IEventsRepository | ||
{ | ||
private readonly IEventStoreConnection _connection; | ||
private readonly IEventSerializer _serializer; | ||
|
||
public EventStoreEventsRepository(IEventStoreConnection connection, IEventSerializer serializer) | ||
{ | ||
_connection = connection; | ||
_serializer = serializer; | ||
} | ||
|
||
public async Task<long> SaveEventsAsync<TEvent>(string streamName, long expectedStreamVersion, IEnumerable<TEvent> events) | ||
{ | ||
var eventDatas = events.Select(e => _serializer.ToEventData(e)); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we have an issue to address this TODO? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created #8 |
||
// TODO: Handle failure cases | ||
var writeResult = await _connection.AppendToStreamAsync(streamName, expectedStreamVersion, eventDatas); | ||
|
||
return writeResult.NextExpectedVersion; | ||
} | ||
|
||
public async Task<IEnumerable<TEvent>> LoadEventsAsync<TEvent>(string streamName) | ||
{ | ||
// TODO: Handle large streams in batches | ||
var eventsSlice = await _connection.ReadStreamEventsForwardAsync(streamName, 0, ClientApiConstants.MaxReadSize, false); | ||
|
||
switch (eventsSlice.Status) | ||
{ | ||
case SliceReadStatus.Success: | ||
break; | ||
case SliceReadStatus.StreamNotFound: | ||
// TOOO: Better exception | ||
throw new InvalidOperationException($"Unable to find stream {streamName}"); | ||
case SliceReadStatus.StreamDeleted: | ||
throw new StreamDeletedException(streamName); | ||
default: | ||
throw new ArgumentOutOfRangeException(); | ||
} | ||
|
||
var events = eventsSlice.Events; | ||
|
||
return events.Select(e => _serializer.DeserializeEvent<TEvent>(e.OriginalEvent.Data, e.OriginalEvent.EventType)); | ||
} | ||
} | ||
} |
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,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="System.Text.Json" Version="4.7.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\DomainLib\DomainLib.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
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,46 @@ | ||
using System; | ||
using System.Runtime.Serialization; | ||
|
||
namespace DomainLib.Serialization | ||
{ | ||
[Serializable] | ||
public class InvalidEventTypeException : Exception | ||
{ | ||
public string SerializedEventType { get; } | ||
public string ClrTypeName { get; } | ||
|
||
public InvalidEventTypeException(string serializedEventType, string clrTypeName) | ||
{ | ||
SerializedEventType = serializedEventType; | ||
ClrTypeName = clrTypeName; | ||
} | ||
|
||
public InvalidEventTypeException(string message, string serializedEventType, string clrTypeName) | ||
: base(message) | ||
{ | ||
SerializedEventType = serializedEventType; | ||
ClrTypeName = clrTypeName; | ||
} | ||
|
||
public InvalidEventTypeException(string message, string serializedEventType, string clrTypeName, Exception inner) | ||
: base(message, inner) | ||
{ | ||
SerializedEventType = serializedEventType; | ||
ClrTypeName = clrTypeName; | ||
} | ||
|
||
protected InvalidEventTypeException(SerializationInfo info, StreamingContext context) | ||
: base(info, context) | ||
{ | ||
if (info == null) | ||
{ | ||
throw new ArgumentNullException(nameof(info)); | ||
} | ||
|
||
info.AddValue(nameof(ClrTypeName), ClrTypeName); | ||
info.AddValue(nameof(SerializedEventType), SerializedEventType); | ||
|
||
base.GetObjectData(info, context); | ||
} | ||
} | ||
} |
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,23 @@ | ||
using System; | ||
using DomainLib.Persistence; | ||
|
||
namespace DomainLib.Serialization | ||
{ | ||
public readonly struct JsonEventPersistenceData : IEventPersistenceData | ||
{ | ||
public JsonEventPersistenceData(Guid eventId, string eventName, byte[] eventData, byte[] eventMetadata) : this() | ||
{ | ||
EventId = eventId; | ||
EventName = eventName; | ||
IsJsonBytes = true; | ||
EventData = eventData; | ||
EventMetadata = eventMetadata; | ||
} | ||
|
||
public Guid EventId { get; } | ||
public string EventName { get; } | ||
public bool IsJsonBytes { get; } | ||
public byte[] EventData { get; } | ||
public byte[] EventMetadata { get; } | ||
} | ||
} |
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,59 @@ | ||
using DomainLib.Aggregates; | ||
using DomainLib.Persistence; | ||
using System; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace DomainLib.Serialization | ||
{ | ||
public class JsonEventSerializer : IEventSerializer | ||
{ | ||
private readonly JsonSerializerOptions _options; | ||
private readonly IEventNameMap _eventNameMap = new EventNameMap(); | ||
|
||
public JsonEventSerializer() | ||
{ | ||
_options = new JsonSerializerOptions | ||
{ | ||
WriteIndented = true, | ||
AllowTrailingCommas = false, | ||
}; | ||
} | ||
|
||
public JsonEventSerializer(JsonSerializerOptions options) | ||
{ | ||
_options = options; | ||
} | ||
|
||
public void RegisterConverter(JsonConverter customConverter) | ||
{ | ||
_options.Converters.Add(customConverter); | ||
} | ||
|
||
public void RegisterEventTypeMappings(IEventNameMap eventNameMap) | ||
{ | ||
_eventNameMap.Merge(eventNameMap); | ||
} | ||
|
||
public IEventPersistenceData GetPersistenceData(object @event) | ||
{ | ||
var eventName = _eventNameMap.GetEventNameForClrType(@event.GetType()); | ||
return new JsonEventPersistenceData(Guid.NewGuid(), eventName, JsonSerializer.SerializeToUtf8Bytes(@event, _options), null); | ||
} | ||
|
||
public TEvent DeserializeEvent<TEvent>(byte[] eventData, string eventName) | ||
{ | ||
var clrType = _eventNameMap.GetClrTypeForEventName(eventName); | ||
|
||
var evt = JsonSerializer.Deserialize(eventData, clrType, _options); | ||
|
||
if (evt is TEvent @event) | ||
{ | ||
return @event; | ||
} | ||
|
||
var runtTimeType = typeof(TEvent); | ||
throw new InvalidEventTypeException($"Cannot cast event of type {eventName} to {runtTimeType.FullName}", eventName, runtTimeType.FullName); | ||
} | ||
} | ||
} |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dude wrap this 😛