From 253b41cb2e19caae796e4b69fa41afd96cc26973 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 09:37:05 +0100 Subject: [PATCH 01/33] Renamed EventStore names to Kurrent. Updated packages. Changed Corsham references to Pharmaxo. --- ...ore.sln => CR.MessageDispatch.Kurrent.sln} | 0 src/eventstore/CatchupProgress.cs | 6 +-- .../CheckpointingWrappingDispatcher.cs | 10 ++--- src/eventstore/EventWrapper.cs | 6 +-- src/eventstore/GlobalSuppressions.cs | 4 +- ....cs => KurrentAggregateEventDispatcher.cs} | 16 ++++---- ...patcher.cs => KurrentJObjectDispatcher.cs} | 14 +++---- ...toreSubscriber.cs => KurrentSubscriber.cs} | 40 ++++++++++--------- .../NoSynchronizationContextScope.cs | 4 +- ...spatcher.cs => SimpleKurrentDispatcher.cs} | 14 +++---- src/eventstore/WriteThroughFileCheckpoint.cs | 6 +-- src/eventstore/eventstore.csproj | 19 ++++----- 12 files changed, 71 insertions(+), 68 deletions(-) rename src/{CR.MessageDispatch.EventStore.sln => CR.MessageDispatch.Kurrent.sln} (100%) rename src/eventstore/{EventStoreAggregateEventDispatcher.cs => KurrentAggregateEventDispatcher.cs} (87%) rename src/eventstore/{EventStoreJObjectDispatcher.cs => KurrentJObjectDispatcher.cs} (74%) rename src/eventstore/{EventStoreSubscriber.cs => KurrentSubscriber.cs} (93%) rename src/eventstore/{SimpleEventStoreDispatcher.cs => SimpleKurrentDispatcher.cs} (77%) diff --git a/src/CR.MessageDispatch.EventStore.sln b/src/CR.MessageDispatch.Kurrent.sln similarity index 100% rename from src/CR.MessageDispatch.EventStore.sln rename to src/CR.MessageDispatch.Kurrent.sln diff --git a/src/eventstore/CatchupProgress.cs b/src/eventstore/CatchupProgress.cs index 3da9850..766e2c2 100644 --- a/src/eventstore/CatchupProgress.cs +++ b/src/eventstore/CatchupProgress.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { /// /// Class to handle calculating catchup progress. diff --git a/src/eventstore/CheckpointingWrappingDispatcher.cs b/src/eventstore/CheckpointingWrappingDispatcher.cs index 1b074f5..6d11787 100644 --- a/src/eventstore/CheckpointingWrappingDispatcher.cs +++ b/src/eventstore/CheckpointingWrappingDispatcher.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System; using System.Text; @@ -13,7 +13,7 @@ namespace CorshamScience.MessageDispatch.EventStore /// /// A wrapping event dispatcher which keeps track of a checkpoint, and whether the dispatched event has been previously processed or not. /// - public class CheckpointingWrappingDispatcher : EventStoreAggregateEventDispatcher + public class CheckpointingWrappingDispatcher : KurrentAggregateEventDispatcher { private readonly WriteThroughFileCheckpoint _checkpoint; private readonly long _startupCheckpointValue; @@ -63,7 +63,7 @@ protected override bool TryDeserialize( try { - var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span); + var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); var @event = JObject.Parse(jsonString); deserialized = new EventWrapper(@event, previouslyProcessed); diff --git a/src/eventstore/EventWrapper.cs b/src/eventstore/EventWrapper.cs index 5e7fb5f..ee4b18b 100644 --- a/src/eventstore/EventWrapper.cs +++ b/src/eventstore/EventWrapper.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using Newtonsoft.Json.Linq; diff --git a/src/eventstore/GlobalSuppressions.cs b/src/eventstore/GlobalSuppressions.cs index 758d81e..98b75e5 100644 --- a/src/eventstore/GlobalSuppressions.cs +++ b/src/eventstore/GlobalSuppressions.cs @@ -1,5 +1,5 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "The file was taken from the eventstore codebase so it is not valid to add a copyright header.", Scope = "namespace", Target = "~N:CorshamScience.MessageDispatch.EventStore")] diff --git a/src/eventstore/EventStoreAggregateEventDispatcher.cs b/src/eventstore/KurrentAggregateEventDispatcher.cs similarity index 87% rename from src/eventstore/EventStoreAggregateEventDispatcher.cs rename to src/eventstore/KurrentAggregateEventDispatcher.cs index 234f676..ca0bb72 100644 --- a/src/eventstore/EventStoreAggregateEventDispatcher.cs +++ b/src/eventstore/KurrentAggregateEventDispatcher.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System; using System.Collections.Generic; @@ -17,7 +17,7 @@ namespace CorshamScience.MessageDispatch.EventStore /// A deserializing event dispatcher for events produced by CorshamScience.AggregatRepository. /// // ReSharper disable once UnusedMember.Global - public class EventStoreAggregateEventDispatcher : DeserializingMessageDispatcher + public class KurrentAggregateEventDispatcher : DeserializingMessageDispatcher { private readonly JsonSerializerSettings _serializerSettings; private readonly Dictionary _typeCache = new Dictionary(); @@ -26,12 +26,12 @@ public class EventStoreAggregateEventDispatcher : DeserializingMessageDispatcher #pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The handler methods for processing messages with. /// Determines the settings for the JSON serialization of events. // ReSharper disable once UnusedMember.Global - public EventStoreAggregateEventDispatcher( + public KurrentAggregateEventDispatcher( IMessageHandlerLookup handlers, JsonSerializerSettings serializerSettings = null, string metadataKey = null) @@ -55,7 +55,7 @@ protected override bool TryGetMessageType(ResolvedEvent rawMessage, out Type typ try { - IDictionary metadata = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Metadata.Span)); + IDictionary metadata = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Metadata.Span.ToArray())); if (!metadata.ContainsKey(_metadataKey)) { @@ -108,7 +108,7 @@ protected override bool TryDeserialize(Type messageType, ResolvedEvent rawMessag try { - var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span); + var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); deserialized = JsonConvert.DeserializeObject(jsonString, messageType, _serializerSettings); return deserialized != null; } diff --git a/src/eventstore/EventStoreJObjectDispatcher.cs b/src/eventstore/KurrentJObjectDispatcher.cs similarity index 74% rename from src/eventstore/EventStoreJObjectDispatcher.cs rename to src/eventstore/KurrentJObjectDispatcher.cs index 83db131..9175ea3 100644 --- a/src/eventstore/EventStoreJObjectDispatcher.cs +++ b/src/eventstore/KurrentJObjectDispatcher.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System; using System.Text; @@ -15,16 +15,16 @@ namespace CorshamScience.MessageDispatch.EventStore /// A message dispatcher that deserializes messages to a JObject upon dispatch. /// // ReSharper disable once UnusedMember.Global - public class EventStoreJObjectDispatcher : DeserializingMessageDispatcher + public class KurrentJObjectDispatcher : DeserializingMessageDispatcher { #pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Lookups for the handlers which the class can use to process messages. // ReSharper disable once UnusedMember.Global - public EventStoreJObjectDispatcher(IMessageHandlerLookup handlers) + public KurrentJObjectDispatcher(IMessageHandlerLookup handlers) : base(handlers) { } @@ -44,7 +44,7 @@ protected override bool TryDeserialize(string messageType, ResolvedEvent rawMess try { - deserialized = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Data.Span)); + deserialized = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray())); return true; } catch (Exception) diff --git a/src/eventstore/EventStoreSubscriber.cs b/src/eventstore/KurrentSubscriber.cs similarity index 93% rename from src/eventstore/EventStoreSubscriber.cs rename to src/eventstore/KurrentSubscriber.cs index 4d7030e..39c521c 100644 --- a/src/eventstore/EventStoreSubscriber.cs +++ b/src/eventstore/KurrentSubscriber.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System; using System.Linq; @@ -15,9 +15,10 @@ namespace CorshamScience.MessageDispatch.EventStore /// /// Subscriber for event store. /// - public class EventStoreSubscriber + public class KurrentSubscriber { private const string AllStreamName = "$all"; + private const uint CheckpointInterval = 1; private readonly WriteThroughFileCheckpoint _checkpoint; private readonly object _subscriptionLock = new object(); @@ -39,7 +40,7 @@ public class EventStoreSubscriber private IDispatcher _dispatcher; private ILogger _logger; - private EventStoreSubscriber( + private KurrentSubscriber( EventStoreClient eventStoreClient, IDispatcher dispatcher, string streamName, @@ -48,7 +49,7 @@ private EventStoreSubscriber( ulong liveEventThreshold) => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); - private EventStoreSubscriber( + private KurrentSubscriber( EventStoreClient eventStoreClient, IDispatcher dispatcher, ILogger logger, @@ -68,7 +69,7 @@ private EventStoreSubscriber( Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); } - private EventStoreSubscriber( + private KurrentSubscriber( EventStoreClient eventStoreClient, IDispatcher dispatcher, string streamName, @@ -133,13 +134,13 @@ public bool IsLive /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static EventStoreSubscriber CreateLiveSubscription( + public static KurrentSubscriber CreateLiveSubscription( EventStoreClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong liveEventThreshold = 10) - => new EventStoreSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); + => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); /// /// Creates an eventstore catchup subscription using a checkpoint file. @@ -152,14 +153,14 @@ public static EventStoreSubscriber CreateLiveSubscription( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static EventStoreSubscriber CreateCatchupSubscriptionUsingCheckpoint( + public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( EventStoreClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new EventStoreSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); + => new KurrentSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); /// /// Creates an eventstore catchup subscription from a position. @@ -172,14 +173,14 @@ public static EventStoreSubscriber CreateCatchupSubscriptionUsingCheckpoint( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static EventStoreSubscriber CreateCatchupSubscriptionFromPosition( + public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( EventStoreClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new EventStoreSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); + => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); /// /// Creates an eventstore catchup subscription that is subscribed to all from the start. @@ -190,12 +191,12 @@ public static EventStoreSubscriber CreateCatchupSubscriptionFromPosition( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static EventStoreSubscriber CreateCatchupSubscriptionSubscribedToAll( + public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( EventStoreClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong liveEventThreshold = 10) - => new EventStoreSubscriber( + => new KurrentSubscriber( eventStoreClient, dispatcher, AllStreamName, @@ -212,13 +213,13 @@ public static EventStoreSubscriber CreateCatchupSubscriptionSubscribedToAll( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static EventStoreSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( + public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( EventStoreClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new EventStoreSubscriber( + => new KurrentSubscriber( eventStoreClient, dispatcher, AllStreamName, @@ -236,13 +237,13 @@ public static EventStoreSubscriber CreateCatchupSubscriptionSubscribedToAllFromP /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static EventStoreSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( + public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( EventStoreClient eventStoreClient, IDispatcher dispatcher, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new EventStoreSubscriber( + => new KurrentSubscriber( eventStoreClient, dispatcher, logger, @@ -273,6 +274,7 @@ public void Start() { var filterOptions = new SubscriptionFilterOptions( EventTypeFilter.ExcludeSystemEvents(), + CheckpointInterval, checkpointReached: CheckpointReached); const bool resolveLinkTos = true; diff --git a/src/eventstore/NoSynchronizationContextScope.cs b/src/eventstore/NoSynchronizationContextScope.cs index 2f3b05b..e5291b4 100644 --- a/src/eventstore/NoSynchronizationContextScope.cs +++ b/src/eventstore/NoSynchronizationContextScope.cs @@ -1,11 +1,11 @@ /* This file is taken from Event Store codebase https://github.com/EventStore/samples/blob/main/CQRS_Flow/.NET/Core/Core/Threading/NoSynchronizationContextScope.cs - As such we should not add a Corsham Science copyright file header */ + As such we should not add a Pharmaxo Scientific copyright file header */ // ReSharper disable InconsistentNaming #pragma warning disable CS8632, SA1600, SX1309 -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System; using System.Threading; diff --git a/src/eventstore/SimpleEventStoreDispatcher.cs b/src/eventstore/SimpleKurrentDispatcher.cs similarity index 77% rename from src/eventstore/SimpleEventStoreDispatcher.cs rename to src/eventstore/SimpleKurrentDispatcher.cs index 6574917..84765e1 100644 --- a/src/eventstore/SimpleEventStoreDispatcher.cs +++ b/src/eventstore/SimpleKurrentDispatcher.cs @@ -1,8 +1,8 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System; using System.Collections.Generic; @@ -15,7 +15,7 @@ namespace CorshamScience.MessageDispatch.EventStore /// /// A simple event store dispatcher. /// - public class SimpleEventStoreDispatcher : DeserializingMessageDispatcher + public class SimpleKurrentDispatcher : DeserializingMessageDispatcher { private readonly Dictionary _eventTypeMapping; private readonly JsonSerializerSettings _serializerSettings; @@ -23,13 +23,13 @@ public class SimpleEventStoreDispatcher : DeserializingMessageDispatcher /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Message handler lookup of a type. /// Event Type Map. /// Json Serializer settings. // ReSharper disable once UnusedMember.Global - public SimpleEventStoreDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) + public SimpleKurrentDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) : base(handlers) { _eventTypeMapping = eventTypeMapping; @@ -51,7 +51,7 @@ protected override bool TryDeserialize(Type messageType, ResolvedEvent rawMessag try { - var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span); + var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); deserialized = JsonConvert.DeserializeObject(jsonString, messageType, _serializerSettings); return deserialized != null; } diff --git a/src/eventstore/WriteThroughFileCheckpoint.cs b/src/eventstore/WriteThroughFileCheckpoint.cs index 5da49b8..080044b 100644 --- a/src/eventstore/WriteThroughFileCheckpoint.cs +++ b/src/eventstore/WriteThroughFileCheckpoint.cs @@ -1,9 +1,9 @@ -// -// Copyright (c) Corsham Science. All rights reserved. +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. // #pragma warning disable CA1001 -namespace CorshamScience.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore { using System.IO; diff --git a/src/eventstore/eventstore.csproj b/src/eventstore/eventstore.csproj index 594fdc0..8afa2a8 100644 --- a/src/eventstore/eventstore.csproj +++ b/src/eventstore/eventstore.csproj @@ -1,18 +1,19 @@  - net6.0 - CorshamScience.MessageDispatch.EventStore - CorshamScience.MessageDispatch.EventStore - Corsham Science - Corsham Science - Corsham Science 2019 + net8.0;net481 + latest + PharmaxoScientific.MessageDispatch.EventStore + PharmaxoScientific.MessageDispatch.EventStore + Pharmaxo Scientific + Pharmaxo Scientific + Pharmaxo Scientific https://github.com/qphl/MessageDispatch.EventStore/blob/master/LICENSE https://github.com/qphl/MessageDispatch.EventStore https://github.com/qphl/MessageDispatch.EventStore Message Dispatching, Event Sourcing - CorshamScience.MessageDispatch.EventStore - CorshamScience.MessageDispatch + PharmaxoScientific.MessageDispatch.EventStore + PharmaxoScientific.MessageDispatch A package to use EventStore to get Events to Dispatch using CorshamScience.MessageDispatch. https://GitHub.com/qphl/MessageDispatch.EventStore/releases/tag/$(Tag) BSD-3-Clause @@ -34,7 +35,7 @@ All - + From f159c1b32696b2ea6c0adc1f39f88bdda1c6586b Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 10:22:53 +0100 Subject: [PATCH 02/33] Additional replacements of CorshamScience to PharmaxoScientific --- README.md | 2 +- src/eventstore/eventstore.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1cd6e2d..19aeb2f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CorshamScience.MessageDispatch.EventStore +# PharmaxoScientific.MessageDispatch.EventStore A package to use EventStore with CorshamScience.MessageDispatch. diff --git a/src/eventstore/eventstore.csproj b/src/eventstore/eventstore.csproj index 8afa2a8..f783d97 100644 --- a/src/eventstore/eventstore.csproj +++ b/src/eventstore/eventstore.csproj @@ -21,11 +21,11 @@ - bin\Debug\net6.0\CorshamScience.MessageDispatch.EventStore.xml + bin\Debug\net6.0\PharmaxoScientific.MessageDispatch.EventStore.xml - bin\Release\net6.0\CorshamScience.MessageDispatch.EventStore.xml + bin\Release\net6.0\PharmaxoScientific.MessageDispatch.EventStore.xml true From 835e1f92672110fe52a8c2f527b136d272ab4757 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 11:44:29 +0100 Subject: [PATCH 03/33] Changed project name from eventstore to kurrent --- src/CR.MessageDispatch.Kurrent.sln | 50 +++++++++---------- .../{eventstore.csproj => kurrent.csproj} | 0 2 files changed, 25 insertions(+), 25 deletions(-) rename src/eventstore/{eventstore.csproj => kurrent.csproj} (100%) diff --git a/src/CR.MessageDispatch.Kurrent.sln b/src/CR.MessageDispatch.Kurrent.sln index 3abbc73..0b363c0 100644 --- a/src/CR.MessageDispatch.Kurrent.sln +++ b/src/CR.MessageDispatch.Kurrent.sln @@ -1,25 +1,25 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eventstore", "eventstore\eventstore.csproj", "{8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {58A60822-0CA7-4255-AA1E-533ECE617C52} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35931.192 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kurrent", "eventstore\kurrent.csproj", "{8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {58A60822-0CA7-4255-AA1E-533ECE617C52} + EndGlobalSection +EndGlobal diff --git a/src/eventstore/eventstore.csproj b/src/eventstore/kurrent.csproj similarity index 100% rename from src/eventstore/eventstore.csproj rename to src/eventstore/kurrent.csproj From a758349e35bd18586c917e4c52a21f494d099d4d Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 11:47:10 +0100 Subject: [PATCH 04/33] run dotnet format --- .../KurrentAggregateEventDispatcher.cs | 10 ++-- src/eventstore/KurrentSubscriber.cs | 56 +++++++++---------- .../NoSynchronizationContextScope.cs | 6 +- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/eventstore/KurrentAggregateEventDispatcher.cs b/src/eventstore/KurrentAggregateEventDispatcher.cs index ca0bb72..433a04d 100644 --- a/src/eventstore/KurrentAggregateEventDispatcher.cs +++ b/src/eventstore/KurrentAggregateEventDispatcher.cs @@ -35,11 +35,11 @@ public KurrentAggregateEventDispatcher( IMessageHandlerLookup handlers, JsonSerializerSettings serializerSettings = null, string metadataKey = null) - : base(handlers) - { - _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); - _metadataKey = metadataKey ?? "ClrType"; - } + : base(handlers) + { + _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); + _metadataKey = metadataKey ?? "ClrType"; + } #pragma warning restore SA1648 // inheritdoc should be used with inheriting class /// diff --git a/src/eventstore/KurrentSubscriber.cs b/src/eventstore/KurrentSubscriber.cs index 39c521c..69ad064 100644 --- a/src/eventstore/KurrentSubscriber.cs +++ b/src/eventstore/KurrentSubscriber.cs @@ -294,20 +294,20 @@ Task Appeared( resolveLinkTos, SubscriptionDropped).Result; break; - case false when !_subscribeToAll: - { - var fromStream = _startingPosition.HasValue ? - FromStream.After(new StreamPosition(_startingPosition.Value)) : - FromStream.Start; - - _subscription = _eventStoreClient.SubscribeToStreamAsync( - _streamName, - fromStream, - Appeared, - resolveLinkTos, - SubscriptionDropped).Result; - break; - } + case false when !_subscribeToAll: + { + var fromStream = _startingPosition.HasValue ? + FromStream.After(new StreamPosition(_startingPosition.Value)) : + FromStream.Start; + + _subscription = _eventStoreClient.SubscribeToStreamAsync( + _streamName, + fromStream, + Appeared, + resolveLinkTos, + SubscriptionDropped).Result; + break; + } case true when _subscribeToAll: _subscription = _eventStoreClient.SubscribeToAllAsync( @@ -401,20 +401,20 @@ private void Init( _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEvent.Position.CommitPosition; _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEvent.Position.CommitPosition; - } - : async () => - { - var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( - Direction.Backwards, - _streamName, - StreamPosition.End, - maxCount: (long)_liveEventThreshold, - resolveLinkTos: false) - .ToListAsync(); - - _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); - _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); - }; + } + : async () => + { + var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( + Direction.Backwards, + _streamName, + StreamPosition.End, + maxCount: (long)_liveEventThreshold, + resolveLinkTos: false) + .ToListAsync(); + + _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); + _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); + }; } private void SubscriptionDropped(StreamSubscription eventStoreCatchUpSubscription, SubscriptionDroppedReason subscriptionDropReason, Exception ex) diff --git a/src/eventstore/NoSynchronizationContextScope.cs b/src/eventstore/NoSynchronizationContextScope.cs index e5291b4..3c2c6cd 100644 --- a/src/eventstore/NoSynchronizationContextScope.cs +++ b/src/eventstore/NoSynchronizationContextScope.cs @@ -1,4 +1,8 @@ -/* This file is taken from Event Store codebase +// +// Copyright (c) Pharmaxo Scientific. All rights reserved. +// + +/* This file is taken from Event Store codebase https://github.com/EventStore/samples/blob/main/CQRS_Flow/.NET/Core/Core/Threading/NoSynchronizationContextScope.cs As such we should not add a Pharmaxo Scientific copyright file header */ From a660dc34a28b0dd8a74cff25616627d30d844968 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 11:49:45 +0100 Subject: [PATCH 05/33] renamed solution --- ...CR.MessageDispatch.Kurrent.sln => MessageDispatch.Kurrent.sln} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{CR.MessageDispatch.Kurrent.sln => MessageDispatch.Kurrent.sln} (100%) diff --git a/src/CR.MessageDispatch.Kurrent.sln b/src/MessageDispatch.Kurrent.sln similarity index 100% rename from src/CR.MessageDispatch.Kurrent.sln rename to src/MessageDispatch.Kurrent.sln From e28d5570478395e6475329afd96cd5f47d564775 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 11:55:42 +0100 Subject: [PATCH 06/33] Added editorconfig and ran formatting --- src/.editorconfig | 212 ++++ src/Directory.Build.targets | 5 + src/eventstore/CatchupProgress.cs | 137 ++- .../CheckpointingWrappingDispatcher.cs | 117 ++- src/eventstore/EventWrapper.cs | 55 +- src/eventstore/GlobalSuppressions.cs | 6 +- .../KurrentAggregateEventDispatcher.cs | 181 ++-- src/eventstore/KurrentJObjectDispatcher.cs | 77 +- src/eventstore/KurrentSubscriber.cs | 943 +++++++++--------- .../NoSynchronizationContextScope.cs | 43 +- src/eventstore/SimpleKurrentDispatcher.cs | 95 +- src/eventstore/WriteThroughFileCheckpoint.cs | 112 +-- src/eventstore/kurrent.csproj | 7 +- 13 files changed, 1091 insertions(+), 899 deletions(-) create mode 100644 src/.editorconfig create mode 100644 src/Directory.Build.targets diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..8799067 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,212 @@ +# editorconfig.org + +# Pharmaxo Scientific .net Codestyles editorconfig v1.0 + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[project.json] +indent_size = 2 + +# C# and Visual Basic files +[*.{cs,vb}] +charset = utf-8-bom + +# Analyzers +dotnet_analyzer_diagnostic.category-Security.severity = warning +dotnet_code_quality.ca1802.api_surface = private, internal + +# Miscellaneous style rules +dotnet_sort_system_directives_first = true +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have _ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = warning +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = _ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = warning +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code quality +dotnet_style_readonly_field = true:warning +dotnet_code_quality_unused_parameters = non_public:warning + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = error + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Experimental New line rules + +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning +dotnet_style_allow_multiple_blank_lines_experimental = false:warning + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning + +# Code style defaults +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_braces = true:warning +csharp_preserve_single_line_blocks = true:warning +csharp_preserve_single_line_statements = false:warning +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_constructors = when_on_single_line:suggestion +csharp_style_expression_bodied_operators = when_on_single_line:suggestion +csharp_style_expression_bodied_properties = when_on_single_line:suggestion +csharp_style_expression_bodied_indexers = when_on_single_line:suggestion +csharp_style_expression_bodied_accessors = when_on_single_line:suggestion +csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion +csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Namespace preference +csharp_style_namespace_declarations = file_scoped:warning +dotnet_style_namespace_match_folder = true:suggestion + +# Types: Will suggest var in all instances, but does not enforce it. +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = true:suggestion + +# License header (second line required to set severity level for this) +file_header_template = Copyright (c) Pharmaxo. All rights reserved. +dotnet_diagnostic.IDE0073.severity = warning + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf + +[*.{cmd, bat}] +end_of_line = crlf + +# Markdown files +[*.md] + # Double trailing spaces can be used for BR tags, and other instances are enforced by Markdownlint +trim_trailing_whitespace = false diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..f1bdc6a --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,5 @@ + + + true + + diff --git a/src/eventstore/CatchupProgress.cs b/src/eventstore/CatchupProgress.cs index 766e2c2..425bafb 100644 --- a/src/eventstore/CatchupProgress.cs +++ b/src/eventstore/CatchupProgress.cs @@ -1,85 +1,82 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// Class to handle calculating catchup progress. +/// +public class CatchupProgress { /// - /// Class to handle calculating catchup progress. + /// Initializes a new instance of the class. /// - public class CatchupProgress + /// The last processed event position. + /// The name of the stream which is being caught up on (or $all if this is subscribed to all). + /// The end of the stream position (stream position for stream subscription of commit position for all subscription). + /// The starting position (Event number for stream subscription or commit position for all subscription). + /// Whether the subscriber is subscribed to all. + public CatchupProgress( + ulong lastProcessedEventPosition, + string streamName, + ulong endOfStreamPosition, + ulong startPosition, + bool subscribeToAll) { - /// - /// Initializes a new instance of the class. - /// - /// The last processed event position. - /// The name of the stream which is being caught up on (or $all if this is subscribed to all). - /// The end of the stream position (stream position for stream subscription of commit position for all subscription). - /// The starting position (Event number for stream subscription or commit position for all subscription). - /// Whether the subscriber is subscribed to all. - public CatchupProgress( - ulong lastProcessedEventPosition, - string streamName, - ulong endOfStreamPosition, - ulong startPosition, - bool subscribeToAll) - { - IsAllSubscription = subscribeToAll; - LastProcessedEventPosition = lastProcessedEventPosition; - StartPosition = startPosition; - StreamName = streamName; - EndOfStreamPosition = endOfStreamPosition; - } + IsAllSubscription = subscribeToAll; + LastProcessedEventPosition = lastProcessedEventPosition; + StartPosition = startPosition; + StreamName = streamName; + EndOfStreamPosition = endOfStreamPosition; + } - /// - /// Gets a value indicating whether the subscriber is subscribed to all. - /// - public bool IsAllSubscription { get; } + /// + /// Gets a value indicating whether the subscriber is subscribed to all. + /// + public bool IsAllSubscription { get; } - /// - /// Gets the name of the stream ($all if this is an all subscription). - /// - public string StreamName { get; } + /// + /// Gets the name of the stream ($all if this is an all subscription). + /// + public string StreamName { get; } - /// - /// Gets the starting position (Event number for stream subscription or commit position for all subscription). - /// - public ulong StartPosition { get; } + /// + /// Gets the starting position (Event number for stream subscription or commit position for all subscription). + /// + public ulong StartPosition { get; } - /// - /// Gets the last processed event (stream position for stream subscription of commit position for all subscription). - /// - public ulong LastProcessedEventPosition { get; } + /// + /// Gets the last processed event (stream position for stream subscription of commit position for all subscription). + /// + public ulong LastProcessedEventPosition { get; } - /// - /// Gets the end of the stream position (stream position for stream subscription of commit position for all subscription). - /// - public ulong EndOfStreamPosition { get; } + /// + /// Gets the end of the stream position (stream position for stream subscription of commit position for all subscription). + /// + public ulong EndOfStreamPosition { get; } - /// - /// Gets the percentage of events in the stream which have been processed (either by number of events or position in the transaction log). - /// - public decimal OverallPercentage => - LastProcessedEventPosition == 0 || EndOfStreamPosition == 0 - ? 0.0m - : (decimal)LastProcessedEventPosition / EndOfStreamPosition * 100; + /// + /// Gets the percentage of events in the stream which have been processed (either by number of events or position in the transaction log). + /// + public decimal OverallPercentage => + LastProcessedEventPosition == 0 || EndOfStreamPosition == 0 + ? 0.0m + : (decimal)LastProcessedEventPosition / EndOfStreamPosition * 100; - /// - /// Gets the percentage of events in the stream which require catching up on, which have been processed (either by number of events or position in the transaction log). - /// - public decimal CatchupPercentage => - LastProcessedEventPosition - StartPosition == 0 || EndOfStreamPosition - StartPosition == 0 - ? 0.0m - : ((decimal)LastProcessedEventPosition - StartPosition) / (EndOfStreamPosition - StartPosition) * 100; + /// + /// Gets the percentage of events in the stream which require catching up on, which have been processed (either by number of events or position in the transaction log). + /// + public decimal CatchupPercentage => + LastProcessedEventPosition - StartPosition == 0 || EndOfStreamPosition - StartPosition == 0 + ? 0.0m + : ((decimal)LastProcessedEventPosition - StartPosition) / (EndOfStreamPosition - StartPosition) * 100; - /// - /// Generates a string describing the state of the stream catch up progress. - /// - /// A string describing the state of the stream catch up progress. - public override string ToString() - { - return - $"[{StreamName}] Overall Pos: {OverallPercentage:0.#}% ({LastProcessedEventPosition}/{EndOfStreamPosition}), Caught up: {CatchupPercentage:0.#}% ({LastProcessedEventPosition - StartPosition}/{EndOfStreamPosition - StartPosition})"; - } + /// + /// Generates a string describing the state of the stream catch up progress. + /// + /// A string describing the state of the stream catch up progress. + public override string ToString() + { + return + $"[{StreamName}] Overall Pos: {OverallPercentage:0.#}% ({LastProcessedEventPosition}/{EndOfStreamPosition}), Caught up: {CatchupPercentage:0.#}% ({LastProcessedEventPosition - StartPosition}/{EndOfStreamPosition - StartPosition})"; } } diff --git a/src/eventstore/CheckpointingWrappingDispatcher.cs b/src/eventstore/CheckpointingWrappingDispatcher.cs index 6d11787..b7a1372 100644 --- a/src/eventstore/CheckpointingWrappingDispatcher.cs +++ b/src/eventstore/CheckpointingWrappingDispatcher.cs @@ -1,79 +1,76 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using global::EventStore.Client; +using Newtonsoft.Json.Linq; + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// A wrapping event dispatcher which keeps track of a checkpoint, and whether the dispatched event has been previously processed or not. +/// +public class CheckpointingWrappingDispatcher : KurrentAggregateEventDispatcher { - using System; - using System.Text; - using CorshamScience.MessageDispatch.Core; - using global::EventStore.Client; - using Newtonsoft.Json.Linq; + private readonly WriteThroughFileCheckpoint _checkpoint; + private readonly long _startupCheckpointValue; /// - /// A wrapping event dispatcher which keeps track of a checkpoint, and whether the dispatched event has been previously processed or not. + /// Initializes a new instance of the class. /// - public class CheckpointingWrappingDispatcher : KurrentAggregateEventDispatcher + /// The file to write a checkpoint to. + /// The initial value to write. + /// The handler methods for processing messages with. + /// The metadata key. + public CheckpointingWrappingDispatcher( + string checkpointFilePath, + long initValue, + IMessageHandlerLookup handlers, + string metadataKey = null) + : base(handlers, null, metadataKey) { - private readonly WriteThroughFileCheckpoint _checkpoint; - private readonly long _startupCheckpointValue; + _checkpoint = new WriteThroughFileCheckpoint(checkpointFilePath, initValue); + _startupCheckpointValue = _checkpoint.Read(); + } - /// - /// Initializes a new instance of the class. - /// - /// The file to write a checkpoint to. - /// The initial value to write. - /// The handler methods for processing messages with. - /// The metadata key. - public CheckpointingWrappingDispatcher( - string checkpointFilePath, - long initValue, - IMessageHandlerLookup handlers, - string metadataKey = null) - : base(handlers, null, metadataKey) - { - _checkpoint = new WriteThroughFileCheckpoint(checkpointFilePath, initValue); - _startupCheckpointValue = _checkpoint.Read(); - } + /// + public override void Dispatch(ResolvedEvent message) + { + base.Dispatch(message); - /// - public override void Dispatch(ResolvedEvent message) + var previouslyProcessed = message.OriginalEventNumber.ToInt64() <= _startupCheckpointValue; + + if (previouslyProcessed) { - base.Dispatch(message); + return; + } - var previouslyProcessed = message.OriginalEventNumber.ToInt64() <= _startupCheckpointValue; + _checkpoint.Write(message.OriginalEventNumber.ToInt64()); + } - if (previouslyProcessed) - { - return; - } + /// + protected override bool TryDeserialize( + Type messageType, + ResolvedEvent rawMessage, + out object deserialized) + { + deserialized = null!; - _checkpoint.Write(message.OriginalEventNumber.ToInt64()); - } + var previouslyProcessed = rawMessage.OriginalEventNumber.ToInt64() <= _startupCheckpointValue; - /// - protected override bool TryDeserialize( - Type messageType, - ResolvedEvent rawMessage, - out object deserialized) + try { - deserialized = null!; - - var previouslyProcessed = rawMessage.OriginalEventNumber.ToInt64() <= _startupCheckpointValue; + var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); + var @event = JObject.Parse(jsonString); - try - { - var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); - var @event = JObject.Parse(jsonString); + deserialized = new EventWrapper(@event, previouslyProcessed); - deserialized = new EventWrapper(@event, previouslyProcessed); - - return deserialized != null; - } - catch (Exception) - { - return false; - } + return deserialized != null; + } + catch (Exception) + { + return false; } } } diff --git a/src/eventstore/EventWrapper.cs b/src/eventstore/EventWrapper.cs index ee4b18b..dea5d6e 100644 --- a/src/eventstore/EventWrapper.cs +++ b/src/eventstore/EventWrapper.cs @@ -1,38 +1,35 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using Newtonsoft.Json.Linq; + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// Represents a wrapper for an event. +/// +public class EventWrapper { - using Newtonsoft.Json.Linq; - /// - /// Represents a wrapper for an event. + /// Initializes a new instance of the class. /// - public class EventWrapper + /// The event to wrap. + /// A value indicating whether the event has been previously processed. + public EventWrapper(JObject @event, bool previouslyProcessed) { - /// - /// Initializes a new instance of the class. - /// - /// The event to wrap. - /// A value indicating whether the event has been previously processed. - public EventWrapper(JObject @event, bool previouslyProcessed) - { - Event = @event; - PreviouslyProcessed = previouslyProcessed; - } + Event = @event; + PreviouslyProcessed = previouslyProcessed; + } - /// - /// Gets a value indicating whether this event has been previously processed before. - /// - public bool PreviouslyProcessed { get; } + /// + /// Gets a value indicating whether this event has been previously processed before. + /// + public bool PreviouslyProcessed { get; } - /// - /// Gets the event data. - /// - public JObject Event { get; } = null!; + /// + /// Gets the event data. + /// + public JObject Event { get; } = null!; - /// - public override string ToString() => $"Event: {Event}, PreviouslyProcessed: {PreviouslyProcessed}"; - } + /// + public override string ToString() => $"Event: {Event}, PreviouslyProcessed: {PreviouslyProcessed}"; } diff --git a/src/eventstore/GlobalSuppressions.cs b/src/eventstore/GlobalSuppressions.cs index 98b75e5..dd4c5bc 100644 --- a/src/eventstore/GlobalSuppressions.cs +++ b/src/eventstore/GlobalSuppressions.cs @@ -1,5 +1,3 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// - +// Copyright (c) Pharmaxo. All rights reserved. + [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "The file was taken from the eventstore codebase so it is not valid to add a copyright header.", Scope = "namespace", Target = "~N:CorshamScience.MessageDispatch.EventStore")] diff --git a/src/eventstore/KurrentAggregateEventDispatcher.cs b/src/eventstore/KurrentAggregateEventDispatcher.cs index 433a04d..7ff4542 100644 --- a/src/eventstore/KurrentAggregateEventDispatcher.cs +++ b/src/eventstore/KurrentAggregateEventDispatcher.cs @@ -1,125 +1,124 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using global::EventStore.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// +/// A deserializing event dispatcher for events produced by CorshamScience.AggregatRepository. +/// +// ReSharper disable once UnusedMember.Global +public class KurrentAggregateEventDispatcher : DeserializingMessageDispatcher { - using System; - using System.Collections.Generic; - using System.Text; - using CorshamScience.MessageDispatch.Core; - using global::EventStore.Client; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + private readonly JsonSerializerSettings _serializerSettings; + private readonly Dictionary _typeCache = new Dictionary(); + private readonly string _metadataKey; + +#pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// A deserializing event dispatcher for events produced by CorshamScience.AggregatRepository. + /// Initializes a new instance of the class. /// - // ReSharper disable once UnusedMember.Global - public class KurrentAggregateEventDispatcher : DeserializingMessageDispatcher + /// The handler methods for processing messages with. + /// Determines the settings for the JSON serialization of events. + /// Optional parameter for a metadata key default is ClrType + // ReSharper disable once UnusedMember.Global + public KurrentAggregateEventDispatcher( + IMessageHandlerLookup handlers, + JsonSerializerSettings serializerSettings = null, + string metadataKey = null) + : base(handlers) + { + _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); + _metadataKey = metadataKey ?? "ClrType"; + } +#pragma warning restore SA1648 // inheritdoc should be used with inheriting class + + /// + protected override bool TryGetMessageType(ResolvedEvent rawMessage, out Type type) { - private readonly JsonSerializerSettings _serializerSettings; - private readonly Dictionary _typeCache = new Dictionary(); - private readonly string _metadataKey; + type = null; -#pragma warning disable SA1648 // inheritdoc should be used with inheriting class - /// - /// - /// Initializes a new instance of the class. - /// - /// The handler methods for processing messages with. - /// Determines the settings for the JSON serialization of events. - // ReSharper disable once UnusedMember.Global - public KurrentAggregateEventDispatcher( - IMessageHandlerLookup handlers, - JsonSerializerSettings serializerSettings = null, - string metadataKey = null) - : base(handlers) - { - _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); - _metadataKey = metadataKey ?? "ClrType"; + // optimization: don't even bother trying to deserialize metadata for system events + if (rawMessage.Event.EventType.StartsWith("$") || rawMessage.Event.Metadata.Length == 0) + { + return false; } -#pragma warning restore SA1648 // inheritdoc should be used with inheriting class - /// - protected override bool TryGetMessageType(ResolvedEvent rawMessage, out Type type) + try { - type = null; + IDictionary metadata = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Metadata.Span.ToArray())); - // optimization: don't even bother trying to deserialize metadata for system events - if (rawMessage.Event.EventType.StartsWith("$") || rawMessage.Event.Metadata.Length == 0) + if (!metadata.ContainsKey(_metadataKey)) { return false; } - try - { - IDictionary metadata = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Metadata.Span.ToArray())); + string typeString = (string)metadata[_metadataKey]; - if (!metadata.ContainsKey(_metadataKey)) - { - return false; - } - - string typeString = (string)metadata[_metadataKey]; - - if (!_typeCache.TryGetValue(typeString, out var cached)) + if (!_typeCache.TryGetValue(typeString, out var cached)) + { + try { - try - { - cached = Type.GetType( - typeString, - (assemblyName) => - { - assemblyName.Version = null; - return System.Reflection.Assembly.Load(assemblyName); - }, - null, - true, - true); - } - catch (Exception) - { - cached = typeof(TypeNotFound); - } - - _typeCache.Add(typeString, cached); + cached = Type.GetType( + typeString, + (assemblyName) => + { + assemblyName.Version = null; + return System.Reflection.Assembly.Load(assemblyName); + }, + null, + true, + true); } - - if (cached?.Name.Equals("TypeNotFound") ?? false) + catch (Exception) { - return false; + cached = typeof(TypeNotFound); } - type = cached; - return true; + _typeCache.Add(typeString, cached); } - catch (Exception) + + if (cached?.Name.Equals("TypeNotFound") ?? false) { return false; } - } - /// - protected override bool TryDeserialize(Type messageType, ResolvedEvent rawMessage, out object deserialized) + type = cached; + return true; + } + catch (Exception) { - deserialized = null; - - try - { - var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); - deserialized = JsonConvert.DeserializeObject(jsonString, messageType, _serializerSettings); - return deserialized != null; - } - catch (Exception) - { - return false; - } + return false; } + } - private class TypeNotFound + /// + protected override bool TryDeserialize(Type messageType, ResolvedEvent rawMessage, out object deserialized) + { + deserialized = null; + + try + { + var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); + deserialized = JsonConvert.DeserializeObject(jsonString, messageType, _serializerSettings); + return deserialized != null; + } + catch (Exception) { + return false; } } + + private class TypeNotFound + { + } } diff --git a/src/eventstore/KurrentJObjectDispatcher.cs b/src/eventstore/KurrentJObjectDispatcher.cs index 9175ea3..b74cbac 100644 --- a/src/eventstore/KurrentJObjectDispatcher.cs +++ b/src/eventstore/KurrentJObjectDispatcher.cs @@ -1,56 +1,53 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using global::EventStore.Client; +using Newtonsoft.Json.Linq; + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// +/// A message dispatcher that deserializes messages to a JObject upon dispatch. +/// +// ReSharper disable once UnusedMember.Global +public class KurrentJObjectDispatcher : DeserializingMessageDispatcher { - using System; - using System.Text; - using CorshamScience.MessageDispatch.Core; - using global::EventStore.Client; - using Newtonsoft.Json.Linq; - +#pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// A message dispatcher that deserializes messages to a JObject upon dispatch. + /// Initializes a new instance of the class. /// + /// Lookups for the handlers which the class can use to process messages. // ReSharper disable once UnusedMember.Global - public class KurrentJObjectDispatcher : DeserializingMessageDispatcher + public KurrentJObjectDispatcher(IMessageHandlerLookup handlers) + : base(handlers) { -#pragma warning disable SA1648 // inheritdoc should be used with inheriting class - /// - /// - /// Initializes a new instance of the class. - /// - /// Lookups for the handlers which the class can use to process messages. - // ReSharper disable once UnusedMember.Global - public KurrentJObjectDispatcher(IMessageHandlerLookup handlers) - : base(handlers) - { - } + } #pragma warning restore SA1648 // inheritdoc should be used with inheriting class - /// - protected override bool TryGetMessageType(ResolvedEvent rawMessage, out string type) + /// + protected override bool TryGetMessageType(ResolvedEvent rawMessage, out string type) + { + type = rawMessage.Event.EventType; + return true; + } + + /// + protected override bool TryDeserialize(string messageType, ResolvedEvent rawMessage, out object deserialized) + { + deserialized = null; + + try { - type = rawMessage.Event.EventType; + deserialized = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray())); return true; } - - /// - protected override bool TryDeserialize(string messageType, ResolvedEvent rawMessage, out object deserialized) + catch (Exception) { - deserialized = null; - - try - { - deserialized = JObject.Parse(Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray())); - return true; - } - catch (Exception) - { - return false; - } + return false; } } } diff --git a/src/eventstore/KurrentSubscriber.cs b/src/eventstore/KurrentSubscriber.cs index 69ad064..ad80fe2 100644 --- a/src/eventstore/KurrentSubscriber.cs +++ b/src/eventstore/KurrentSubscriber.cs @@ -1,551 +1,548 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CorshamScience.MessageDispatch.Core; +using global::EventStore.Client; +using Microsoft.Extensions.Logging; + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// Subscriber for event store. +/// +public class KurrentSubscriber { - using System; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; - using CorshamScience.MessageDispatch.Core; - using global::EventStore.Client; - using Microsoft.Extensions.Logging; + private const string AllStreamName = "$all"; + private const uint CheckpointInterval = 1; + private readonly WriteThroughFileCheckpoint _checkpoint; + private readonly object _subscriptionLock = new object(); + + private EventStoreClient _eventStoreClient; + private ulong? _startingPosition; + private StreamSubscription _subscription; + private string _streamName; + private bool _liveOnly; + private bool _isSubscribed; + private bool _isSubscriptionLive; + private bool _subscribeToAll; + private ulong? _lastProcessedEventPosition; + private ulong _actualEndOfStreamPosition; + private ulong _liveEventThreshold; + private ulong _liveThresholdPosition; + private DateTime _lastStreamPositionTimestamp; + private Func _setLastPositions; + + private IDispatcher _dispatcher; + private ILogger _logger; + + private KurrentSubscriber( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + string streamName, + ILogger logger, + ulong? startingPosition, + ulong liveEventThreshold) + => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); + + private KurrentSubscriber( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + ILogger logger, + string streamName, + string checkpointFilePath, + ulong liveEventThreshold) + { + _checkpoint = new WriteThroughFileCheckpoint(checkpointFilePath, -1); + var initialCheckpointPosition = _checkpoint.Read(); + ulong? startingPosition = null; + + if (initialCheckpointPosition != -1) + { + startingPosition = (ulong)initialCheckpointPosition; + } + + Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); + } + + private KurrentSubscriber( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + string streamName, + ILogger logger, + ulong liveEventThreshold) + => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, liveOnly: true); /// - /// Subscriber for event store. + /// Gets a new catchup progress object. /// - public class KurrentSubscriber + // ReSharper disable once UnusedMember.Global + public CatchupProgress CatchupProgress { - private const string AllStreamName = "$all"; - private const uint CheckpointInterval = 1; - private readonly WriteThroughFileCheckpoint _checkpoint; - private readonly object _subscriptionLock = new object(); - - private EventStoreClient _eventStoreClient; - private ulong? _startingPosition; - private StreamSubscription _subscription; - private string _streamName; - private bool _liveOnly; - private bool _isSubscribed; - private bool _isSubscriptionLive; - private bool _subscribeToAll; - private ulong? _lastProcessedEventPosition; - private ulong _actualEndOfStreamPosition; - private ulong _liveEventThreshold; - private ulong _liveThresholdPosition; - private DateTime _lastStreamPositionTimestamp; - private Func _setLastPositions; - - private IDispatcher _dispatcher; - private ILogger _logger; - - private KurrentSubscriber( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - string streamName, - ILogger logger, - ulong? startingPosition, - ulong liveEventThreshold) - => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); - - private KurrentSubscriber( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - ILogger logger, - string streamName, - string checkpointFilePath, - ulong liveEventThreshold) + get { - _checkpoint = new WriteThroughFileCheckpoint(checkpointFilePath, -1); - var initialCheckpointPosition = _checkpoint.Read(); - ulong? startingPosition = null; + var lastStreamPosition = GetLastPositions().Result; + + return new CatchupProgress( + _lastProcessedEventPosition ?? 0, + _streamName, + lastStreamPosition.actualEndOfStreamPosition, + _startingPosition ?? 0, + _subscribeToAll); + } + } - if (initialCheckpointPosition != -1) + /// + /// Gets a value indicating whether the view model is ready or not. + /// + /// Returns true if catchup is within threshold. + public bool IsLive + { + get + { + // if we aren't subscribed, it doesn't count as live + if (!_isSubscribed) { - startingPosition = (ulong)initialCheckpointPosition; + return false; } - Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); - } - - private KurrentSubscriber( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - string streamName, - ILogger logger, - ulong liveEventThreshold) - => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, liveOnly: true); - - /// - /// Gets a new catchup progress object. - /// - // ReSharper disable once UnusedMember.Global - public CatchupProgress CatchupProgress - { - get + // if we are still subscribed, and we have ever been live, we are still live + if (_isSubscribed && _isSubscriptionLive) { - var lastStreamPosition = GetLastPositions().Result; - - return new CatchupProgress( - _lastProcessedEventPosition ?? 0, - _streamName, - lastStreamPosition.actualEndOfStreamPosition, - _startingPosition ?? 0, - _subscribeToAll); + return true; } + + var lastStreamPosition = GetLastPositions().Result; + + _isSubscriptionLive = (_liveOnly && _lastProcessedEventPosition is null && _isSubscribed) || + _lastProcessedEventPosition >= lastStreamPosition.liveThresholdPosition; + return _isSubscriptionLive; } + } - /// - /// Gets a value indicating whether the view model is ready or not. - /// - /// Returns true if catchup is within threshold. - public bool IsLive - { - get - { - // if we aren't subscribed, it doesn't count as live - if (!_isSubscribed) - { - return false; - } + /// + /// Creates a live eventstore subscription. + /// + /// Eventstore connection. + /// Dispatcher. + /// Stream name to push events into. + /// Logger. + /// Proximity to end of stream before subscription considered live. + /// A new EventStoreSubscriber object. + // ReSharper disable once UnusedMember.Global + public static KurrentSubscriber CreateLiveSubscription( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + string streamName, + ILogger logger, + ulong liveEventThreshold = 10) + => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); - // if we are still subscribed, and we have ever been live, we are still live - if (_isSubscribed && _isSubscriptionLive) - { - return true; - } + /// + /// Creates an eventstore catchup subscription using a checkpoint file. + /// + /// Eventstore connection. + /// Dispatcher. + /// Stream name to push events into. + /// Logger. + /// Path of the checkpoint file. + /// Proximity to end of stream before subscription considered live. + /// A new EventStoreSubscriber object. + // ReSharper disable once UnusedMember.Global + public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + string streamName, + ILogger logger, + string checkpointFilePath, + ulong liveEventThreshold = 10) + => new KurrentSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); - var lastStreamPosition = GetLastPositions().Result; + /// + /// Creates an eventstore catchup subscription from a position. + /// + /// Eventstore connection. + /// Dispatcher. + /// Stream name to push events into. + /// Logger. + /// Starting Position. + /// Proximity to end of stream before subscription considered live. + /// A new EventStoreSubscriber object. + // ReSharper disable once UnusedMember.Global + public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + string streamName, + ILogger logger, + ulong? startingPosition, + ulong liveEventThreshold = 10) + => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); - _isSubscriptionLive = (_liveOnly && _lastProcessedEventPosition is null && _isSubscribed) || - _lastProcessedEventPosition >= lastStreamPosition.liveThresholdPosition; - return _isSubscriptionLive; - } - } + /// + /// Creates an eventstore catchup subscription that is subscribed to all from the start. + /// + /// Eventstore connection. + /// Dispatcher. + /// Logger. + /// Proximity to end of stream before subscription considered live. + /// A new EventStoreSubscriber object. + // ReSharper disable once UnusedMember.Global + public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + ILogger logger, + ulong liveEventThreshold = 10) + => new KurrentSubscriber( + eventStoreClient, + dispatcher, + AllStreamName, + logger, + liveEventThreshold); - /// - /// Creates a live eventstore subscription. - /// - /// Eventstore connection. - /// Dispatcher. - /// Stream name to push events into. - /// Logger. - /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. - // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateLiveSubscription( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - string streamName, - ILogger logger, - ulong liveEventThreshold = 10) - => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); - - /// - /// Creates an eventstore catchup subscription using a checkpoint file. - /// - /// Eventstore connection. - /// Dispatcher. - /// Stream name to push events into. - /// Logger. - /// Path of the checkpoint file. - /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. - // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - string streamName, - ILogger logger, - string checkpointFilePath, - ulong liveEventThreshold = 10) - => new KurrentSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); - - /// - /// Creates an eventstore catchup subscription from a position. - /// - /// Eventstore connection. - /// Dispatcher. - /// Stream name to push events into. - /// Logger. - /// Starting Position. - /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. - // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - string streamName, - ILogger logger, - ulong? startingPosition, - ulong liveEventThreshold = 10) - => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); - - /// - /// Creates an eventstore catchup subscription that is subscribed to all from the start. - /// - /// Eventstore connection. - /// Dispatcher. - /// Logger. - /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. - // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - ILogger logger, - ulong liveEventThreshold = 10) - => new KurrentSubscriber( - eventStoreClient, - dispatcher, - AllStreamName, - logger, - liveEventThreshold); + /// + /// Creates an eventstore catchup subscription that is subscribed to all from a position. + /// + /// Eventstore connection. + /// Dispatcher. + /// Logger. + /// Starting Position. + /// Proximity to end of stream before subscription considered live. + /// A new EventStoreSubscriber object. + // ReSharper disable once UnusedMember.Global + public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + ILogger logger, + ulong? startingPosition, + ulong liveEventThreshold = 10) + => new KurrentSubscriber( + eventStoreClient, + dispatcher, + AllStreamName, + logger, + startingPosition, + liveEventThreshold); - /// - /// Creates an eventstore catchup subscription that is subscribed to all from a position. - /// - /// Eventstore connection. - /// Dispatcher. - /// Logger. - /// Starting Position. - /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. - // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - ILogger logger, - ulong? startingPosition, - ulong liveEventThreshold = 10) - => new KurrentSubscriber( + /// + /// Creates an eventstore catchup subscription subscribed to all using a checkpoint file. + /// + /// Eventstore connection. + /// Dispatcher. + /// Logger. + /// Path of the checkpoint file. + /// Proximity to end of stream before subscription considered live. + /// A new EventStoreSubscriber object. + // ReSharper disable once UnusedMember.Global + public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( + EventStoreClient eventStoreClient, + IDispatcher dispatcher, + ILogger logger, + string checkpointFilePath, + ulong liveEventThreshold = 10) + => new KurrentSubscriber( eventStoreClient, dispatcher, - AllStreamName, logger, - startingPosition, + AllStreamName, + checkpointFilePath, liveEventThreshold); - /// - /// Creates an eventstore catchup subscription subscribed to all using a checkpoint file. - /// - /// Eventstore connection. - /// Dispatcher. - /// Logger. - /// Path of the checkpoint file. - /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. - // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( - EventStoreClient eventStoreClient, - IDispatcher dispatcher, - ILogger logger, - string checkpointFilePath, - ulong liveEventThreshold = 10) - => new KurrentSubscriber( - eventStoreClient, - dispatcher, - logger, - AllStreamName, - checkpointFilePath, - liveEventThreshold); - - /// - /// Start the subscriber. - /// - // ReSharper disable once MemberCanBePrivate.Global - public void Start() + /// + /// Start the subscriber. + /// + // ReSharper disable once MemberCanBePrivate.Global + public void Start() + { + while (true) { - while (true) - { - _isSubscribed = false; + _isSubscribed = false; - try - { - Monitor.Enter(_subscriptionLock); + try + { + Monitor.Enter(_subscriptionLock); - KillSubscription(); + KillSubscription(); - // No synchronization context is needed to disable synchronization context. - // That enables running asynchronous method not causing deadlocks. - // As this is a background process then we don't need to have async context here. - using (NoSynchronizationContextScope.Enter()) + // No synchronization context is needed to disable synchronization context. + // That enables running asynchronous method not causing deadlocks. + // As this is a background process then we don't need to have async context here. + using (NoSynchronizationContextScope.Enter()) + { + var filterOptions = new SubscriptionFilterOptions( + EventTypeFilter.ExcludeSystemEvents(), + CheckpointInterval, + checkpointReached: CheckpointReached); + const bool resolveLinkTos = true; + + Task Appeared( + StreamSubscription streamSubscription, + ResolvedEvent e, + CancellationToken cancellationToken) => + EventAppeared(e); + + switch (_liveOnly) { - var filterOptions = new SubscriptionFilterOptions( - EventTypeFilter.ExcludeSystemEvents(), - CheckpointInterval, - checkpointReached: CheckpointReached); - const bool resolveLinkTos = true; - - Task Appeared( - StreamSubscription streamSubscription, - ResolvedEvent e, - CancellationToken cancellationToken) => - EventAppeared(e); - - switch (_liveOnly) - { - case true when !_subscribeToAll: - _subscription = _eventStoreClient.SubscribeToStreamAsync( - _streamName, - FromStream.End, + case true when !_subscribeToAll: + _subscription = _eventStoreClient.SubscribeToStreamAsync( + _streamName, + FromStream.End, + Appeared, + resolveLinkTos, + SubscriptionDropped).Result; + break; + case false when !_subscribeToAll: + { + var fromStream = _startingPosition.HasValue ? + FromStream.After(new StreamPosition(_startingPosition.Value)) : + FromStream.Start; + + _subscription = _eventStoreClient.SubscribeToStreamAsync( + _streamName, + fromStream, + Appeared, + resolveLinkTos, + SubscriptionDropped).Result; + break; + } + + case true when _subscribeToAll: + _subscription = _eventStoreClient.SubscribeToAllAsync( + FromAll.End, Appeared, resolveLinkTos, - SubscriptionDropped).Result; - break; - case false when !_subscribeToAll: - { - var fromStream = _startingPosition.HasValue ? - FromStream.After(new StreamPosition(_startingPosition.Value)) : - FromStream.Start; - - _subscription = _eventStoreClient.SubscribeToStreamAsync( - _streamName, - fromStream, - Appeared, - resolveLinkTos, - SubscriptionDropped).Result; - break; - } - - case true when _subscribeToAll: - _subscription = _eventStoreClient.SubscribeToAllAsync( - FromAll.End, - Appeared, - resolveLinkTos, - SubscriptionDropped, - filterOptions) - .Result; - break; - case false when _subscribeToAll: - var fromAll = _startingPosition.HasValue ? - FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : - FromAll.Start; - - _subscription = _eventStoreClient.SubscribeToAllAsync( - fromAll, - Appeared, - resolveLinkTos, - SubscriptionDropped, - filterOptions) - .Result; - break; - } + SubscriptionDropped, + filterOptions) + .Result; + break; + case false when _subscribeToAll: + var fromAll = _startingPosition.HasValue ? + FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : + FromAll.Start; + + _subscription = _eventStoreClient.SubscribeToAllAsync( + fromAll, + Appeared, + resolveLinkTos, + SubscriptionDropped, + filterOptions) + .Result; + break; } - - _isSubscribed = true; - _logger.LogInformation("Subscribed to '{StreamName}'", _streamName); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to resubscribe to '{StreamName}' dropped with '{ExceptionMessage}{ExceptionStackTrace}'", _streamName, ex.Message, ex.StackTrace); - } - finally - { - Monitor.Exit(_subscriptionLock); } - if (_isSubscribed) - { - break; - } - - // Sleep between reconnections to not flood the database or not kill the CPU with infinite loop - // Randomness added to reduce the chance of multiple subscriptions trying to reconnect at the same time - Thread.Sleep(1000 + new Random((int)DateTime.UtcNow.Ticks).Next(1000)); - } - } - - /// - /// Shut down the subscription. - /// - // ReSharper disable once UnusedMember.Global - public void ShutDown() - { - lock (_subscriptionLock) - { - KillSubscription(); + _isSubscribed = true; + _logger.LogInformation("Subscribed to '{StreamName}'", _streamName); } - } - - private void Init( - EventStoreClient connection, - IDispatcher dispatcher, - string streamName, - ILogger logger, - ulong liveEventThreshold, - ulong? startingPosition = null, - bool liveOnly = false) - { - _logger = logger; - _startingPosition = startingPosition; - _lastProcessedEventPosition = startingPosition; - _dispatcher = dispatcher; - _streamName = streamName; - _eventStoreClient = connection; - _liveOnly = liveOnly; - _subscribeToAll = streamName == AllStreamName; - _liveEventThreshold = liveEventThreshold; - _liveThresholdPosition = StreamPosition.End; - _lastStreamPositionTimestamp = DateTime.MinValue; - - _setLastPositions = _subscribeToAll - ? async () => - { - var eventsWithinThreshold = await _eventStoreClient.ReadAllAsync( - Direction.Backwards, - Position.End, - maxCount: (long)_liveEventThreshold) - .ToListAsync(); - - _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEvent.Position.CommitPosition; - _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEvent.Position.CommitPosition; - } - : async () => - { - var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( - Direction.Backwards, - _streamName, - StreamPosition.End, - maxCount: (long)_liveEventThreshold, - resolveLinkTos: false) - .ToListAsync(); - - _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); - _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); - }; - } - - private void SubscriptionDropped(StreamSubscription eventStoreCatchUpSubscription, SubscriptionDroppedReason subscriptionDropReason, Exception ex) - { - if (ex != null) + catch (Exception ex) { - _logger.LogInformation(ex, "Event Store subscription dropped {0}", subscriptionDropReason.ToString()); + _logger.LogWarning(ex, "Failed to resubscribe to '{StreamName}' dropped with '{ExceptionMessage}{ExceptionStackTrace}'", _streamName, ex.Message, ex.StackTrace); } - else + finally { - _logger.LogInformation("Event Store subscription dropped {0}", subscriptionDropReason.ToString()); + Monitor.Exit(_subscriptionLock); } - if (subscriptionDropReason == SubscriptionDroppedReason.Disposed) + if (_isSubscribed) { - _logger.LogInformation("Not attempting to restart subscription was disposed. Subscription is dead."); - return; + break; } - _isSubscribed = false; - - // if the subscription drops, set its 'liveness' to false - _isSubscriptionLive = false; - _startingPosition = _lastProcessedEventPosition; - Start(); + // Sleep between reconnections to not flood the database or not kill the CPU with infinite loop + // Randomness added to reduce the chance of multiple subscriptions trying to reconnect at the same time + Thread.Sleep(1000 + new Random((int)DateTime.UtcNow.Ticks).Next(1000)); } + } - private Task EventAppeared(ResolvedEvent resolvedEvent) + /// + /// Shut down the subscription. + /// + // ReSharper disable once UnusedMember.Global + public void ShutDown() + { + lock (_subscriptionLock) { - ProcessEvent(resolvedEvent); - - var lastProcessedEventPosition = GetLastProcessedPosition(resolvedEvent); + KillSubscription(); + } + } - if (_liveOnly && _lastProcessedEventPosition is null) + private void Init( + EventStoreClient connection, + IDispatcher dispatcher, + string streamName, + ILogger logger, + ulong liveEventThreshold, + ulong? startingPosition = null, + bool liveOnly = false) + { + _logger = logger; + _startingPosition = startingPosition; + _lastProcessedEventPosition = startingPosition; + _dispatcher = dispatcher; + _streamName = streamName; + _eventStoreClient = connection; + _liveOnly = liveOnly; + _subscribeToAll = streamName == AllStreamName; + _liveEventThreshold = liveEventThreshold; + _liveThresholdPosition = StreamPosition.End; + _lastStreamPositionTimestamp = DateTime.MinValue; + + _setLastPositions = _subscribeToAll + ? async () => { - _startingPosition = lastProcessedEventPosition; - } - - _lastProcessedEventPosition = lastProcessedEventPosition; + var eventsWithinThreshold = await _eventStoreClient.ReadAllAsync( + Direction.Backwards, + Position.End, + maxCount: (long)_liveEventThreshold) + .ToListAsync(); + + _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEvent.Position.CommitPosition; + _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEvent.Position.CommitPosition; + } + : async () => + { + var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( + Direction.Backwards, + _streamName, + StreamPosition.End, + maxCount: (long)_liveEventThreshold, + resolveLinkTos: false) + .ToListAsync(); + + _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); + _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); + }; + } - return Task.CompletedTask; + private void SubscriptionDropped(StreamSubscription eventStoreCatchUpSubscription, SubscriptionDroppedReason subscriptionDropReason, Exception ex) + { + if (ex != null) + { + _logger.LogInformation(ex, "Event Store subscription dropped {0}", subscriptionDropReason.ToString()); + } + else + { + _logger.LogInformation("Event Store subscription dropped {0}", subscriptionDropReason.ToString()); } - private void ProcessEvent(ResolvedEvent resolvedEvent) + if (subscriptionDropReason == SubscriptionDroppedReason.Disposed) { - if (resolvedEvent.Event == null || resolvedEvent.Event.EventType.StartsWith("$")) - { - return; - } + _logger.LogInformation("Not attempting to restart subscription was disposed. Subscription is dead."); + return; + } - try - { - _dispatcher.Dispatch(resolvedEvent); + _isSubscribed = false; - var checkpointNumber = GetLastProcessedPosition(resolvedEvent); + // if the subscription drops, set its 'liveness' to false + _isSubscriptionLive = false; + _startingPosition = _lastProcessedEventPosition; + Start(); + } - WriteCheckpoint(checkpointNumber); - _logger.LogTrace( - "Event dispatched from Eventstore subscriber ({0}/{1})", - resolvedEvent.Event.EventStreamId, - resolvedEvent.Event.EventNumber); - } - catch (Exception ex) - { - _logger.LogError( - ex, - "Error dispatching event from Event Store subscriber ({0}/{1})", - resolvedEvent.Event.EventStreamId, - resolvedEvent.Event.EventNumber); - } - } + private Task EventAppeared(ResolvedEvent resolvedEvent) + { + ProcessEvent(resolvedEvent); + + var lastProcessedEventPosition = GetLastProcessedPosition(resolvedEvent); - private ulong GetLastProcessedPosition(ResolvedEvent resolvedEvent) + if (_liveOnly && _lastProcessedEventPosition is null) { - return _subscribeToAll - ? resolvedEvent.OriginalEvent.Position.CommitPosition - : resolvedEvent.OriginalEventNumber.ToUInt64(); + _startingPosition = lastProcessedEventPosition; } - private void WriteCheckpoint(ulong checkpointNumber) - { - if (_checkpoint == null) - { - return; - } + _lastProcessedEventPosition = lastProcessedEventPosition; - if (checkpointNumber > long.MaxValue) - { - _logger.LogError( - "Value is too large to be checkpointed. Checkpoint number {CheckpointNumber}", - checkpointNumber); - return; - } + return Task.CompletedTask; + } - _checkpoint.Write((long)checkpointNumber); - _logger.LogTrace("Checkpoint written. Checkpoint number {CheckpointNumber}", checkpointNumber); + private void ProcessEvent(ResolvedEvent resolvedEvent) + { + if (resolvedEvent.Event == null || resolvedEvent.Event.EventType.StartsWith("$")) + { + return; } - private async Task<(ulong liveThresholdPosition, ulong actualEndOfStreamPosition)> GetLastPositions() + try { - var streamPositionIsStale = (DateTime.UtcNow - _lastStreamPositionTimestamp) > TimeSpan.FromSeconds(10); + _dispatcher.Dispatch(resolvedEvent); - if (_isSubscribed && streamPositionIsStale) - { - await _setLastPositions(); - _lastStreamPositionTimestamp = DateTime.UtcNow; - } + var checkpointNumber = GetLastProcessedPosition(resolvedEvent); - return (_liveThresholdPosition, _actualEndOfStreamPosition); + WriteCheckpoint(checkpointNumber); + _logger.LogTrace( + "Event dispatched from Eventstore subscriber ({0}/{1})", + resolvedEvent.Event.EventStreamId, + resolvedEvent.Event.EventNumber); } + catch (Exception ex) + { + _logger.LogError( + ex, + "Error dispatching event from Event Store subscriber ({0}/{1})", + resolvedEvent.Event.EventStreamId, + resolvedEvent.Event.EventNumber); + } + } - private void KillSubscription() + private ulong GetLastProcessedPosition(ResolvedEvent resolvedEvent) + { + return _subscribeToAll + ? resolvedEvent.OriginalEvent.Position.CommitPosition + : resolvedEvent.OriginalEventNumber.ToUInt64(); + } + + private void WriteCheckpoint(ulong checkpointNumber) + { + if (_checkpoint == null) { - if (_subscription != null) - { - _subscription.Dispose(); - _subscription = null; - } + return; + } - _isSubscribed = false; + if (checkpointNumber > long.MaxValue) + { + _logger.LogError( + "Value is too large to be checkpointed. Checkpoint number {CheckpointNumber}", + checkpointNumber); + return; } - private Task CheckpointReached( - StreamSubscription streamSubscription, - Position position, - CancellationToken cancellationToken) + _checkpoint.Write((long)checkpointNumber); + _logger.LogTrace("Checkpoint written. Checkpoint number {CheckpointNumber}", checkpointNumber); + } + + private async Task<(ulong liveThresholdPosition, ulong actualEndOfStreamPosition)> GetLastPositions() + { + var streamPositionIsStale = (DateTime.UtcNow - _lastStreamPositionTimestamp) > TimeSpan.FromSeconds(10); + + if (_isSubscribed && streamPositionIsStale) { - _lastProcessedEventPosition = position.CommitPosition; - WriteCheckpoint((ulong)_lastProcessedEventPosition); + await _setLastPositions(); + _lastStreamPositionTimestamp = DateTime.UtcNow; + } - return Task.CompletedTask; + return (_liveThresholdPosition, _actualEndOfStreamPosition); + } + + private void KillSubscription() + { + if (_subscription != null) + { + _subscription.Dispose(); + _subscription = null; } + + _isSubscribed = false; + } + + private Task CheckpointReached( + StreamSubscription streamSubscription, + Position position, + CancellationToken cancellationToken) + { + _lastProcessedEventPosition = position.CommitPosition; + WriteCheckpoint((ulong)_lastProcessedEventPosition); + + return Task.CompletedTask; } } diff --git a/src/eventstore/NoSynchronizationContextScope.cs b/src/eventstore/NoSynchronizationContextScope.cs index 3c2c6cd..1bb3b38 100644 --- a/src/eventstore/NoSynchronizationContextScope.cs +++ b/src/eventstore/NoSynchronizationContextScope.cs @@ -1,6 +1,7 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Threading; /* This file is taken from Event Store codebase https://github.com/EventStore/samples/blob/main/CQRS_Flow/.NET/Core/Core/Threading/NoSynchronizationContextScope.cs @@ -9,32 +10,28 @@ As such we should not add a Pharmaxo Scientific copyright file header */ // ReSharper disable InconsistentNaming #pragma warning disable CS8632, SA1600, SX1309 -namespace PharmaxoScientific.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore; + +internal static class NoSynchronizationContextScope { - using System; - using System.Threading; + public static Disposable Enter() + { + var context = SynchronizationContext.Current; + SynchronizationContext.SetSynchronizationContext(null); + return new Disposable(context); + } - internal static class NoSynchronizationContextScope + public struct Disposable : IDisposable { - public static Disposable Enter() - { - var context = SynchronizationContext.Current; - SynchronizationContext.SetSynchronizationContext(null); - return new Disposable(context); - } + private readonly SynchronizationContext? synchronizationContext; - public struct Disposable : IDisposable + public Disposable(SynchronizationContext? synchronizationContext) { - private readonly SynchronizationContext? synchronizationContext; - - public Disposable(SynchronizationContext? synchronizationContext) - { - this.synchronizationContext = synchronizationContext; - } - - public void Dispose() => - SynchronizationContext.SetSynchronizationContext(synchronizationContext); + this.synchronizationContext = synchronizationContext; } + + public void Dispose() => + SynchronizationContext.SetSynchronizationContext(synchronizationContext); } } #pragma warning restore CS8632, SA1600, SX1309 diff --git a/src/eventstore/SimpleKurrentDispatcher.cs b/src/eventstore/SimpleKurrentDispatcher.cs index 84765e1..56c841d 100644 --- a/src/eventstore/SimpleKurrentDispatcher.cs +++ b/src/eventstore/SimpleKurrentDispatcher.cs @@ -1,64 +1,61 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using global::EventStore.Client; +using Newtonsoft.Json; + +namespace PharmaxoScientific.MessageDispatch.EventStore; -namespace PharmaxoScientific.MessageDispatch.EventStore +/// +/// +/// A simple event store dispatcher. +/// +public class SimpleKurrentDispatcher : DeserializingMessageDispatcher { - using System; - using System.Collections.Generic; - using System.Text; - using CorshamScience.MessageDispatch.Core; - using global::EventStore.Client; - using Newtonsoft.Json; + private readonly Dictionary _eventTypeMapping; + private readonly JsonSerializerSettings _serializerSettings; +#pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// A simple event store dispatcher. + /// Initializes a new instance of the class. /// - public class SimpleKurrentDispatcher : DeserializingMessageDispatcher + /// Message handler lookup of a type. + /// Event Type Map. + /// Json Serializer settings. + // ReSharper disable once UnusedMember.Global + public SimpleKurrentDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) + : base(handlers) { - private readonly Dictionary _eventTypeMapping; - private readonly JsonSerializerSettings _serializerSettings; - -#pragma warning disable SA1648 // inheritdoc should be used with inheriting class - /// - /// - /// Initializes a new instance of the class. - /// - /// Message handler lookup of a type. - /// Event Type Map. - /// Json Serializer settings. - // ReSharper disable once UnusedMember.Global - public SimpleKurrentDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) - : base(handlers) - { - _eventTypeMapping = eventTypeMapping; - _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); - } + _eventTypeMapping = eventTypeMapping; + _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); + } #pragma warning restore SA1648 // inheritdoc should be used with inheriting class - /// - protected override bool TryGetMessageType(ResolvedEvent rawMessage, out Type type) + /// + protected override bool TryGetMessageType(ResolvedEvent rawMessage, out Type type) + { + var eventType = rawMessage.Event.EventType; + return _eventTypeMapping.TryGetValue(eventType, out type); + } + + /// + protected override bool TryDeserialize(Type messageType, ResolvedEvent rawMessage, out object deserialized) + { + deserialized = null; + + try { - var eventType = rawMessage.Event.EventType; - return _eventTypeMapping.TryGetValue(eventType, out type); + var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); + deserialized = JsonConvert.DeserializeObject(jsonString, messageType, _serializerSettings); + return deserialized != null; } - - /// - protected override bool TryDeserialize(Type messageType, ResolvedEvent rawMessage, out object deserialized) + catch (Exception) { - deserialized = null; - - try - { - var jsonString = Encoding.UTF8.GetString(rawMessage.Event.Data.Span.ToArray()); - deserialized = JsonConvert.DeserializeObject(jsonString, messageType, _serializerSettings); - return deserialized != null; - } - catch (Exception) - { - return false; - } + return false; } } } diff --git a/src/eventstore/WriteThroughFileCheckpoint.cs b/src/eventstore/WriteThroughFileCheckpoint.cs index 080044b..fe408c9 100644 --- a/src/eventstore/WriteThroughFileCheckpoint.cs +++ b/src/eventstore/WriteThroughFileCheckpoint.cs @@ -1,79 +1,77 @@ -// -// Copyright (c) Pharmaxo Scientific. All rights reserved. -// +// Copyright (c) Pharmaxo. All rights reserved. + +using System.IO; + #pragma warning disable CA1001 -namespace PharmaxoScientific.MessageDispatch.EventStore +namespace PharmaxoScientific.MessageDispatch.EventStore; + +/// +/// Writes a checkpoint to a file pulled from event store. +/// +internal class WriteThroughFileCheckpoint { - using System.IO; + private readonly object _streamLock = new(); + private readonly FileStream _fileStream; + private readonly BinaryReader _reader; + private readonly BinaryWriter _writer; + private long? _lastWritten; /// - /// Writes a checkpoint to a file pulled from event store. + /// Initializes a new instance of the class. /// - internal class WriteThroughFileCheckpoint + /// The file to write a checkpoint to. + /// The initial value to write. + public WriteThroughFileCheckpoint(string filePath, long initValue = 0L) { - private readonly object _streamLock = new (); - private readonly FileStream _fileStream; - private readonly BinaryReader _reader; - private readonly BinaryWriter _writer; - private long? _lastWritten; - - /// - /// Initializes a new instance of the class. - /// - /// The file to write a checkpoint to. - /// The initial value to write. - public WriteThroughFileCheckpoint(string filePath, long initValue = 0L) - { - var alreadyExisted = File.Exists(filePath); + var alreadyExisted = File.Exists(filePath); - _fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); + _fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); - if (_fileStream.Length != sizeof(long)) - { - _fileStream.SetLength(8); - } + if (_fileStream.Length != sizeof(long)) + { + _fileStream.SetLength(8); + } - _reader = new BinaryReader(_fileStream); - _writer = new BinaryWriter(_fileStream); + _reader = new BinaryReader(_fileStream); + _writer = new BinaryWriter(_fileStream); - if (!alreadyExisted) - { - Write(initValue); - } + if (!alreadyExisted) + { + Write(initValue); } + } - /// - /// Reads the current checkpoint. - /// - /// The current checkpoint. - public long Read() + /// + /// Reads the current checkpoint. + /// + /// The current checkpoint. + public long Read() + { + lock (_streamLock) { - lock (_streamLock) + if (_lastWritten.HasValue) { - if (_lastWritten.HasValue) - { - return _lastWritten.Value; - } - - _fileStream.Seek(0, SeekOrigin.Begin); - return _reader.ReadInt64(); + return _lastWritten.Value; } + + _fileStream.Seek(0, SeekOrigin.Begin); + return _reader.ReadInt64(); } + } - /// - /// Writes the checkpoint value and optionally flushes the underlying stream. - /// - /// The checkpoint value to write. - public void Write(long checkpoint) + /// + /// Writes the checkpoint value and optionally flushes the underlying stream. + /// + /// The checkpoint value to write. + public void Write(long checkpoint) + { + lock (_streamLock) { - lock (_streamLock) - { - _fileStream.Seek(0, SeekOrigin.Begin); - _writer.Write(checkpoint); - _lastWritten = checkpoint; - _fileStream.Flush(flushToDisk: true); - } + _fileStream.Seek(0, SeekOrigin.Begin); + _writer.Write(checkpoint); + _lastWritten = checkpoint; + _fileStream.Flush(flushToDisk: true); } } } diff --git a/src/eventstore/kurrent.csproj b/src/eventstore/kurrent.csproj index f783d97..42dc255 100644 --- a/src/eventstore/kurrent.csproj +++ b/src/eventstore/kurrent.csproj @@ -30,10 +30,11 @@ + + + + - - All - From 5a4db1bd628bd06d4cf4a071ed67c48b48271ed0 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 12:04:18 +0100 Subject: [PATCH 07/33] Removed Eventstore references --- .../CheckpointingWrappingDispatcher.cs | 2 +- .../KurrentAggregateEventDispatcher.cs | 2 +- src/eventstore/KurrentJObjectDispatcher.cs | 2 +- src/eventstore/KurrentSubscriber.cs | 25 +++++++++---------- src/eventstore/SimpleKurrentDispatcher.cs | 2 +- src/eventstore/kurrent.csproj | 2 +- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/eventstore/CheckpointingWrappingDispatcher.cs b/src/eventstore/CheckpointingWrappingDispatcher.cs index b7a1372..f4eea36 100644 --- a/src/eventstore/CheckpointingWrappingDispatcher.cs +++ b/src/eventstore/CheckpointingWrappingDispatcher.cs @@ -3,7 +3,7 @@ using System; using System.Text; using CorshamScience.MessageDispatch.Core; -using global::EventStore.Client; +using KurrentDB.Client; using Newtonsoft.Json.Linq; namespace PharmaxoScientific.MessageDispatch.EventStore; diff --git a/src/eventstore/KurrentAggregateEventDispatcher.cs b/src/eventstore/KurrentAggregateEventDispatcher.cs index 7ff4542..77bdc30 100644 --- a/src/eventstore/KurrentAggregateEventDispatcher.cs +++ b/src/eventstore/KurrentAggregateEventDispatcher.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text; using CorshamScience.MessageDispatch.Core; -using global::EventStore.Client; +using KurrentDB.Client; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/src/eventstore/KurrentJObjectDispatcher.cs b/src/eventstore/KurrentJObjectDispatcher.cs index b74cbac..6265805 100644 --- a/src/eventstore/KurrentJObjectDispatcher.cs +++ b/src/eventstore/KurrentJObjectDispatcher.cs @@ -3,7 +3,7 @@ using System; using System.Text; using CorshamScience.MessageDispatch.Core; -using global::EventStore.Client; +using KurrentDB.Client; using Newtonsoft.Json.Linq; namespace PharmaxoScientific.MessageDispatch.EventStore; diff --git a/src/eventstore/KurrentSubscriber.cs b/src/eventstore/KurrentSubscriber.cs index ad80fe2..515dd34 100644 --- a/src/eventstore/KurrentSubscriber.cs +++ b/src/eventstore/KurrentSubscriber.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; using CorshamScience.MessageDispatch.Core; -using global::EventStore.Client; +using KurrentDB.Client; using Microsoft.Extensions.Logging; namespace PharmaxoScientific.MessageDispatch.EventStore; @@ -19,8 +19,7 @@ public class KurrentSubscriber private const uint CheckpointInterval = 1; private readonly WriteThroughFileCheckpoint _checkpoint; private readonly object _subscriptionLock = new object(); - - private EventStoreClient _eventStoreClient; + private KurrentDBClient _eventStoreClient; private ulong? _startingPosition; private StreamSubscription _subscription; private string _streamName; @@ -39,7 +38,7 @@ public class KurrentSubscriber private ILogger _logger; private KurrentSubscriber( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, @@ -48,7 +47,7 @@ private KurrentSubscriber( => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); private KurrentSubscriber( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, string streamName, @@ -68,7 +67,7 @@ private KurrentSubscriber( } private KurrentSubscriber( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, @@ -133,7 +132,7 @@ public bool IsLive /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentSubscriber CreateLiveSubscription( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, @@ -152,7 +151,7 @@ public static KurrentSubscriber CreateLiveSubscription( /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, @@ -172,7 +171,7 @@ public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, @@ -190,7 +189,7 @@ public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong liveEventThreshold = 10) @@ -212,7 +211,7 @@ public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong? startingPosition, @@ -236,7 +235,7 @@ public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosi /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( - EventStoreClient eventStoreClient, + KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, string checkpointFilePath, @@ -368,7 +367,7 @@ public void ShutDown() } private void Init( - EventStoreClient connection, + KurrentDBClient connection, IDispatcher dispatcher, string streamName, ILogger logger, diff --git a/src/eventstore/SimpleKurrentDispatcher.cs b/src/eventstore/SimpleKurrentDispatcher.cs index 56c841d..e4923d5 100644 --- a/src/eventstore/SimpleKurrentDispatcher.cs +++ b/src/eventstore/SimpleKurrentDispatcher.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text; using CorshamScience.MessageDispatch.Core; -using global::EventStore.Client; +using KurrentDB.Client; using Newtonsoft.Json; namespace PharmaxoScientific.MessageDispatch.EventStore; diff --git a/src/eventstore/kurrent.csproj b/src/eventstore/kurrent.csproj index 42dc255..0a94a3d 100644 --- a/src/eventstore/kurrent.csproj +++ b/src/eventstore/kurrent.csproj @@ -36,7 +36,7 @@ - + From d05fc370e69198394eb8428f659f4d4fa231b26f Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 12:06:09 +0100 Subject: [PATCH 08/33] Renamed files from Kurrent to KurrentDB --- .../CheckpointingWrappingDispatcher.cs | 2 +- ...s => KurrentDBAggregateEventDispatcher.cs} | 6 ++-- ...tcher.cs => KurrentDBJObjectDispatcher.cs} | 6 ++-- ...ntSubscriber.cs => KurrentDBSubscriber.cs} | 32 +++++++++---------- ...atcher.cs => SimpleKurrentDBDispatcher.cs} | 6 ++-- 5 files changed, 26 insertions(+), 26 deletions(-) rename src/eventstore/{KurrentAggregateEventDispatcher.cs => KurrentDBAggregateEventDispatcher.cs} (93%) rename src/eventstore/{KurrentJObjectDispatcher.cs => KurrentDBJObjectDispatcher.cs} (84%) rename src/eventstore/{KurrentSubscriber.cs => KurrentDBSubscriber.cs} (94%) rename src/eventstore/{SimpleKurrentDispatcher.cs => SimpleKurrentDBDispatcher.cs} (86%) diff --git a/src/eventstore/CheckpointingWrappingDispatcher.cs b/src/eventstore/CheckpointingWrappingDispatcher.cs index f4eea36..cbf8ae0 100644 --- a/src/eventstore/CheckpointingWrappingDispatcher.cs +++ b/src/eventstore/CheckpointingWrappingDispatcher.cs @@ -11,7 +11,7 @@ namespace PharmaxoScientific.MessageDispatch.EventStore; /// /// A wrapping event dispatcher which keeps track of a checkpoint, and whether the dispatched event has been previously processed or not. /// -public class CheckpointingWrappingDispatcher : KurrentAggregateEventDispatcher +public class CheckpointingWrappingDispatcher : KurrentDBAggregateEventDispatcher { private readonly WriteThroughFileCheckpoint _checkpoint; private readonly long _startupCheckpointValue; diff --git a/src/eventstore/KurrentAggregateEventDispatcher.cs b/src/eventstore/KurrentDBAggregateEventDispatcher.cs similarity index 93% rename from src/eventstore/KurrentAggregateEventDispatcher.cs rename to src/eventstore/KurrentDBAggregateEventDispatcher.cs index 77bdc30..6207cfd 100644 --- a/src/eventstore/KurrentAggregateEventDispatcher.cs +++ b/src/eventstore/KurrentDBAggregateEventDispatcher.cs @@ -15,7 +15,7 @@ namespace PharmaxoScientific.MessageDispatch.EventStore; /// A deserializing event dispatcher for events produced by CorshamScience.AggregatRepository. /// // ReSharper disable once UnusedMember.Global -public class KurrentAggregateEventDispatcher : DeserializingMessageDispatcher +public class KurrentDBAggregateEventDispatcher : DeserializingMessageDispatcher { private readonly JsonSerializerSettings _serializerSettings; @@ -25,13 +25,13 @@ public class KurrentAggregateEventDispatcher : DeserializingMessageDispatcher /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The handler methods for processing messages with. /// Determines the settings for the JSON serialization of events. /// Optional parameter for a metadata key default is ClrType // ReSharper disable once UnusedMember.Global - public KurrentAggregateEventDispatcher( + public KurrentDBAggregateEventDispatcher( IMessageHandlerLookup handlers, JsonSerializerSettings serializerSettings = null, string metadataKey = null) diff --git a/src/eventstore/KurrentJObjectDispatcher.cs b/src/eventstore/KurrentDBJObjectDispatcher.cs similarity index 84% rename from src/eventstore/KurrentJObjectDispatcher.cs rename to src/eventstore/KurrentDBJObjectDispatcher.cs index 6265805..e34b3b5 100644 --- a/src/eventstore/KurrentJObjectDispatcher.cs +++ b/src/eventstore/KurrentDBJObjectDispatcher.cs @@ -13,16 +13,16 @@ namespace PharmaxoScientific.MessageDispatch.EventStore; /// A message dispatcher that deserializes messages to a JObject upon dispatch. /// // ReSharper disable once UnusedMember.Global -public class KurrentJObjectDispatcher : DeserializingMessageDispatcher +public class KurrentDBJObjectDispatcher : DeserializingMessageDispatcher { #pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Lookups for the handlers which the class can use to process messages. // ReSharper disable once UnusedMember.Global - public KurrentJObjectDispatcher(IMessageHandlerLookup handlers) + public KurrentDBJObjectDispatcher(IMessageHandlerLookup handlers) : base(handlers) { } diff --git a/src/eventstore/KurrentSubscriber.cs b/src/eventstore/KurrentDBSubscriber.cs similarity index 94% rename from src/eventstore/KurrentSubscriber.cs rename to src/eventstore/KurrentDBSubscriber.cs index 515dd34..bc0793e 100644 --- a/src/eventstore/KurrentSubscriber.cs +++ b/src/eventstore/KurrentDBSubscriber.cs @@ -13,7 +13,7 @@ namespace PharmaxoScientific.MessageDispatch.EventStore; /// /// Subscriber for event store. /// -public class KurrentSubscriber +public class KurrentDBSubscriber { private const string AllStreamName = "$all"; private const uint CheckpointInterval = 1; @@ -37,7 +37,7 @@ public class KurrentSubscriber private IDispatcher _dispatcher; private ILogger _logger; - private KurrentSubscriber( + private KurrentDBSubscriber( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, @@ -46,7 +46,7 @@ private KurrentSubscriber( ulong liveEventThreshold) => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); - private KurrentSubscriber( + private KurrentDBSubscriber( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, @@ -66,7 +66,7 @@ private KurrentSubscriber( Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); } - private KurrentSubscriber( + private KurrentDBSubscriber( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, @@ -131,13 +131,13 @@ public bool IsLive /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateLiveSubscription( + public static KurrentDBSubscriber CreateLiveSubscription( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong liveEventThreshold = 10) - => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); + => new KurrentDBSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); /// /// Creates an eventstore catchup subscription using a checkpoint file. @@ -150,14 +150,14 @@ public static KurrentSubscriber CreateLiveSubscription( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( + public static KurrentDBSubscriber CreateCatchupSubscriptionUsingCheckpoint( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new KurrentSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); + => new KurrentDBSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); /// /// Creates an eventstore catchup subscription from a position. @@ -170,14 +170,14 @@ public static KurrentSubscriber CreateCatchupSubscriptionUsingCheckpoint( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( + public static KurrentDBSubscriber CreateCatchupSubscriptionFromPosition( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new KurrentSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); + => new KurrentDBSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); /// /// Creates an eventstore catchup subscription that is subscribed to all from the start. @@ -188,12 +188,12 @@ public static KurrentSubscriber CreateCatchupSubscriptionFromPosition( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( + public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAll( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong liveEventThreshold = 10) - => new KurrentSubscriber( + => new KurrentDBSubscriber( eventStoreClient, dispatcher, AllStreamName, @@ -210,13 +210,13 @@ public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAll( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( + public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new KurrentSubscriber( + => new KurrentDBSubscriber( eventStoreClient, dispatcher, AllStreamName, @@ -234,13 +234,13 @@ public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosi /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( + public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new KurrentSubscriber( + => new KurrentDBSubscriber( eventStoreClient, dispatcher, logger, diff --git a/src/eventstore/SimpleKurrentDispatcher.cs b/src/eventstore/SimpleKurrentDBDispatcher.cs similarity index 86% rename from src/eventstore/SimpleKurrentDispatcher.cs rename to src/eventstore/SimpleKurrentDBDispatcher.cs index e4923d5..9438b57 100644 --- a/src/eventstore/SimpleKurrentDispatcher.cs +++ b/src/eventstore/SimpleKurrentDBDispatcher.cs @@ -13,7 +13,7 @@ namespace PharmaxoScientific.MessageDispatch.EventStore; /// /// A simple event store dispatcher. /// -public class SimpleKurrentDispatcher : DeserializingMessageDispatcher +public class SimpleKurrentDBDispatcher : DeserializingMessageDispatcher { private readonly Dictionary _eventTypeMapping; private readonly JsonSerializerSettings _serializerSettings; @@ -21,13 +21,13 @@ public class SimpleKurrentDispatcher : DeserializingMessageDispatcher /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Message handler lookup of a type. /// Event Type Map. /// Json Serializer settings. // ReSharper disable once UnusedMember.Global - public SimpleKurrentDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) + public SimpleKurrentDBDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) : base(handlers) { _eventTypeMapping = eventTypeMapping; From f078459afc3a3d555a5603f258f5bf28f519f378 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 12:08:59 +0100 Subject: [PATCH 09/33] Fixed build script --- build.cmd | 2 +- build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.cmd b/build.cmd index d401c47..0c963f5 100644 --- a/build.cmd +++ b/build.cmd @@ -7,4 +7,4 @@ SET TAG=0.0.0 IF NOT [%2]==[] (set TAG=%2) SET TAG=%TAG:tags/=% -dotnet pack .\src\eventstore\eventstore.csproj -o .\dist -p:Version="%VERSION%" -p:PackageVersion="%VERSION%" -p:Tag="%TAG%" -c Release \ No newline at end of file +dotnet pack .\src\eventstore\kurrent.csproj -o .\dist -p:Version="%VERSION%" -p:PackageVersion="%VERSION%" -p:Tag="%TAG%" -c Release \ No newline at end of file diff --git a/build.sh b/build.sh index 534e996..04978d0 100644 --- a/build.sh +++ b/build.sh @@ -9,4 +9,4 @@ if [ -n "$2" ]; then tag="$2" fi tag=${tag/tags\//} -dotnet pack .\\src\\eventstore\\eventstore.csproj -o .\\dist -p:Version="$version" -p:PackageVersion="$version" -p:Tag="$tag" -c Release \ No newline at end of file +dotnet pack .\\src\\eventstore\\kurrent.csproj -o .\\dist -p:Version="$version" -p:PackageVersion="$version" -p:Tag="$tag" -c Release \ No newline at end of file From b7013d22bf258a3f26560f2b45397e9fd2df97d1 Mon Sep 17 00:00:00 2001 From: Josh Pattie Date: Wed, 28 May 2025 14:55:34 +0100 Subject: [PATCH 10/33] Renamed to KurrentDB + updated build script --- LICENSE | 4 +- README.md | 4 +- build.cmd | 11 +- ...rent.sln => MessageDispatch.KurrentBD.sln} | 50 ++++----- .../CatchupProgress.cs | 6 +- .../CheckpointingWrappingDispatcher.cs | 18 +-- .../EventWrapper.cs | 8 +- .../GlobalSuppressions.cs | 4 +- .../KurrentDbAggregateEventDispatcher.cs} | 48 ++++---- .../KurrentDbJObjectDispatcher.cs} | 22 ++-- .../KurrentDbSubscriber.cs} | 106 +++++++++--------- .../Logo.png | Bin .../MessageDispatch.KurrentDB.csproj | 46 ++++++++ .../NoSynchronizationContextScope.cs | 10 +- .../SimpleKurrentDbDispatcher.cs} | 24 ++-- .../WriteThroughFileCheckpoint.cs | 8 +- src/eventstore/kurrent.csproj | 50 --------- 17 files changed, 210 insertions(+), 209 deletions(-) rename src/{MessageDispatch.Kurrent.sln => MessageDispatch.KurrentBD.sln} (80%) rename src/{eventstore => MessageDispatch.KurrentDB}/CatchupProgress.cs (98%) rename src/{eventstore => MessageDispatch.KurrentDB}/CheckpointingWrappingDispatcher.cs (94%) rename src/{eventstore => MessageDispatch.KurrentDB}/EventWrapper.cs (94%) rename src/{eventstore => MessageDispatch.KurrentDB}/GlobalSuppressions.cs (99%) rename src/{eventstore/KurrentDBAggregateEventDispatcher.cs => MessageDispatch.KurrentDB/KurrentDbAggregateEventDispatcher.cs} (87%) rename src/{eventstore/KurrentDBJObjectDispatcher.cs => MessageDispatch.KurrentDB/KurrentDbJObjectDispatcher.cs} (85%) rename src/{eventstore/KurrentDBSubscriber.cs => MessageDispatch.KurrentDB/KurrentDbSubscriber.cs} (95%) rename src/{eventstore => MessageDispatch.KurrentDB}/Logo.png (100%) create mode 100644 src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj rename src/{eventstore => MessageDispatch.KurrentDB}/NoSynchronizationContextScope.cs (94%) rename src/{eventstore/SimpleKurrentDBDispatcher.cs => MessageDispatch.KurrentDB/SimpleKurrentDbDispatcher.cs} (90%) rename src/{eventstore => MessageDispatch.KurrentDB}/WriteThroughFileCheckpoint.cs (97%) delete mode 100644 src/eventstore/kurrent.csproj diff --git a/LICENSE b/LICENSE index 9d0eab8..855d86f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,9 @@ -Copyright (c) 2019, Corsham Science +Copyright (c) 2025, Pharmaxo Scientific All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the name of Corsham Science nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +Neither the name of Pharmaxo Scientific nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 19aeb2f..7d37bc9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# PharmaxoScientific.MessageDispatch.EventStore -A package to use EventStore with CorshamScience.MessageDispatch. +# PharmaxoScientific.MessageDispatch.KurrentDB +A package to use KurrentDB with PharmaxoScientific.MessageDispatch.KurrentDB diff --git a/build.cmd b/build.cmd index 0c963f5..313f4c5 100644 --- a/build.cmd +++ b/build.cmd @@ -1,10 +1,15 @@ @echo off SET VERSION=0.0.0 -IF NOT [%1]==[] (set VERSION=%1) +IF NOT [%1]==[] (SET VERSION=%1) SET TAG=0.0.0 -IF NOT [%2]==[] (set TAG=%2) +IF NOT [%2]==[] (SET TAG=%2) SET TAG=%TAG:tags/=% -dotnet pack .\src\eventstore\kurrent.csproj -o .\dist -p:Version="%VERSION%" -p:PackageVersion="%VERSION%" -p:Tag="%TAG%" -c Release \ No newline at end of file +dotnet restore .\src\MessageDispatch.KurrentDB.sln -PackagesDirectory .\src\packages -Verbosity detailed + +dotnet format .\src\MessageDispatch.KurrentBD.sln --severity warn --verify-no-changes -v diag +IF %errorlevel% neq 0 EXIT /B %errorlevel% + +dotnet pack .\src\MessageDispatch.KurrentDB\MessageDispatch.KurrentDB.csproj -o .\dist -p:Version="%VERSION%" -p:PackageVersion="%VERSION%" -p:Tag="%TAG%" -c Release \ No newline at end of file diff --git a/src/MessageDispatch.Kurrent.sln b/src/MessageDispatch.KurrentBD.sln similarity index 80% rename from src/MessageDispatch.Kurrent.sln rename to src/MessageDispatch.KurrentBD.sln index 0b363c0..2d4c0c8 100644 --- a/src/MessageDispatch.Kurrent.sln +++ b/src/MessageDispatch.KurrentBD.sln @@ -1,25 +1,25 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.12.35931.192 d17.12 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kurrent", "eventstore\kurrent.csproj", "{8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {58A60822-0CA7-4255-AA1E-533ECE617C52} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35931.192 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessageDispatch.KurrentDB", "MessageDispatch.KurrentDB\MessageDispatch.KurrentDB.csproj", "{8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {58A60822-0CA7-4255-AA1E-533ECE617C52} + EndGlobalSection +EndGlobal diff --git a/src/eventstore/CatchupProgress.cs b/src/MessageDispatch.KurrentDB/CatchupProgress.cs similarity index 98% rename from src/eventstore/CatchupProgress.cs rename to src/MessageDispatch.KurrentDB/CatchupProgress.cs index 425bafb..fe59e1d 100644 --- a/src/eventstore/CatchupProgress.cs +++ b/src/MessageDispatch.KurrentDB/CatchupProgress.cs @@ -1,6 +1,6 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -namespace PharmaxoScientific.MessageDispatch.EventStore; +// Copyright (c) Pharmaxo. All rights reserved. + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// Class to handle calculating catchup progress. diff --git a/src/eventstore/CheckpointingWrappingDispatcher.cs b/src/MessageDispatch.KurrentDB/CheckpointingWrappingDispatcher.cs similarity index 94% rename from src/eventstore/CheckpointingWrappingDispatcher.cs rename to src/MessageDispatch.KurrentDB/CheckpointingWrappingDispatcher.cs index cbf8ae0..2535270 100644 --- a/src/eventstore/CheckpointingWrappingDispatcher.cs +++ b/src/MessageDispatch.KurrentDB/CheckpointingWrappingDispatcher.cs @@ -1,17 +1,17 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -using System; -using System.Text; -using CorshamScience.MessageDispatch.Core; -using KurrentDB.Client; +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using KurrentDB.Client; using Newtonsoft.Json.Linq; - -namespace PharmaxoScientific.MessageDispatch.EventStore; + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// A wrapping event dispatcher which keeps track of a checkpoint, and whether the dispatched event has been previously processed or not. /// -public class CheckpointingWrappingDispatcher : KurrentDBAggregateEventDispatcher +public class CheckpointingWrappingDispatcher : KurrentDbAggregateEventDispatcher { private readonly WriteThroughFileCheckpoint _checkpoint; private readonly long _startupCheckpointValue; diff --git a/src/eventstore/EventWrapper.cs b/src/MessageDispatch.KurrentDB/EventWrapper.cs similarity index 94% rename from src/eventstore/EventWrapper.cs rename to src/MessageDispatch.KurrentDB/EventWrapper.cs index dea5d6e..0e4abb2 100644 --- a/src/eventstore/EventWrapper.cs +++ b/src/MessageDispatch.KurrentDB/EventWrapper.cs @@ -1,8 +1,8 @@ -// Copyright (c) Pharmaxo. All rights reserved. - +// Copyright (c) Pharmaxo. All rights reserved. + using Newtonsoft.Json.Linq; - -namespace PharmaxoScientific.MessageDispatch.EventStore; + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// Represents a wrapper for an event. diff --git a/src/eventstore/GlobalSuppressions.cs b/src/MessageDispatch.KurrentDB/GlobalSuppressions.cs similarity index 99% rename from src/eventstore/GlobalSuppressions.cs rename to src/MessageDispatch.KurrentDB/GlobalSuppressions.cs index dd4c5bc..ac35db8 100644 --- a/src/eventstore/GlobalSuppressions.cs +++ b/src/MessageDispatch.KurrentDB/GlobalSuppressions.cs @@ -1,3 +1,3 @@ -// Copyright (c) Pharmaxo. All rights reserved. - +// Copyright (c) Pharmaxo. All rights reserved. + [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "The file was taken from the eventstore codebase so it is not valid to add a copyright header.", Scope = "namespace", Target = "~N:CorshamScience.MessageDispatch.EventStore")] diff --git a/src/eventstore/KurrentDBAggregateEventDispatcher.cs b/src/MessageDispatch.KurrentDB/KurrentDbAggregateEventDispatcher.cs similarity index 87% rename from src/eventstore/KurrentDBAggregateEventDispatcher.cs rename to src/MessageDispatch.KurrentDB/KurrentDbAggregateEventDispatcher.cs index 6207cfd..5556ba1 100644 --- a/src/eventstore/KurrentDBAggregateEventDispatcher.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbAggregateEventDispatcher.cs @@ -1,44 +1,44 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Text; -using CorshamScience.MessageDispatch.Core; -using KurrentDB.Client; -using Newtonsoft.Json; +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using KurrentDB.Client; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; - -namespace PharmaxoScientific.MessageDispatch.EventStore; + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// /// A deserializing event dispatcher for events produced by CorshamScience.AggregatRepository. /// // ReSharper disable once UnusedMember.Global -public class KurrentDBAggregateEventDispatcher : DeserializingMessageDispatcher +public class KurrentDbAggregateEventDispatcher : DeserializingMessageDispatcher { private readonly JsonSerializerSettings _serializerSettings; - private readonly Dictionary _typeCache = new Dictionary(); - private readonly string _metadataKey; - + private readonly Dictionary _typeCache = new(); + private readonly string _metadataKey; + #pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The handler methods for processing messages with. - /// Determines the settings for the JSON serialization of events. - /// Optional parameter for a metadata key default is ClrType - // ReSharper disable once UnusedMember.Global - public KurrentDBAggregateEventDispatcher( + /// Determines the settings for the JSON serialization of events. + /// Optional parameter for the metadata key. Default is "ClrType" + // ReSharper disable once UnusedMember.Global + public KurrentDbAggregateEventDispatcher( IMessageHandlerLookup handlers, JsonSerializerSettings serializerSettings = null, string metadataKey = null) - : base(handlers) - { - _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); - _metadataKey = metadataKey ?? "ClrType"; + : base(handlers) + { + _serializerSettings = serializerSettings ?? new JsonSerializerSettings(); + _metadataKey = metadataKey ?? "ClrType"; } #pragma warning restore SA1648 // inheritdoc should be used with inheriting class @@ -62,7 +62,7 @@ protected override bool TryGetMessageType(ResolvedEvent rawMessage, out Type typ return false; } - string typeString = (string)metadata[_metadataKey]; + var typeString = (string)metadata[_metadataKey]; if (!_typeCache.TryGetValue(typeString, out var cached)) { diff --git a/src/eventstore/KurrentDBJObjectDispatcher.cs b/src/MessageDispatch.KurrentDB/KurrentDbJObjectDispatcher.cs similarity index 85% rename from src/eventstore/KurrentDBJObjectDispatcher.cs rename to src/MessageDispatch.KurrentDB/KurrentDbJObjectDispatcher.cs index e34b3b5..bf37432 100644 --- a/src/eventstore/KurrentDBJObjectDispatcher.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbJObjectDispatcher.cs @@ -1,28 +1,28 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -using System; -using System.Text; -using CorshamScience.MessageDispatch.Core; -using KurrentDB.Client; +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using KurrentDB.Client; using Newtonsoft.Json.Linq; - -namespace PharmaxoScientific.MessageDispatch.EventStore; + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// /// A message dispatcher that deserializes messages to a JObject upon dispatch. /// // ReSharper disable once UnusedMember.Global -public class KurrentDBJObjectDispatcher : DeserializingMessageDispatcher +public class KurrentDbjObjectDispatcher : DeserializingMessageDispatcher { #pragma warning disable SA1648 // inheritdoc should be used with inheriting class /// /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Lookups for the handlers which the class can use to process messages. // ReSharper disable once UnusedMember.Global - public KurrentDBJObjectDispatcher(IMessageHandlerLookup handlers) + public KurrentDbjObjectDispatcher(IMessageHandlerLookup handlers) : base(handlers) { } diff --git a/src/eventstore/KurrentDBSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs similarity index 95% rename from src/eventstore/KurrentDBSubscriber.cs rename to src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index bc0793e..527bcbc 100644 --- a/src/eventstore/KurrentDBSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -1,24 +1,24 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using CorshamScience.MessageDispatch.Core; -using KurrentDB.Client; +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CorshamScience.MessageDispatch.Core; +using KurrentDB.Client; using Microsoft.Extensions.Logging; - -namespace PharmaxoScientific.MessageDispatch.EventStore; + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// Subscriber for event store. /// -public class KurrentDBSubscriber +public class KurrentDbSubscriber { private const string AllStreamName = "$all"; private const uint CheckpointInterval = 1; private readonly WriteThroughFileCheckpoint _checkpoint; - private readonly object _subscriptionLock = new object(); + private readonly object _subscriptionLock = new(); private KurrentDBClient _eventStoreClient; private ulong? _startingPosition; private StreamSubscription _subscription; @@ -37,7 +37,7 @@ public class KurrentDBSubscriber private IDispatcher _dispatcher; private ILogger _logger; - private KurrentDBSubscriber( + private KurrentDbSubscriber( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, @@ -46,7 +46,7 @@ private KurrentDBSubscriber( ulong liveEventThreshold) => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); - private KurrentDBSubscriber( + private KurrentDbSubscriber( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, @@ -66,7 +66,7 @@ private KurrentDBSubscriber( Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); } - private KurrentDBSubscriber( + private KurrentDbSubscriber( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, @@ -131,13 +131,13 @@ public bool IsLive /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentDBSubscriber CreateLiveSubscription( + public static KurrentDbSubscriber CreateLiveSubscription( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong liveEventThreshold = 10) - => new KurrentDBSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); + => new KurrentDbSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); /// /// Creates an eventstore catchup subscription using a checkpoint file. @@ -150,14 +150,14 @@ public static KurrentDBSubscriber CreateLiveSubscription( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentDBSubscriber CreateCatchupSubscriptionUsingCheckpoint( + public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new KurrentDBSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); + => new KurrentDbSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); /// /// Creates an eventstore catchup subscription from a position. @@ -170,14 +170,14 @@ public static KurrentDBSubscriber CreateCatchupSubscriptionUsingCheckpoint( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentDBSubscriber CreateCatchupSubscriptionFromPosition( + public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( KurrentDBClient eventStoreClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new KurrentDBSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); + => new KurrentDbSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); /// /// Creates an eventstore catchup subscription that is subscribed to all from the start. @@ -188,12 +188,12 @@ public static KurrentDBSubscriber CreateCatchupSubscriptionFromPosition( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAll( + public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong liveEventThreshold = 10) - => new KurrentDBSubscriber( + => new KurrentDbSubscriber( eventStoreClient, dispatcher, AllStreamName, @@ -210,13 +210,13 @@ public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAll( /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( + public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new KurrentDBSubscriber( + => new KurrentDbSubscriber( eventStoreClient, dispatcher, AllStreamName, @@ -234,13 +234,13 @@ public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAllFromPo /// Proximity to end of stream before subscription considered live. /// A new EventStoreSubscriber object. // ReSharper disable once UnusedMember.Global - public static KurrentDBSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( + public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( KurrentDBClient eventStoreClient, IDispatcher dispatcher, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new KurrentDBSubscriber( + => new KurrentDbSubscriber( eventStoreClient, dispatcher, logger, @@ -291,19 +291,19 @@ Task Appeared( resolveLinkTos, SubscriptionDropped).Result; break; - case false when !_subscribeToAll: - { - var fromStream = _startingPosition.HasValue ? - FromStream.After(new StreamPosition(_startingPosition.Value)) : - FromStream.Start; - - _subscription = _eventStoreClient.SubscribeToStreamAsync( - _streamName, - fromStream, - Appeared, - resolveLinkTos, - SubscriptionDropped).Result; - break; + case false when !_subscribeToAll: + { + var fromStream = _startingPosition.HasValue ? + FromStream.After(new StreamPosition(_startingPosition.Value)) : + FromStream.Start; + + _subscription = _eventStoreClient.SubscribeToStreamAsync( + _streamName, + fromStream, + Appeared, + resolveLinkTos, + SubscriptionDropped).Result; + break; } case true when _subscribeToAll: @@ -398,19 +398,19 @@ private void Init( _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEvent.Position.CommitPosition; _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEvent.Position.CommitPosition; - } - : async () => - { - var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( - Direction.Backwards, - _streamName, - StreamPosition.End, - maxCount: (long)_liveEventThreshold, - resolveLinkTos: false) - .ToListAsync(); - - _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); - _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); + } + : async () => + { + var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( + Direction.Backwards, + _streamName, + StreamPosition.End, + maxCount: (long)_liveEventThreshold, + resolveLinkTos: false) + .ToListAsync(); + + _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); + _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); }; } diff --git a/src/eventstore/Logo.png b/src/MessageDispatch.KurrentDB/Logo.png similarity index 100% rename from src/eventstore/Logo.png rename to src/MessageDispatch.KurrentDB/Logo.png diff --git a/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj b/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj new file mode 100644 index 0000000..e507129 --- /dev/null +++ b/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj @@ -0,0 +1,46 @@ + + + + net8.0;net481 + latest + PharmaxoScientific.MessageDispatch.KurrentDB + PharmaxoScientific.MessageDispatch.KurrentDB + Pharmaxo Scientific + Pharmaxo Scientific + Pharmaxo Scientific + https://github.com/qphl/MessageDispatch.KurrentDB/blob/master/LICENSE + https://github.com/qphl/MessageDispatch.KurrentDB + https://github.com/qphl/MessageDispatch.KurrentDB + Message Dispatching, Event Sourcing, EventStore, KurrentDB + PharmaxoScientific.MessageDispatch.KurrentDB + PharmaxoScientific.MessageDispatch.KurrentDB + A package to use KurrentDB to get Events to Dispatch using PharmaxoScientific.MessageDispatch.KurrentDB. + https://GitHub.com/qphl/MessageDispatch.KurrentDB/releases/tag/$(Tag) + BSD-3-Clause + Logo.png + + + + bin\Debug\net8.0\PharmaxoScientific.MessageDispatch.KurrentDB.xml + + + + bin\Release\net8.0\PharmaxoScientific.MessageDispatch.KurrentDB.xml + true + + + + + + + + + + + + + True + + + + diff --git a/src/eventstore/NoSynchronizationContextScope.cs b/src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs similarity index 94% rename from src/eventstore/NoSynchronizationContextScope.cs rename to src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs index 1bb3b38..5e82d77 100644 --- a/src/eventstore/NoSynchronizationContextScope.cs +++ b/src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs @@ -1,8 +1,8 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -using System; +// Copyright (c) Pharmaxo. All rights reserved. + +using System; using System.Threading; - + /* This file is taken from Event Store codebase https://github.com/EventStore/samples/blob/main/CQRS_Flow/.NET/Core/Core/Threading/NoSynchronizationContextScope.cs As such we should not add a Pharmaxo Scientific copyright file header */ @@ -10,7 +10,7 @@ As such we should not add a Pharmaxo Scientific copyright file header */ // ReSharper disable InconsistentNaming #pragma warning disable CS8632, SA1600, SX1309 -namespace PharmaxoScientific.MessageDispatch.EventStore; +namespace PharmaxoScientific.MessageDispatch.KurrentDB; internal static class NoSynchronizationContextScope { diff --git a/src/eventstore/SimpleKurrentDBDispatcher.cs b/src/MessageDispatch.KurrentDB/SimpleKurrentDbDispatcher.cs similarity index 90% rename from src/eventstore/SimpleKurrentDBDispatcher.cs rename to src/MessageDispatch.KurrentDB/SimpleKurrentDbDispatcher.cs index 9438b57..8ee6867 100644 --- a/src/eventstore/SimpleKurrentDBDispatcher.cs +++ b/src/MessageDispatch.KurrentDB/SimpleKurrentDbDispatcher.cs @@ -1,19 +1,19 @@ -// Copyright (c) Pharmaxo. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Text; -using CorshamScience.MessageDispatch.Core; -using KurrentDB.Client; +// Copyright (c) Pharmaxo. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Text; +using CorshamScience.MessageDispatch.Core; +using KurrentDB.Client; using Newtonsoft.Json; - -namespace PharmaxoScientific.MessageDispatch.EventStore; + +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// /// A simple event store dispatcher. /// -public class SimpleKurrentDBDispatcher : DeserializingMessageDispatcher +public class SimpleKurrentDbDispatcher : DeserializingMessageDispatcher { private readonly Dictionary _eventTypeMapping; private readonly JsonSerializerSettings _serializerSettings; @@ -21,13 +21,13 @@ public class SimpleKurrentDBDispatcher : DeserializingMessageDispatcher /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Message handler lookup of a type. /// Event Type Map. /// Json Serializer settings. // ReSharper disable once UnusedMember.Global - public SimpleKurrentDBDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) + public SimpleKurrentDbDispatcher(IMessageHandlerLookup handlers, Dictionary eventTypeMapping, JsonSerializerSettings serializerSettings = null) : base(handlers) { _eventTypeMapping = eventTypeMapping; diff --git a/src/eventstore/WriteThroughFileCheckpoint.cs b/src/MessageDispatch.KurrentDB/WriteThroughFileCheckpoint.cs similarity index 97% rename from src/eventstore/WriteThroughFileCheckpoint.cs rename to src/MessageDispatch.KurrentDB/WriteThroughFileCheckpoint.cs index fe408c9..6590451 100644 --- a/src/eventstore/WriteThroughFileCheckpoint.cs +++ b/src/MessageDispatch.KurrentDB/WriteThroughFileCheckpoint.cs @@ -1,10 +1,10 @@ -// Copyright (c) Pharmaxo. All rights reserved. - +// Copyright (c) Pharmaxo. All rights reserved. + using System.IO; - + #pragma warning disable CA1001 -namespace PharmaxoScientific.MessageDispatch.EventStore; +namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// /// Writes a checkpoint to a file pulled from event store. diff --git a/src/eventstore/kurrent.csproj b/src/eventstore/kurrent.csproj deleted file mode 100644 index 0a94a3d..0000000 --- a/src/eventstore/kurrent.csproj +++ /dev/null @@ -1,50 +0,0 @@ - - - - net8.0;net481 - latest - PharmaxoScientific.MessageDispatch.EventStore - PharmaxoScientific.MessageDispatch.EventStore - Pharmaxo Scientific - Pharmaxo Scientific - Pharmaxo Scientific - https://github.com/qphl/MessageDispatch.EventStore/blob/master/LICENSE - https://github.com/qphl/MessageDispatch.EventStore - https://github.com/qphl/MessageDispatch.EventStore - Message Dispatching, Event Sourcing - PharmaxoScientific.MessageDispatch.EventStore - PharmaxoScientific.MessageDispatch - A package to use EventStore to get Events to Dispatch using CorshamScience.MessageDispatch. - https://GitHub.com/qphl/MessageDispatch.EventStore/releases/tag/$(Tag) - BSD-3-Clause - Logo.png - - - - bin\Debug\net6.0\PharmaxoScientific.MessageDispatch.EventStore.xml - - - - bin\Release\net6.0\PharmaxoScientific.MessageDispatch.EventStore.xml - true - - - - - - - - - - - - - - - - - True - - - - From 4a6bd0c459a77c1fb5e3c3f4c022a2eabe3d7782 Mon Sep 17 00:00:00 2001 From: Josh Pattie Date: Wed, 28 May 2025 15:01:29 +0100 Subject: [PATCH 11/33] Fixed naming violation --- .../NoSynchronizationContextScope.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs b/src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs index 5e82d77..965a64c 100644 --- a/src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs +++ b/src/MessageDispatch.KurrentDB/NoSynchronizationContextScope.cs @@ -23,15 +23,12 @@ public static Disposable Enter() public struct Disposable : IDisposable { - private readonly SynchronizationContext? synchronizationContext; + private readonly SynchronizationContext? _synchronizationContext; - public Disposable(SynchronizationContext? synchronizationContext) - { - this.synchronizationContext = synchronizationContext; - } + public Disposable(SynchronizationContext? synchronizationContext) => _synchronizationContext = synchronizationContext; public void Dispose() => - SynchronizationContext.SetSynchronizationContext(synchronizationContext); + SynchronizationContext.SetSynchronizationContext(_synchronizationContext); } } #pragma warning restore CS8632, SA1600, SX1309 From 8d846a30d9685742610f9d1de0f78023c05dc0f6 Mon Sep 17 00:00:00 2001 From: Josh Pattie Date: Thu, 29 May 2025 12:54:15 +0100 Subject: [PATCH 12/33] Variable rename from EventStore to KurrentDB --- .../KurrentDbSubscriber.cs | 94 +++++++++---------- .../MessageDispatch.KurrentDB.csproj | 2 +- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index 527bcbc..07fea79 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -19,7 +19,7 @@ public class KurrentDbSubscriber private const uint CheckpointInterval = 1; private readonly WriteThroughFileCheckpoint _checkpoint; private readonly object _subscriptionLock = new(); - private KurrentDBClient _eventStoreClient; + private KurrentDBClient _kurrentDbClient; private ulong? _startingPosition; private StreamSubscription _subscription; private string _streamName; @@ -38,16 +38,16 @@ public class KurrentDbSubscriber private ILogger _logger; private KurrentDbSubscriber( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong? startingPosition, ulong liveEventThreshold) - => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); + => Init(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); private KurrentDbSubscriber( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, string streamName, @@ -63,16 +63,16 @@ private KurrentDbSubscriber( startingPosition = (ulong)initialCheckpointPosition; } - Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); + Init(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); } private KurrentDbSubscriber( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong liveEventThreshold) - => Init(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold, liveOnly: true); + => Init(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold, liveOnly: true); /// /// Gets a new catchup progress object. @@ -122,102 +122,102 @@ public bool IsLive } /// - /// Creates a live eventstore subscription. + /// Creates a live KurrentDB subscription. /// - /// Eventstore connection. + /// KurrentDB connection. /// Dispatcher. /// Stream name to push events into. /// Logger. /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. + /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateLiveSubscription( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong liveEventThreshold = 10) - => new KurrentDbSubscriber(eventStoreClient, dispatcher, streamName, logger, liveEventThreshold); + => new KurrentDbSubscriber(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold); /// - /// Creates an eventstore catchup subscription using a checkpoint file. + /// Creates an KurrentDB catchup subscription using a checkpoint file. /// - /// Eventstore connection. + /// KurrentDB connection. /// Dispatcher. /// Stream name to push events into. /// Logger. /// Path of the checkpoint file. /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. + /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) - => new KurrentDbSubscriber(eventStoreClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); + => new KurrentDbSubscriber(kurrentDbClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); /// - /// Creates an eventstore catchup subscription from a position. + /// Creates an KurrentDB catchup subscription from a position. /// - /// Eventstore connection. + /// KurrentDB connection. /// Dispatcher. /// Stream name to push events into. /// Logger. /// Starting Position. /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. + /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) - => new KurrentDbSubscriber(eventStoreClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); + => new KurrentDbSubscriber(kurrentDbClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); /// - /// Creates an eventstore catchup subscription that is subscribed to all from the start. + /// Creates an KurrentDB catchup subscription that is subscribed to all from the start. /// - /// Eventstore connection. + /// KurrentDB connection. /// Dispatcher. /// Logger. /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. + /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, ulong liveEventThreshold = 10) => new KurrentDbSubscriber( - eventStoreClient, + kurrentDbClient, dispatcher, AllStreamName, logger, liveEventThreshold); /// - /// Creates an eventstore catchup subscription that is subscribed to all from a position. + /// Creates an KurrentDB catchup subscription that is subscribed to all from a position. /// - /// Eventstore connection. + /// KurrentDB connection. /// Dispatcher. /// Logger. /// Starting Position. /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. + /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, ulong? startingPosition, ulong liveEventThreshold = 10) => new KurrentDbSubscriber( - eventStoreClient, + kurrentDbClient, dispatcher, AllStreamName, logger, @@ -225,23 +225,23 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPo liveEventThreshold); /// - /// Creates an eventstore catchup subscription subscribed to all using a checkpoint file. + /// Creates an KurrentDB catchup subscription subscribed to all using a checkpoint file. /// - /// Eventstore connection. + /// KurrentDB connection. /// Dispatcher. /// Logger. /// Path of the checkpoint file. /// Proximity to end of stream before subscription considered live. - /// A new EventStoreSubscriber object. + /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( - KurrentDBClient eventStoreClient, + KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, string checkpointFilePath, ulong liveEventThreshold = 10) => new KurrentDbSubscriber( - eventStoreClient, + kurrentDbClient, dispatcher, logger, AllStreamName, @@ -284,7 +284,7 @@ Task Appeared( switch (_liveOnly) { case true when !_subscribeToAll: - _subscription = _eventStoreClient.SubscribeToStreamAsync( + _subscription = _kurrentDbClient.SubscribeToStreamAsync( _streamName, FromStream.End, Appeared, @@ -297,7 +297,7 @@ Task Appeared( FromStream.After(new StreamPosition(_startingPosition.Value)) : FromStream.Start; - _subscription = _eventStoreClient.SubscribeToStreamAsync( + _subscription = _kurrentDbClient.SubscribeToStreamAsync( _streamName, fromStream, Appeared, @@ -307,7 +307,7 @@ Task Appeared( } case true when _subscribeToAll: - _subscription = _eventStoreClient.SubscribeToAllAsync( + _subscription = _kurrentDbClient.SubscribeToAllAsync( FromAll.End, Appeared, resolveLinkTos, @@ -320,7 +320,7 @@ Task Appeared( FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : FromAll.Start; - _subscription = _eventStoreClient.SubscribeToAllAsync( + _subscription = _kurrentDbClient.SubscribeToAllAsync( fromAll, Appeared, resolveLinkTos, @@ -380,7 +380,7 @@ private void Init( _lastProcessedEventPosition = startingPosition; _dispatcher = dispatcher; _streamName = streamName; - _eventStoreClient = connection; + _kurrentDbClient = connection; _liveOnly = liveOnly; _subscribeToAll = streamName == AllStreamName; _liveEventThreshold = liveEventThreshold; @@ -390,7 +390,7 @@ private void Init( _setLastPositions = _subscribeToAll ? async () => { - var eventsWithinThreshold = await _eventStoreClient.ReadAllAsync( + var eventsWithinThreshold = await _kurrentDbClient.ReadAllAsync( Direction.Backwards, Position.End, maxCount: (long)_liveEventThreshold) @@ -401,7 +401,7 @@ private void Init( } : async () => { - var eventsWithinThreshold = await _eventStoreClient.ReadStreamAsync( + var eventsWithinThreshold = await _kurrentDbClient.ReadStreamAsync( Direction.Backwards, _streamName, StreamPosition.End, @@ -414,7 +414,7 @@ private void Init( }; } - private void SubscriptionDropped(StreamSubscription eventStoreCatchUpSubscription, SubscriptionDroppedReason subscriptionDropReason, Exception ex) + private void SubscriptionDropped(StreamSubscription streamSubscription, SubscriptionDroppedReason subscriptionDropReason, Exception ex) { if (ex != null) { @@ -470,7 +470,7 @@ private void ProcessEvent(ResolvedEvent resolvedEvent) WriteCheckpoint(checkpointNumber); _logger.LogTrace( - "Event dispatched from Eventstore subscriber ({0}/{1})", + "Event dispatched from subscriber ({0}/{1})", resolvedEvent.Event.EventStreamId, resolvedEvent.Event.EventNumber); } @@ -478,7 +478,7 @@ private void ProcessEvent(ResolvedEvent resolvedEvent) { _logger.LogError( ex, - "Error dispatching event from Event Store subscriber ({0}/{1})", + "Error dispatching event from subscriber ({0}/{1})", resolvedEvent.Event.EventStreamId, resolvedEvent.Event.EventNumber); } diff --git a/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj b/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj index e507129..a1d3180 100644 --- a/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj +++ b/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj @@ -11,7 +11,7 @@ https://github.com/qphl/MessageDispatch.KurrentDB/blob/master/LICENSE https://github.com/qphl/MessageDispatch.KurrentDB https://github.com/qphl/MessageDispatch.KurrentDB - Message Dispatching, Event Sourcing, EventStore, KurrentDB + Message Dispatching, Event Sourcing, KurrentDB PharmaxoScientific.MessageDispatch.KurrentDB PharmaxoScientific.MessageDispatch.KurrentDB A package to use KurrentDB to get Events to Dispatch using PharmaxoScientific.MessageDispatch.KurrentDB. From 2ea2da59cb37f76da3350cca5bd1dd8a96a43563 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Thu, 29 May 2025 17:06:47 +0100 Subject: [PATCH 13/33] Redoing liveness logic --- .../KurrentDbSubscriber.cs | 357 ++++++------------ 1 file changed, 119 insertions(+), 238 deletions(-) diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index 07fea79..7fc40ba 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -7,6 +7,7 @@ using CorshamScience.MessageDispatch.Core; using KurrentDB.Client; using Microsoft.Extensions.Logging; +using static KurrentDB.Client.KurrentDBClient; namespace PharmaxoScientific.MessageDispatch.KurrentDB; @@ -17,20 +18,15 @@ public class KurrentDbSubscriber { private const string AllStreamName = "$all"; private const uint CheckpointInterval = 1; - private readonly WriteThroughFileCheckpoint _checkpoint; - private readonly object _subscriptionLock = new(); + private readonly WriteThroughFileCheckpoint _checkpoint; private KurrentDBClient _kurrentDbClient; private ulong? _startingPosition; - private StreamSubscription _subscription; private string _streamName; private bool _liveOnly; - private bool _isSubscribed; - private bool _isSubscriptionLive; private bool _subscribeToAll; private ulong? _lastProcessedEventPosition; - private ulong _actualEndOfStreamPosition; - private ulong _liveEventThreshold; - private ulong _liveThresholdPosition; + private ulong _actualEndOfStreamPosition; + private CancellationTokenSource _cts; private DateTime _lastStreamPositionTimestamp; private Func _setLastPositions; @@ -42,17 +38,15 @@ private KurrentDbSubscriber( IDispatcher dispatcher, string streamName, ILogger logger, - ulong? startingPosition, - ulong liveEventThreshold) - => Init(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); + ulong? startingPosition) + => Init(kurrentDbClient, dispatcher, streamName, logger, startingPosition); private KurrentDbSubscriber( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, string streamName, - string checkpointFilePath, - ulong liveEventThreshold) + string checkpointFilePath) { _checkpoint = new WriteThroughFileCheckpoint(checkpointFilePath, -1); var initialCheckpointPosition = _checkpoint.Read(); @@ -63,16 +57,15 @@ private KurrentDbSubscriber( startingPosition = (ulong)initialCheckpointPosition; } - Init(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold, startingPosition); + Init(kurrentDbClient, dispatcher, streamName, logger, startingPosition); } private KurrentDbSubscriber( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, - ILogger logger, - ulong liveEventThreshold) - => Init(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold, liveOnly: true); + ILogger logger) + => Init(kurrentDbClient, dispatcher, streamName, logger, liveOnly: true); /// /// Gets a new catchup progress object. @@ -82,12 +75,12 @@ public CatchupProgress CatchupProgress { get { - var lastStreamPosition = GetLastPositions().Result; + var lastStreamPosition = GetEndOfStreamPosition().Result; return new CatchupProgress( _lastProcessedEventPosition ?? 0, _streamName, - lastStreamPosition.actualEndOfStreamPosition, + lastStreamPosition, _startingPosition ?? 0, _subscribeToAll); } @@ -97,29 +90,7 @@ public CatchupProgress CatchupProgress /// Gets a value indicating whether the view model is ready or not. /// /// Returns true if catchup is within threshold. - public bool IsLive - { - get - { - // if we aren't subscribed, it doesn't count as live - if (!_isSubscribed) - { - return false; - } - - // if we are still subscribed, and we have ever been live, we are still live - if (_isSubscribed && _isSubscriptionLive) - { - return true; - } - - var lastStreamPosition = GetLastPositions().Result; - - _isSubscriptionLive = (_liveOnly && _lastProcessedEventPosition is null && _isSubscribed) || - _lastProcessedEventPosition >= lastStreamPosition.liveThresholdPosition; - return _isSubscriptionLive; - } - } + public bool IsLive { get; set; } = false; /// /// Creates a live KurrentDB subscription. @@ -128,16 +99,14 @@ public bool IsLive /// Dispatcher. /// Stream name to push events into. /// Logger. - /// Proximity to end of stream before subscription considered live. /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateLiveSubscription( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, string streamName, - ILogger logger, - ulong liveEventThreshold = 10) - => new KurrentDbSubscriber(kurrentDbClient, dispatcher, streamName, logger, liveEventThreshold); + ILogger logger) + => new KurrentDbSubscriber(kurrentDbClient, dispatcher, streamName, logger); /// /// Creates an KurrentDB catchup subscription using a checkpoint file. @@ -147,7 +116,6 @@ public static KurrentDbSubscriber CreateLiveSubscription( /// Stream name to push events into. /// Logger. /// Path of the checkpoint file. - /// Proximity to end of stream before subscription considered live. /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( @@ -155,9 +123,8 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( IDispatcher dispatcher, string streamName, ILogger logger, - string checkpointFilePath, - ulong liveEventThreshold = 10) - => new KurrentDbSubscriber(kurrentDbClient, dispatcher, logger, streamName, checkpointFilePath, liveEventThreshold); + string checkpointFilePath) + => new KurrentDbSubscriber(kurrentDbClient, dispatcher, logger, streamName, checkpointFilePath); /// /// Creates an KurrentDB catchup subscription from a position. @@ -167,7 +134,6 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( /// Stream name to push events into. /// Logger. /// Starting Position. - /// Proximity to end of stream before subscription considered live. /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( @@ -175,9 +141,8 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( IDispatcher dispatcher, string streamName, ILogger logger, - ulong? startingPosition, - ulong liveEventThreshold = 10) - => new KurrentDbSubscriber(kurrentDbClient, dispatcher, streamName, logger, startingPosition, liveEventThreshold); + ulong? startingPosition) + => new KurrentDbSubscriber(kurrentDbClient, dispatcher, streamName, logger, startingPosition); /// /// Creates an KurrentDB catchup subscription that is subscribed to all from the start. @@ -185,20 +150,17 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( /// KurrentDB connection. /// Dispatcher. /// Logger. - /// Proximity to end of stream before subscription considered live. /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, - ILogger logger, - ulong liveEventThreshold = 10) + ILogger logger) => new KurrentDbSubscriber( kurrentDbClient, dispatcher, AllStreamName, - logger, - liveEventThreshold); + logger); /// /// Creates an KurrentDB catchup subscription that is subscribed to all from a position. @@ -207,22 +169,19 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( /// Dispatcher. /// Logger. /// Starting Position. - /// Proximity to end of stream before subscription considered live. /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, - ulong? startingPosition, - ulong liveEventThreshold = 10) + ulong? startingPosition) => new KurrentDbSubscriber( kurrentDbClient, dispatcher, AllStreamName, logger, - startingPosition, - liveEventThreshold); + startingPosition); /// /// Creates an KurrentDB catchup subscription subscribed to all using a checkpoint file. @@ -231,139 +190,128 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPo /// Dispatcher. /// Logger. /// Path of the checkpoint file. - /// Proximity to end of stream before subscription considered live. /// A new KurrentDbSubscriber object. // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, ILogger logger, - string checkpointFilePath, - ulong liveEventThreshold = 10) + string checkpointFilePath) => new KurrentDbSubscriber( kurrentDbClient, dispatcher, logger, AllStreamName, - checkpointFilePath, - liveEventThreshold); + checkpointFilePath); /// /// Start the subscriber. /// // ReSharper disable once MemberCanBePrivate.Global - public void Start() + public async void Start() { + _cts = new CancellationTokenSource(); + while (true) { - _isSubscribed = false; + try + { + var subscription = CreateSubscription(); + _logger.LogInformation("Subscribed to '{StreamName}'", _streamName); - try - { - Monitor.Enter(_subscriptionLock); - - KillSubscription(); - - // No synchronization context is needed to disable synchronization context. - // That enables running asynchronous method not causing deadlocks. - // As this is a background process then we don't need to have async context here. - using (NoSynchronizationContextScope.Enter()) - { - var filterOptions = new SubscriptionFilterOptions( - EventTypeFilter.ExcludeSystemEvents(), - CheckpointInterval, - checkpointReached: CheckpointReached); - const bool resolveLinkTos = true; - - Task Appeared( - StreamSubscription streamSubscription, - ResolvedEvent e, - CancellationToken cancellationToken) => - EventAppeared(e); - - switch (_liveOnly) - { - case true when !_subscribeToAll: - _subscription = _kurrentDbClient.SubscribeToStreamAsync( - _streamName, - FromStream.End, - Appeared, - resolveLinkTos, - SubscriptionDropped).Result; - break; - case false when !_subscribeToAll: - { - var fromStream = _startingPosition.HasValue ? - FromStream.After(new StreamPosition(_startingPosition.Value)) : - FromStream.Start; - - _subscription = _kurrentDbClient.SubscribeToStreamAsync( - _streamName, - fromStream, - Appeared, - resolveLinkTos, - SubscriptionDropped).Result; - break; - } - - case true when _subscribeToAll: - _subscription = _kurrentDbClient.SubscribeToAllAsync( - FromAll.End, - Appeared, - resolveLinkTos, - SubscriptionDropped, - filterOptions) - .Result; - break; - case false when _subscribeToAll: - var fromAll = _startingPosition.HasValue ? - FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : - FromAll.Start; - - _subscription = _kurrentDbClient.SubscribeToAllAsync( - fromAll, - Appeared, - resolveLinkTos, - SubscriptionDropped, - filterOptions) - .Result; - break; - } + await foreach (var message in subscription.Messages) + { + switch (message) + { + case StreamMessage.Event(var @event): + ProcessEvent(@event); + + var lastProcessedEventPosition = GetLastProcessedPosition(@event); + + if (_liveOnly && _lastProcessedEventPosition is null) + { + _startingPosition = lastProcessedEventPosition; + } + + _lastProcessedEventPosition = lastProcessedEventPosition; + break; + case StreamMessage.AllStreamCheckpointReached(var allPosition): + _lastProcessedEventPosition = allPosition.CommitPosition; + WriteCheckpoint((ulong)_lastProcessedEventPosition); + break; + case StreamMessage.CaughtUp: + _logger.LogInformation("Stream caught up: {0}", _streamName); + IsLive = true; + break; + case StreamMessage.FellBehind: + _logger.LogWarning("Stream falling behind: {0}", _streamName); + IsLive = false; + break; + } } - - _isSubscribed = true; - _logger.LogInformation("Subscribed to '{StreamName}'", _streamName); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to resubscribe to '{StreamName}' dropped with '{ExceptionMessage}{ExceptionStackTrace}'", _streamName, ex.Message, ex.StackTrace); - } - finally - { - Monitor.Exit(_subscriptionLock); } - - if (_isSubscribed) - { - break; + // User initiated drop, do not resubscribe + catch (OperationCanceledException ex) + { + IsLive = false; + _logger.LogInformation(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.Disposed); + break; + } + // User initiated drop, do not resubscribe + catch (ObjectDisposedException ex) + { + IsLive = false; + _logger.LogInformation(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.Disposed); + break; + } + catch (Exception ex) + { + IsLive = false; + _logger.LogError(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.SubscriberError); + Console.WriteLine(ex); } // Sleep between reconnections to not flood the database or not kill the CPU with infinite loop // Randomness added to reduce the chance of multiple subscriptions trying to reconnect at the same time - Thread.Sleep(1000 + new Random((int)DateTime.UtcNow.Ticks).Next(1000)); + await Task.Delay(1000 + new Random((int)DateTime.UtcNow.Ticks).Next(1000)); } - } - + } + + private StreamSubscriptionResult CreateSubscription() + { + var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents(), CheckpointInterval); + + const bool resolveLinkTos = true; + + if (_subscribeToAll) + { + var subscriptionStart = FromAll.End; + if (!_liveOnly) + { + subscriptionStart = _startingPosition.HasValue ? FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : FromAll.Start; + } + + return _kurrentDbClient.SubscribeToAll(subscriptionStart, resolveLinkTos, filterOptions, cancellationToken: _cts.Token); + } + else + { + var subscriptionStart = FromStream.End; + if (!_liveOnly) + { + subscriptionStart = _startingPosition.HasValue ? FromStream.After(new StreamPosition(_startingPosition.Value)) : FromStream.Start; + } + + return _kurrentDbClient.SubscribeToStream(_streamName, FromStream.End, resolveLinkTos, cancellationToken: _cts.Token); + } + } + /// /// Shut down the subscription. /// - // ReSharper disable once UnusedMember.Global + // ReSharper disable once UnusedMember.Global public void ShutDown() { - lock (_subscriptionLock) - { - KillSubscription(); - } + _cts.Cancel(); } private void Init( @@ -371,7 +319,6 @@ private void Init( IDispatcher dispatcher, string streamName, ILogger logger, - ulong liveEventThreshold, ulong? startingPosition = null, bool liveOnly = false) { @@ -383,8 +330,6 @@ private void Init( _kurrentDbClient = connection; _liveOnly = liveOnly; _subscribeToAll = streamName == AllStreamName; - _liveEventThreshold = liveEventThreshold; - _liveThresholdPosition = StreamPosition.End; _lastStreamPositionTimestamp = DateTime.MinValue; _setLastPositions = _subscribeToAll @@ -393,10 +338,10 @@ private void Init( var eventsWithinThreshold = await _kurrentDbClient.ReadAllAsync( Direction.Backwards, Position.End, - maxCount: (long)_liveEventThreshold) + maxCount: 1, + resolveLinkTos: false) .ToListAsync(); - _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEvent.Position.CommitPosition; _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEvent.Position.CommitPosition; } : async () => @@ -405,56 +350,14 @@ private void Init( Direction.Backwards, _streamName, StreamPosition.End, - maxCount: (long)_liveEventThreshold, + maxCount: 1, resolveLinkTos: false) .ToListAsync(); - _liveThresholdPosition = eventsWithinThreshold.Last().OriginalEventNumber.ToUInt64(); _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); }; } - private void SubscriptionDropped(StreamSubscription streamSubscription, SubscriptionDroppedReason subscriptionDropReason, Exception ex) - { - if (ex != null) - { - _logger.LogInformation(ex, "Event Store subscription dropped {0}", subscriptionDropReason.ToString()); - } - else - { - _logger.LogInformation("Event Store subscription dropped {0}", subscriptionDropReason.ToString()); - } - - if (subscriptionDropReason == SubscriptionDroppedReason.Disposed) - { - _logger.LogInformation("Not attempting to restart subscription was disposed. Subscription is dead."); - return; - } - - _isSubscribed = false; - - // if the subscription drops, set its 'liveness' to false - _isSubscriptionLive = false; - _startingPosition = _lastProcessedEventPosition; - Start(); - } - - private Task EventAppeared(ResolvedEvent resolvedEvent) - { - ProcessEvent(resolvedEvent); - - var lastProcessedEventPosition = GetLastProcessedPosition(resolvedEvent); - - if (_liveOnly && _lastProcessedEventPosition is null) - { - _startingPosition = lastProcessedEventPosition; - } - - _lastProcessedEventPosition = lastProcessedEventPosition; - - return Task.CompletedTask; - } - private void ProcessEvent(ResolvedEvent resolvedEvent) { if (resolvedEvent.Event == null || resolvedEvent.Event.EventType.StartsWith("$")) @@ -510,38 +413,16 @@ private void WriteCheckpoint(ulong checkpointNumber) _logger.LogTrace("Checkpoint written. Checkpoint number {CheckpointNumber}", checkpointNumber); } - private async Task<(ulong liveThresholdPosition, ulong actualEndOfStreamPosition)> GetLastPositions() + private async Task GetEndOfStreamPosition() { var streamPositionIsStale = (DateTime.UtcNow - _lastStreamPositionTimestamp) > TimeSpan.FromSeconds(10); - if (_isSubscribed && streamPositionIsStale) + if (!_cts.Token.IsCancellationRequested && streamPositionIsStale) { await _setLastPositions(); _lastStreamPositionTimestamp = DateTime.UtcNow; } - return (_liveThresholdPosition, _actualEndOfStreamPosition); - } - - private void KillSubscription() - { - if (_subscription != null) - { - _subscription.Dispose(); - _subscription = null; - } - - _isSubscribed = false; - } - - private Task CheckpointReached( - StreamSubscription streamSubscription, - Position position, - CancellationToken cancellationToken) - { - _lastProcessedEventPosition = position.CommitPosition; - WriteCheckpoint((ulong)_lastProcessedEventPosition); - - return Task.CompletedTask; + return _actualEndOfStreamPosition; } } From 610b2892059d5f54a763199b285263aa5db01c4b Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Thu, 29 May 2025 17:08:06 +0100 Subject: [PATCH 14/33] Renamed solution from KurrentBD to KurrentDB --- ...essageDispatch.KurrentBD.sln => MessageDispatch.KurrentDB.sln} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{MessageDispatch.KurrentBD.sln => MessageDispatch.KurrentDB.sln} (100%) diff --git a/src/MessageDispatch.KurrentBD.sln b/src/MessageDispatch.KurrentDB.sln similarity index 100% rename from src/MessageDispatch.KurrentBD.sln rename to src/MessageDispatch.KurrentDB.sln From 4a3dc0a854ec6392fc4cc78fb4794a86bd9d45c7 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Thu, 29 May 2025 17:10:48 +0100 Subject: [PATCH 15/33] Fixed typo in build cmd script --- build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index 313f4c5..ca0b654 100644 --- a/build.cmd +++ b/build.cmd @@ -9,7 +9,7 @@ SET TAG=%TAG:tags/=% dotnet restore .\src\MessageDispatch.KurrentDB.sln -PackagesDirectory .\src\packages -Verbosity detailed -dotnet format .\src\MessageDispatch.KurrentBD.sln --severity warn --verify-no-changes -v diag +dotnet format .\src\MessageDispatch.KurrentDB.sln --severity warn --verify-no-changes -v diag IF %errorlevel% neq 0 EXIT /B %errorlevel% dotnet pack .\src\MessageDispatch.KurrentDB\MessageDispatch.KurrentDB.csproj -o .\dist -p:Version="%VERSION%" -p:PackageVersion="%VERSION%" -p:Tag="%TAG%" -c Release \ No newline at end of file From f05c1d7a1e5f08dbfe751231e7eab92782a056f0 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Fri, 30 May 2025 09:28:28 +0100 Subject: [PATCH 16/33] Altered the dotnet pack directory in build.sh --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 04978d0..7b920af 100644 --- a/build.sh +++ b/build.sh @@ -9,4 +9,4 @@ if [ -n "$2" ]; then tag="$2" fi tag=${tag/tags\//} -dotnet pack .\\src\\eventstore\\kurrent.csproj -o .\\dist -p:Version="$version" -p:PackageVersion="$version" -p:Tag="$tag" -c Release \ No newline at end of file +dotnet pack .\\src\\MessageDispatch.KurrentDB\\MessageDispatch.KurrentDB.csproj -o .\\dist -p:Version="$version" -p:PackageVersion="$version" -p:Tag="$tag" -c Release \ No newline at end of file From 16f30a08f74ea4089aa99401cfee36f0de7d0a95 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Fri, 30 May 2025 11:59:07 +0100 Subject: [PATCH 17/33] Handled Rider Warnings --- .../KurrentDbSubscriber.cs | 186 +++++++++--------- 1 file changed, 90 insertions(+), 96 deletions(-) diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index 7fc40ba..cca6cc4 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -18,14 +18,14 @@ public class KurrentDbSubscriber { private const string AllStreamName = "$all"; private const uint CheckpointInterval = 1; - private readonly WriteThroughFileCheckpoint _checkpoint; + private readonly WriteThroughFileCheckpoint _checkpoint; private KurrentDBClient _kurrentDbClient; private ulong? _startingPosition; private string _streamName; private bool _liveOnly; private bool _subscribeToAll; private ulong? _lastProcessedEventPosition; - private ulong _actualEndOfStreamPosition; + private ulong _actualEndOfStreamPosition; private CancellationTokenSource _cts; private DateTime _lastStreamPositionTimestamp; private Func _setLastPositions; @@ -33,6 +33,11 @@ public class KurrentDbSubscriber private IDispatcher _dispatcher; private ILogger _logger; + /// + /// Gets a value indicating whether the view model is ready or not. + /// + public bool IsLive; + private KurrentDbSubscriber( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, @@ -86,12 +91,6 @@ public CatchupProgress CatchupProgress } } - /// - /// Gets a value indicating whether the view model is ready or not. - /// - /// Returns true if catchup is within threshold. - public bool IsLive { get; set; } = false; - /// /// Creates a live KurrentDB subscription. /// @@ -214,60 +213,60 @@ public async void Start() while (true) { - try - { + try + { var subscription = CreateSubscription(); _logger.LogInformation("Subscribed to '{StreamName}'", _streamName); - await foreach (var message in subscription.Messages) - { - switch (message) - { - case StreamMessage.Event(var @event): - ProcessEvent(@event); - - var lastProcessedEventPosition = GetLastProcessedPosition(@event); - - if (_liveOnly && _lastProcessedEventPosition is null) - { - _startingPosition = lastProcessedEventPosition; - } - - _lastProcessedEventPosition = lastProcessedEventPosition; - break; - case StreamMessage.AllStreamCheckpointReached(var allPosition): - _lastProcessedEventPosition = allPosition.CommitPosition; - WriteCheckpoint((ulong)_lastProcessedEventPosition); - break; - case StreamMessage.CaughtUp: - _logger.LogInformation("Stream caught up: {0}", _streamName); - IsLive = true; - break; - case StreamMessage.FellBehind: - _logger.LogWarning("Stream falling behind: {0}", _streamName); - IsLive = false; - break; - } + await foreach (var message in subscription.Messages) + { + switch (message) + { + case StreamMessage.Event(var @event): + ProcessEvent(@event); + + var lastProcessedEventPosition = GetLastProcessedPosition(@event); + + if (_liveOnly && _lastProcessedEventPosition is null) + { + _startingPosition = lastProcessedEventPosition; + } + + _lastProcessedEventPosition = lastProcessedEventPosition; + break; + case StreamMessage.AllStreamCheckpointReached(var allPosition): + _lastProcessedEventPosition = allPosition.CommitPosition; + WriteCheckpoint((ulong)_lastProcessedEventPosition); + break; + case StreamMessage.CaughtUp: + _logger.LogInformation("Stream caught up: {0}", _streamName); + IsLive = true; + break; + case StreamMessage.FellBehind: + _logger.LogWarning("Stream falling behind: {0}", _streamName); + IsLive = false; + break; + } } } // User initiated drop, do not resubscribe - catch (OperationCanceledException ex) - { - IsLive = false; - _logger.LogInformation(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.Disposed); - break; - } - // User initiated drop, do not resubscribe - catch (ObjectDisposedException ex) - { - IsLive = false; - _logger.LogInformation(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.Disposed); - break; - } - catch (Exception ex) - { - IsLive = false; - _logger.LogError(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.SubscriberError); + catch (OperationCanceledException ex) + { + IsLive = false; + _logger.LogInformation(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.Disposed); + break; + } + // User initiated drop, do not resubscribe + catch (ObjectDisposedException ex) + { + IsLive = false; + _logger.LogInformation(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.Disposed); + break; + } + catch (Exception ex) + { + IsLive = false; + _logger.LogError(ex, "Event Store subscription dropped {0}", SubscriptionDroppedReason.SubscriberError); Console.WriteLine(ex); } @@ -275,44 +274,41 @@ public async void Start() // Randomness added to reduce the chance of multiple subscriptions trying to reconnect at the same time await Task.Delay(1000 + new Random((int)DateTime.UtcNow.Ticks).Next(1000)); } - } - - private StreamSubscriptionResult CreateSubscription() - { - var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents(), CheckpointInterval); - - const bool resolveLinkTos = true; - - if (_subscribeToAll) - { - var subscriptionStart = FromAll.End; - if (!_liveOnly) - { - subscriptionStart = _startingPosition.HasValue ? FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : FromAll.Start; - } - - return _kurrentDbClient.SubscribeToAll(subscriptionStart, resolveLinkTos, filterOptions, cancellationToken: _cts.Token); - } - else - { - var subscriptionStart = FromStream.End; - if (!_liveOnly) - { - subscriptionStart = _startingPosition.HasValue ? FromStream.After(new StreamPosition(_startingPosition.Value)) : FromStream.Start; - } - - return _kurrentDbClient.SubscribeToStream(_streamName, FromStream.End, resolveLinkTos, cancellationToken: _cts.Token); - } - } - + } + + private StreamSubscriptionResult CreateSubscription() + { + var filterOptions = new SubscriptionFilterOptions(EventTypeFilter.ExcludeSystemEvents(), checkpointInterval: CheckpointInterval); + + const bool resolveLinkTos = true; + + if (_subscribeToAll) + { + var subscriptionStart = FromAll.End; + if (!_liveOnly) + { + subscriptionStart = _startingPosition.HasValue ? FromAll.After(new Position(_startingPosition.Value, _startingPosition.Value)) : FromAll.Start; + } + + return _kurrentDbClient.SubscribeToAll(subscriptionStart, resolveLinkTos, filterOptions, cancellationToken: _cts.Token); + } + else + { + var subscriptionStart = FromStream.End; + if (!_liveOnly) + { + subscriptionStart = _startingPosition.HasValue ? FromStream.After(new StreamPosition(_startingPosition.Value)) : FromStream.Start; + } + + return _kurrentDbClient.SubscribeToStream(_streamName, subscriptionStart, resolveLinkTos, cancellationToken: _cts.Token); + } + } + /// /// Shut down the subscription. /// - // ReSharper disable once UnusedMember.Global - public void ShutDown() - { - _cts.Cancel(); - } + // ReSharper disable once UnusedMember.Global + public void ShutDown() => _cts.Cancel(); private void Init( KurrentDBClient connection, @@ -360,7 +356,7 @@ private void Init( private void ProcessEvent(ResolvedEvent resolvedEvent) { - if (resolvedEvent.Event == null || resolvedEvent.Event.EventType.StartsWith("$")) + if (resolvedEvent.Event.EventType.StartsWith("$")) { return; } @@ -387,12 +383,10 @@ private void ProcessEvent(ResolvedEvent resolvedEvent) } } - private ulong GetLastProcessedPosition(ResolvedEvent resolvedEvent) - { - return _subscribeToAll + private ulong GetLastProcessedPosition(ResolvedEvent resolvedEvent) => + _subscribeToAll ? resolvedEvent.OriginalEvent.Position.CommitPosition : resolvedEvent.OriginalEventNumber.ToUInt64(); - } private void WriteCheckpoint(ulong checkpointNumber) { From 142a6c13182c107fa0fd5ccd9c2237a80363abbc Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Fri, 30 May 2025 15:40:11 +0100 Subject: [PATCH 18/33] Initial test setup --- .../MessageDispatch.KurrentDB.Tests.csproj | 29 ++++ .../SubscriberTests.cs | 142 ++++++++++++++++++ src/MessageDispatch.KurrentDB.sln | 6 + 3 files changed, 177 insertions(+) create mode 100644 src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj create mode 100644 src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs diff --git a/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj b/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj new file mode 100644 index 0000000..0589167 --- /dev/null +++ b/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs new file mode 100644 index 0000000..6a8ac83 --- /dev/null +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -0,0 +1,142 @@ +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using CorshamScience.MessageDispatch.Core; +using DotNet.Testcontainers.Builders; +using KurrentDB.Client; +using Microsoft.Extensions.Logging.Abstractions; +using PharmaxoScientific.MessageDispatch.KurrentDB; +using Testcontainers.EventStoreDb; + +namespace MessageDispatch.KurrentDB.Tests; + +public class SubscriberTests +{ + private const string StreamName = "stream1"; + private string _connectionString; + + [SetUp] + public async Task Setup() + { + const int eventStoreHostPort = 1234; + const string eventStoreVersion = "23.10.0"; + + var eventStoreImageName = RuntimeInformation.OSArchitecture == Architecture.Arm64 + ? $"ghcr.io/eventstore/eventstore:{eventStoreVersion}-alpha-arm64v8" + : $"eventstore/eventstore:{eventStoreVersion}-bookworm-slim"; + + var eventStoreContainer = BuildEventStoreContainer(eventStoreImageName, eventStoreHostPort); + await eventStoreContainer.StartAsync(); + + var mappedHostPort = eventStoreContainer.GetMappedPublicPort(eventStoreHostPort); + _connectionString = $"esdb://admin:changeit@localhost:{mappedHostPort}?tls=false"; + } + + [Test] + public async Task CreateLiveSubscription_GivenNoEventsInStreamWhenNewEventsAdded_DispatchesEventsAndBecomesLive() + { + var kurrentDbClient = new KurrentDBClient(KurrentDBClientSettings.Create(_connectionString)); + + var dispatcher = new AwaitableDispatcherSpy(); + var subscriber = KurrentDbSubscriber.CreateLiveSubscription( + kurrentDbClient, + dispatcher, + StreamName, + new NullLogger()); + + subscriber.Start(); + + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List events = [event1, event2, event3]; + + await AppendEventsToStreamAsync(event1, event2, event3); + await dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + + var deserializedDispatchedEvents = + dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); + Assert.That(subscriber.IsLive); + }); + } + + // ReSharper disable once NotAccessedPositionalProperty.Local + private record SimpleEvent(Guid Id) + { + public static SimpleEvent Create() => new(Guid.NewGuid()); + } + + private class AwaitableDispatcherSpy : IDispatcher + { + public List DispatchedEvents { get; } = []; + + public void Dispatch(ResolvedEvent message) => DispatchedEvents.Add(message); + + public Task WaitForEventsToBeDispatched(params object[] events) + { + if (events.Length == 0) + { + return Task.CompletedTask; + } + + var iterations = 0; + while (DispatchedEvents.Count != events.Length) + { + Thread.Sleep(100); + iterations++; + + if (iterations > 10) + { + throw new TimeoutException("Expected events weren't dispatched within the allotted time."); + } + } + + return Task.CompletedTask; + } + } + + private static T? DeserializeEventData(ResolvedEvent message) => + JsonSerializer.Deserialize(Encoding.UTF8.GetString(message.Event.Data.Span)); + + private static EventStoreDbContainer BuildEventStoreContainer(string imageName, int hostPort) => + new EventStoreDbBuilder() + .WithImage(imageName) + .WithCleanUp(true) + .WithPortBinding(hostPort, true) + .WithEnvironment(new Dictionary + { + { "EVENTSTORE_INSECURE", "true" }, + { "EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true" }, + { "EVENTSTORE_ENABLE_EXTERNAL_TCP", "true" }, + { "EVENTSTORE_HTTP_PORT", hostPort.ToString() }, + { "EVENTSTORE_RUN_PROJECTIONS", "All" }, + }) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(hostPort)) + .Build(); + + private static EventData ToEventData(object data, JsonSerializerOptions? options = null) + { + var metaData = new { ClrType = data.GetType().AssemblyQualifiedName, }; + + var type = data.GetType().Name; + + return new EventData( + Uuid.NewUuid(), + type, + Encoding.UTF8.GetBytes(JsonSerializer.Serialize(data, options)), + Encoding.UTF8.GetBytes(JsonSerializer.Serialize(metaData, options))); + } + + private async Task AppendEventsToStreamAsync(params object[] events) + { + var eventData = events.Select(e => ToEventData(e)); + var client = new KurrentDBClient(KurrentDBClientSettings.Create(_connectionString)); + + await client.AppendToStreamAsync(StreamName, StreamState.Any, eventData); + } +} diff --git a/src/MessageDispatch.KurrentDB.sln b/src/MessageDispatch.KurrentDB.sln index 2d4c0c8..b502ac7 100644 --- a/src/MessageDispatch.KurrentDB.sln +++ b/src/MessageDispatch.KurrentDB.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.12.35931.192 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessageDispatch.KurrentDB", "MessageDispatch.KurrentDB\MessageDispatch.KurrentDB.csproj", "{8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessageDispatch.KurrentDB.Tests", "MessageDispatch.KurrentDB.Tests\MessageDispatch.KurrentDB.Tests.csproj", "{10045A11-D589-4A7F-BC17-C4CE508B04F4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU {8B3CD5D9-28DB-4823-8ACE-C5B9770FFE5B}.Release|Any CPU.Build.0 = Release|Any CPU + {10045A11-D589-4A7F-BC17-C4CE508B04F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10045A11-D589-4A7F-BC17-C4CE508B04F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10045A11-D589-4A7F-BC17-C4CE508B04F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10045A11-D589-4A7F-BC17-C4CE508B04F4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 096d593a896b4f2b4e0d98f383e8398ca5600c01 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Fri, 30 May 2025 16:09:23 +0100 Subject: [PATCH 19/33] Add (failing) test for catch up sub to all --- .../SubscriberTests.cs | 82 +++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 6a8ac83..c3bb578 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -14,6 +14,8 @@ public class SubscriberTests { private const string StreamName = "stream1"; private string _connectionString; + private KurrentDBClient _kurrentDbClient; + private AwaitableDispatcherSpy _dispatcher; [SetUp] public async Task Setup() @@ -30,20 +32,58 @@ public async Task Setup() var mappedHostPort = eventStoreContainer.GetMappedPublicPort(eventStoreHostPort); _connectionString = $"esdb://admin:changeit@localhost:{mappedHostPort}?tls=false"; + + _kurrentDbClient = new KurrentDBClient(KurrentDBClientSettings.Create(_connectionString)); + _dispatcher = new AwaitableDispatcherSpy(); } + [TearDown] + public async Task TearDown() => await _kurrentDbClient.DisposeAsync(); + [Test] public async Task CreateLiveSubscription_GivenNoEventsInStreamWhenNewEventsAdded_DispatchesEventsAndBecomesLive() { - var kurrentDbClient = new KurrentDBClient(KurrentDBClientSettings.Create(_connectionString)); + var subscriber = KurrentDbSubscriber.CreateLiveSubscription( + _kurrentDbClient, + _dispatcher, + StreamName, + new NullLogger()); + + subscriber.Start(); + + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List events = [event1, event2, event3]; - var dispatcher = new AwaitableDispatcherSpy(); + await AppendEventsToStreamAsync(event1, event2, event3); + await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); + Assert.That(subscriber.IsLive); + }); + } + + [Test] + public async Task CreateLiveSubscription_GivenExistingEventsInStreamWhenNewEventsAdded_DispatchesNewEventsAndBecomesLive() + { var subscriber = KurrentDbSubscriber.CreateLiveSubscription( - kurrentDbClient, - dispatcher, + _kurrentDbClient, + _dispatcher, StreamName, new NullLogger()); + var oldEvent1 = SimpleEvent.Create(); + var oldEvent2 = SimpleEvent.Create(); + + await AppendEventsToStreamAsync(oldEvent1, oldEvent2); + subscriber.Start(); var event1 = SimpleEvent.Create(); @@ -53,10 +93,40 @@ public async Task CreateLiveSubscription_GivenNoEventsInStreamWhenNewEventsAdded List events = [event1, event2, event3]; await AppendEventsToStreamAsync(event1, event2, event3); - await dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); + Assert.That(subscriber.IsLive); + }); + } + + [Test] + public async Task CreateCatchupSubscriptionSubscribedToAll_GivenEventsInStream_DispatchesEventsAndBecomesLive() + { + var subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAll( + _kurrentDbClient, + _dispatcher, + new NullLogger()); + + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List events = [event1, event2, event3]; + + await AppendEventsToStreamAsync(event1, event2, event3); + + subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); var deserializedDispatchedEvents = - dispatcher.DispatchedEvents.Select(DeserializeEventData); + _dispatcher.DispatchedEvents.Select(DeserializeEventData); Assert.Multiple(() => { From 8373278a1c6e4acb617f80ee6e1983eabd599d81 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Fri, 30 May 2025 16:18:36 +0100 Subject: [PATCH 20/33] Add test for CreateCatchupSubscriptionFromPosition --- .../SubscriberTests.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index c3bb578..45c1424 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -135,6 +135,67 @@ public async Task CreateCatchupSubscriptionSubscribedToAll_GivenEventsInStream_D }); } + [Test] + public async Task CreateCatchupSubscriptionSubscribedToAll_GivenNoEventsInStreamGivenNewEvents_DispatchesEventsAndBecomesLive() + { + var subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAll( + _kurrentDbClient, + _dispatcher, + new NullLogger()); + + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List events = [event1, event2, event3]; + + subscriber.Start(); + + await AppendEventsToStreamAsync(event1, event2, event3); + await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); + Assert.That(subscriber.IsLive); + }); + } + + [Test] + public async Task CreateCatchupSubscriptionFromPosition_GivenEventsInStreamAndStartPosition_DispatchesEventsFromPositionAndBecomesLive() + { + var subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionFromPosition( + _kurrentDbClient, + _dispatcher, + StreamName, + new NullLogger(), + 1); + + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List events = [event3]; + + await AppendEventsToStreamAsync(event1, event2, event3); + + subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); + Assert.That(subscriber.IsLive); + }); + } + // ReSharper disable once NotAccessedPositionalProperty.Local private record SimpleEvent(Guid Id) { From 68da149f4993dd82e0a4453e3e06a2bf07085b61 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Fri, 30 May 2025 16:20:14 +0100 Subject: [PATCH 21/33] Fix formatting --- src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 45c1424..8519846 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -1,3 +1,5 @@ +// Copyright (c) Pharmaxo. All rights reserved. + using System.Runtime.InteropServices; using System.Text; using System.Text.Json; From b4b7d82209f8d610c6dbb672d65070bb340adc78 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Fri, 30 May 2025 16:26:14 +0100 Subject: [PATCH 22/33] Cancel subscription after each test --- .../SubscriberTests.cs | 37 +++++++++++-------- .../KurrentDbSubscriber.cs | 5 --- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 8519846..6895b20 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -18,6 +18,7 @@ public class SubscriberTests private string _connectionString; private KurrentDBClient _kurrentDbClient; private AwaitableDispatcherSpy _dispatcher; + private KurrentDbSubscriber? _subscriber; [SetUp] public async Task Setup() @@ -40,18 +41,22 @@ public async Task Setup() } [TearDown] - public async Task TearDown() => await _kurrentDbClient.DisposeAsync(); + public async Task TearDown() + { + await _kurrentDbClient.DisposeAsync(); + _subscriber?.ShutDown(); + } [Test] public async Task CreateLiveSubscription_GivenNoEventsInStreamWhenNewEventsAdded_DispatchesEventsAndBecomesLive() { - var subscriber = KurrentDbSubscriber.CreateLiveSubscription( + _subscriber = KurrentDbSubscriber.CreateLiveSubscription( _kurrentDbClient, _dispatcher, StreamName, new NullLogger()); - subscriber.Start(); + _subscriber.Start(); var event1 = SimpleEvent.Create(); var event2 = SimpleEvent.Create(); @@ -68,14 +73,14 @@ public async Task CreateLiveSubscription_GivenNoEventsInStreamWhenNewEventsAdded Assert.Multiple(() => { Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); - Assert.That(subscriber.IsLive); + Assert.That(_subscriber.IsLive); }); } [Test] public async Task CreateLiveSubscription_GivenExistingEventsInStreamWhenNewEventsAdded_DispatchesNewEventsAndBecomesLive() { - var subscriber = KurrentDbSubscriber.CreateLiveSubscription( + _subscriber = KurrentDbSubscriber.CreateLiveSubscription( _kurrentDbClient, _dispatcher, StreamName, @@ -86,7 +91,7 @@ public async Task CreateLiveSubscription_GivenExistingEventsInStreamWhenNewEvent await AppendEventsToStreamAsync(oldEvent1, oldEvent2); - subscriber.Start(); + _subscriber.Start(); var event1 = SimpleEvent.Create(); var event2 = SimpleEvent.Create(); @@ -103,14 +108,14 @@ public async Task CreateLiveSubscription_GivenExistingEventsInStreamWhenNewEvent Assert.Multiple(() => { Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); - Assert.That(subscriber.IsLive); + Assert.That(_subscriber.IsLive); }); } [Test] public async Task CreateCatchupSubscriptionSubscribedToAll_GivenEventsInStream_DispatchesEventsAndBecomesLive() { - var subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAll( + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAll( _kurrentDbClient, _dispatcher, new NullLogger()); @@ -123,7 +128,7 @@ public async Task CreateCatchupSubscriptionSubscribedToAll_GivenEventsInStream_D await AppendEventsToStreamAsync(event1, event2, event3); - subscriber.Start(); + _subscriber.Start(); await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); @@ -133,14 +138,14 @@ public async Task CreateCatchupSubscriptionSubscribedToAll_GivenEventsInStream_D Assert.Multiple(() => { Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); - Assert.That(subscriber.IsLive); + Assert.That(_subscriber.IsLive); }); } [Test] public async Task CreateCatchupSubscriptionSubscribedToAll_GivenNoEventsInStreamGivenNewEvents_DispatchesEventsAndBecomesLive() { - var subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAll( + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAll( _kurrentDbClient, _dispatcher, new NullLogger()); @@ -151,7 +156,7 @@ public async Task CreateCatchupSubscriptionSubscribedToAll_GivenNoEventsInStream List events = [event1, event2, event3]; - subscriber.Start(); + _subscriber.Start(); await AppendEventsToStreamAsync(event1, event2, event3); await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); @@ -162,14 +167,14 @@ public async Task CreateCatchupSubscriptionSubscribedToAll_GivenNoEventsInStream Assert.Multiple(() => { Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); - Assert.That(subscriber.IsLive); + Assert.That(_subscriber.IsLive); }); } [Test] public async Task CreateCatchupSubscriptionFromPosition_GivenEventsInStreamAndStartPosition_DispatchesEventsFromPositionAndBecomesLive() { - var subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionFromPosition( + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionFromPosition( _kurrentDbClient, _dispatcher, StreamName, @@ -184,7 +189,7 @@ public async Task CreateCatchupSubscriptionFromPosition_GivenEventsInStreamAndSt await AppendEventsToStreamAsync(event1, event2, event3); - subscriber.Start(); + _subscriber.Start(); await _dispatcher.WaitForEventsToBeDispatched(event3); @@ -194,7 +199,7 @@ public async Task CreateCatchupSubscriptionFromPosition_GivenEventsInStreamAndSt Assert.Multiple(() => { Assert.That(deserializedDispatchedEvents, Is.EqualTo(events)); - Assert.That(subscriber.IsLive); + Assert.That(_subscriber.IsLive); }); } diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index cca6cc4..df79ffd 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -99,7 +99,6 @@ public CatchupProgress CatchupProgress /// Stream name to push events into. /// Logger. /// A new KurrentDbSubscriber object. - // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateLiveSubscription( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, @@ -134,7 +133,6 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( /// Logger. /// Starting Position. /// A new KurrentDbSubscriber object. - // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, @@ -150,7 +148,6 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionFromPosition( /// Dispatcher. /// Logger. /// A new KurrentDbSubscriber object. - // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, @@ -206,7 +203,6 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllUsingC /// /// Start the subscriber. /// - // ReSharper disable once MemberCanBePrivate.Global public async void Start() { _cts = new CancellationTokenSource(); @@ -307,7 +303,6 @@ private StreamSubscriptionResult CreateSubscription() /// /// Shut down the subscription. /// - // ReSharper disable once UnusedMember.Global public void ShutDown() => _cts.Cancel(); private void Init( From 4f20845ba9cf064dd065abff1dbed3b5ae7cf910 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Mon, 2 Jun 2025 09:12:21 +0100 Subject: [PATCH 23/33] Add test for CreateCatchupSubscriptionSubscribedToAllFromPosition --- .../SubscriberTests.cs | 37 ++++++++++++++++++- .../KurrentDbSubscriber.cs | 1 - 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 6895b20..600348b 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -203,6 +203,39 @@ public async Task CreateCatchupSubscriptionFromPosition_GivenEventsInStreamAndSt }); } + [Test] + public async Task CreateCatchupSubscriptionSubscribedToAllFromPosition_GivenEventsInStreamAndStartPosition_DispatchesEventsFromPositionAndBecomesLive() + { + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List eventsExpectedToBeDispatched = [event3]; + + await AppendEventsToStreamAsync(event1); + var startingPosition = await AppendEventsToStreamAsync(event2); + await AppendEventsToStreamAsync(event3); + + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAllFromPosition( + _kurrentDbClient, + _dispatcher, + new NullLogger(), + startingPosition.LogPosition.CommitPosition); + + _subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(eventsExpectedToBeDispatched)); + Assert.That(_subscriber.IsLive); + }); + } + // ReSharper disable once NotAccessedPositionalProperty.Local private record SimpleEvent(Guid Id) { @@ -270,11 +303,11 @@ private static EventData ToEventData(object data, JsonSerializerOptions? options Encoding.UTF8.GetBytes(JsonSerializer.Serialize(metaData, options))); } - private async Task AppendEventsToStreamAsync(params object[] events) + private async Task AppendEventsToStreamAsync(params object[] events) { var eventData = events.Select(e => ToEventData(e)); var client = new KurrentDBClient(KurrentDBClientSettings.Create(_connectionString)); - await client.AppendToStreamAsync(StreamName, StreamState.Any, eventData); + return await client.AppendToStreamAsync(StreamName, StreamState.Any, eventData); } } diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index df79ffd..db5b914 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -166,7 +166,6 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( /// Logger. /// Starting Position. /// A new KurrentDbSubscriber object. - // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPosition( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, From 24b9503a05ebf6110c0662e07fddbf6bfa2db6e3 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Mon, 2 Jun 2025 09:14:24 +0100 Subject: [PATCH 24/33] Fix CreateCatchupSubscriptionSubscribedToAll (actually start from beginning of All) --- src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index db5b914..2ddb444 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -156,7 +156,8 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( kurrentDbClient, dispatcher, AllStreamName, - logger); + logger, + 0); /// /// Creates an KurrentDB catchup subscription that is subscribed to all from a position. From 7800674b5c194606546082ecf23e56fc19ea275a Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Mon, 2 Jun 2025 10:36:17 +0100 Subject: [PATCH 25/33] Add tests for checkpoint subscriptions --- .../SubscriberTests.cs | 148 ++++++++++++++++++ .../KurrentDbSubscriber.cs | 2 - .../MessageDispatch.KurrentDB.csproj | 4 + 3 files changed, 152 insertions(+), 2 deletions(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 600348b..ec977bd 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -236,6 +236,154 @@ public async Task CreateCatchupSubscriptionSubscribedToAllFromPosition_GivenEven }); } + [Test] + public async Task CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint_GivenEventsInStreamAndNoExistingCheckpointFile_DispatchesAllEventsAndBecomesLive() + { + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List eventsExpectedToBeDispatched = [event1, event2, event3]; + + await AppendEventsToStreamAsync(event1); + await AppendEventsToStreamAsync(event2); + await AppendEventsToStreamAsync(event3); + + var checkpointFileName = Path.GetRandomFileName(); + + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( + _kurrentDbClient, + _dispatcher, + new NullLogger(), + checkpointFileName); + + _subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(eventsExpectedToBeDispatched)); + Assert.That(_subscriber.IsLive); + }); + } + + [Test] + public async Task CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint_GivenEventsInStreamAndExistingCheckpointFile_DispatchesEventsFromPositionAndBecomesLive() + { + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List eventsExpectedToBeDispatched = [event3]; + + await AppendEventsToStreamAsync(event1); + var startingPosition = await AppendEventsToStreamAsync(event2); + await AppendEventsToStreamAsync(event3); + + var checkpointFileName = Path.GetRandomFileName(); + var checkpoint = new WriteThroughFileCheckpoint(checkpointFileName); + checkpoint.Write((long)startingPosition.LogPosition.CommitPosition); + + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( + _kurrentDbClient, + _dispatcher, + new NullLogger(), + checkpointFileName); + + _subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(eventsExpectedToBeDispatched)); + Assert.That(_subscriber.IsLive); + }); + } + + + [Test] + public async Task CreateCatchupSubscriptionUsingCheckpoint_GivenEventsInStreamAndNoExistingCheckpointFile_DispatchesAllEventsAndBecomesLive() + { + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List eventsExpectedToBeDispatched = [event1, event2, event3]; + + await AppendEventsToStreamAsync(event1); + await AppendEventsToStreamAsync(event2); + await AppendEventsToStreamAsync(event3); + + var checkpointFileName = Path.GetRandomFileName(); + + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionUsingCheckpoint( + _kurrentDbClient, + _dispatcher, + StreamName, + new NullLogger(), + checkpointFileName); + + _subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event1, event2, event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(eventsExpectedToBeDispatched)); + Assert.That(_subscriber.IsLive); + }); + } + + [Test] + public async Task CreateCatchupSubscriptionUsingCheckpoint_GivenEventsInStreamAndExistingCheckpointFile_DispatchesEventsFromPositionAndBecomesLive() + { + var event1 = SimpleEvent.Create(); + var event2 = SimpleEvent.Create(); + var event3 = SimpleEvent.Create(); + + List eventsExpectedToBeDispatched = [event3]; + + await AppendEventsToStreamAsync(event1); + await AppendEventsToStreamAsync(event2); + await AppendEventsToStreamAsync(event3); + + var checkpointFileName = Path.GetRandomFileName(); + + var checkpoint = new WriteThroughFileCheckpoint(checkpointFileName); + checkpoint.Write(1); + + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionUsingCheckpoint( + _kurrentDbClient, + _dispatcher, + StreamName, + new NullLogger(), + checkpointFileName); + + _subscriber.Start(); + + await _dispatcher.WaitForEventsToBeDispatched(event3); + + var deserializedDispatchedEvents = + _dispatcher.DispatchedEvents.Select(DeserializeEventData); + + Assert.Multiple(() => + { + Assert.That(deserializedDispatchedEvents, Is.EqualTo(eventsExpectedToBeDispatched)); + Assert.That(_subscriber.IsLive); + }); + } + // ReSharper disable once NotAccessedPositionalProperty.Local private record SimpleEvent(Guid Id) { diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index 2ddb444..f0d7328 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -115,7 +115,6 @@ public static KurrentDbSubscriber CreateLiveSubscription( /// Logger. /// Path of the checkpoint file. /// A new KurrentDbSubscriber object. - // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionUsingCheckpoint( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, @@ -187,7 +186,6 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllFromPo /// Logger. /// Path of the checkpoint file. /// A new KurrentDbSubscriber object. - // ReSharper disable once UnusedMember.Global public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint( KurrentDBClient kurrentDbClient, IDispatcher dispatcher, diff --git a/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj b/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj index a1d3180..09f98b3 100644 --- a/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj +++ b/src/MessageDispatch.KurrentDB/MessageDispatch.KurrentDB.csproj @@ -43,4 +43,8 @@ + + + + From abbdc9983a98c0a76d549cfc3d6af671f0766d59 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Mon, 2 Jun 2025 10:39:29 +0100 Subject: [PATCH 26/33] dotnet format --- src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index ec977bd..384367d 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -308,7 +308,6 @@ public async Task CreateCatchupSubscriptionSubscribedToAllUsingCheckpoint_GivenE }); } - [Test] public async Task CreateCatchupSubscriptionUsingCheckpoint_GivenEventsInStreamAndNoExistingCheckpointFile_DispatchesAllEventsAndBecomesLive() { From 6c53ae8c1e90bcdca7eebf924561a39201c066ec Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:53:52 +0100 Subject: [PATCH 27/33] Update to use ES v24.10.5 --- src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 384367d..143673e 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -24,7 +24,7 @@ public class SubscriberTests public async Task Setup() { const int eventStoreHostPort = 1234; - const string eventStoreVersion = "23.10.0"; + const string eventStoreVersion = "24.10.5"; var eventStoreImageName = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? $"ghcr.io/eventstore/eventstore:{eventStoreVersion}-alpha-arm64v8" @@ -430,7 +430,6 @@ private static EventStoreDbContainer BuildEventStoreContainer(string imageName, { { "EVENTSTORE_INSECURE", "true" }, { "EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true" }, - { "EVENTSTORE_ENABLE_EXTERNAL_TCP", "true" }, { "EVENTSTORE_HTTP_PORT", hostPort.ToString() }, { "EVENTSTORE_RUN_PROJECTIONS", "All" }, }) From 9cbf3d217544bab85a3aada817895997deb27563 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 4 Jun 2025 11:33:56 +0100 Subject: [PATCH 28/33] PR Corrections --- .../KurrentDbSubscriber.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index cca6cc4..d2ea3d8 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -15,9 +15,13 @@ namespace PharmaxoScientific.MessageDispatch.KurrentDB; /// Subscriber for event store. /// public class KurrentDbSubscriber -{ +{ + /// + /// Setting this to 100 as 3200 records seems like a sensible balance between checking too often and too infrequently + /// https://docs.kurrent.io/clients/grpc/subscriptions.html#updating-checkpoints-at-regular-intervals + /// + private const uint CheckpointInterval = 100; private const string AllStreamName = "$all"; - private const uint CheckpointInterval = 1; private readonly WriteThroughFileCheckpoint _checkpoint; private KurrentDBClient _kurrentDbClient; private ulong? _startingPosition; @@ -331,18 +335,18 @@ private void Init( _setLastPositions = _subscribeToAll ? async () => { - var eventsWithinThreshold = await _kurrentDbClient.ReadAllAsync( + var lastEventFromStream = await _kurrentDbClient.ReadAllAsync( Direction.Backwards, Position.End, maxCount: 1, resolveLinkTos: false) .ToListAsync(); - _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEvent.Position.CommitPosition; + _actualEndOfStreamPosition = lastEventFromStream.First().OriginalEvent.Position.CommitPosition; } : async () => { - var eventsWithinThreshold = await _kurrentDbClient.ReadStreamAsync( + var lastEventFromStream = await _kurrentDbClient.ReadStreamAsync( Direction.Backwards, _streamName, StreamPosition.End, @@ -350,7 +354,7 @@ private void Init( resolveLinkTos: false) .ToListAsync(); - _actualEndOfStreamPosition = eventsWithinThreshold.First().OriginalEventNumber.ToUInt64(); + _actualEndOfStreamPosition = lastEventFromStream.First().OriginalEventNumber.ToUInt64(); }; } From 980793fb8cf30b4dae671c05b99feed61de6e731 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Wed, 4 Jun 2025 11:34:22 +0100 Subject: [PATCH 29/33] Pass null as starting position --- src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index f0d7328..a9c6875 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -156,7 +156,7 @@ public static KurrentDbSubscriber CreateCatchupSubscriptionSubscribedToAll( dispatcher, AllStreamName, logger, - 0); + null); /// /// Creates an KurrentDB catchup subscription that is subscribed to all from a position. From 29e2a6978e230335e6f22fd6c1d3d4fe93183ba6 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Wed, 4 Jun 2025 12:44:17 +0100 Subject: [PATCH 30/33] Build tests for 481 (currently failing) --- .../MessageDispatch.KurrentDB.Tests.csproj | 3 ++- .../SubscriberTests.cs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj b/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj index 0589167..126a79b 100644 --- a/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj +++ b/src/MessageDispatch.KurrentDB.Tests/MessageDispatch.KurrentDB.Tests.csproj @@ -1,12 +1,13 @@ - net8.0 enable enable false true + net8.0;net481 + latest diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 143673e..58aa2cc 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -383,10 +383,22 @@ public async Task CreateCatchupSubscriptionUsingCheckpoint_GivenEventsInStreamAn }); } - // ReSharper disable once NotAccessedPositionalProperty.Local - private record SimpleEvent(Guid Id) + private class SimpleEvent { + // ReSharper disable once UnusedAutoPropertyAccessor.Local + // ReSharper disable once MemberCanBePrivate.Local + public Guid Id { get; } + + // ReSharper disable once MemberCanBePrivate.Local + public SimpleEvent(Guid id) => Id = id; + public static SimpleEvent Create() => new(Guid.NewGuid()); + + public override bool Equals(object? obj) => obj is SimpleEvent other && Id.Equals(other.Id); + + public override int GetHashCode() => Id.GetHashCode(); + + public override string ToString() => Id.ToString(); } private class AwaitableDispatcherSpy : IDispatcher @@ -419,7 +431,7 @@ public Task WaitForEventsToBeDispatched(params object[] events) } private static T? DeserializeEventData(ResolvedEvent message) => - JsonSerializer.Deserialize(Encoding.UTF8.GetString(message.Event.Data.Span)); + JsonSerializer.Deserialize(Encoding.UTF8.GetString(message.Event.Data.Span.ToArray())); private static EventStoreDbContainer BuildEventStoreContainer(string imageName, int hostPort) => new EventStoreDbBuilder() From ebd686dec86fd4424ed56b62b93710f256362a5d Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Wed, 4 Jun 2025 16:16:47 +0100 Subject: [PATCH 31/33] Add test for and fix null ref on missing linked event --- .../SubscriberTests.cs | 53 +++++++++++++++++++ .../KurrentDbSubscriber.cs | 3 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 58aa2cc..204a7d7 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Pharmaxo. All rights reserved. +using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; @@ -383,6 +384,58 @@ public async Task CreateCatchupSubscriptionUsingCheckpoint_GivenEventsInStreamAn }); } + [Test] + public async Task IsLive_WhenCatchingUpUsingLinkedEventsGivenMissingLinkedEvent_ReturnsTrueOnceCaughtUp() + { + await AppendEventsToStreamAsync(SimpleEvent.Create(), SimpleEvent.Create()); + + const string linkedStream = "non-system"; + var event1LinkedData = new EventData( + Uuid.NewUuid(), + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"0@{StreamName}") + ); + + var event2LinkedData = new EventData( + Uuid.NewUuid(), + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"1@{StreamName}") + ); + + var deletedLinkData = new EventData( + Uuid.NewUuid(), + SystemEventTypes.LinkTo, + Encoding.UTF8.GetBytes($"2@{StreamName}") + ); + + await _kurrentDbClient.AppendToStreamAsync( + linkedStream, + StreamState.NoStream, + [event1LinkedData, event2LinkedData, deletedLinkData]); + + _subscriber = KurrentDbSubscriber.CreateCatchupSubscriptionFromPosition( + _kurrentDbClient, + _dispatcher, + linkedStream, + new NullLogger(), + null); + + _subscriber.Start(); + + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.Elapsed < TimeSpan.FromSeconds(5)) + { + if (_subscriber.IsLive) + { + break; + } + + Thread.Sleep(TimeSpan.FromMilliseconds(100)); + } + + Assert.That(_subscriber.IsLive, "Subscriber was not live"); + } + private class SimpleEvent { // ReSharper disable once UnusedAutoPropertyAccessor.Local diff --git a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs index a9c6875..801443d 100644 --- a/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/KurrentDbSubscriber.cs @@ -349,7 +349,8 @@ private void Init( private void ProcessEvent(ResolvedEvent resolvedEvent) { - if (resolvedEvent.Event.EventType.StartsWith("$")) + // ReSharper disable once ConditionIsAlwaysTrueOrFalse - the linked event could be null if the original event was deleted. + if (resolvedEvent.Event is null || resolvedEvent.Event.EventType.StartsWith("$")) { return; } From b5735c6964e96ae94bd96b049d4779924aa5c41f Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:15:11 +0100 Subject: [PATCH 32/33] Run ES container as root & don't verify cert --- src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index 204a7d7..c7d8a8a 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -35,7 +35,7 @@ public async Task Setup() await eventStoreContainer.StartAsync(); var mappedHostPort = eventStoreContainer.GetMappedPublicPort(eventStoreHostPort); - _connectionString = $"esdb://admin:changeit@localhost:{mappedHostPort}?tls=false"; + _connectionString = $"esdb://admin:changeit@localhost:{mappedHostPort}?tls=true&tlsVerifyCert=false"; _kurrentDbClient = new KurrentDBClient(KurrentDBClientSettings.Create(_connectionString)); _dispatcher = new AwaitableDispatcherSpy(); @@ -490,9 +490,11 @@ private static EventStoreDbContainer BuildEventStoreContainer(string imageName, new EventStoreDbBuilder() .WithImage(imageName) .WithCleanUp(true) + .WithCreateParameterModifier(cmd => cmd.User = "root") .WithPortBinding(hostPort, true) .WithEnvironment(new Dictionary { + { "EVENTSTORE_DEV", "true" }, { "EVENTSTORE_INSECURE", "true" }, { "EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true" }, { "EVENTSTORE_HTTP_PORT", hostPort.ToString() }, From 6c99b40d643bcb3ff99731620a3a69dbde3174fc Mon Sep 17 00:00:00 2001 From: Josh Pattie Date: Thu, 5 Jun 2025 11:23:24 +0100 Subject: [PATCH 33/33] Do not run insecure --- src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs index c7d8a8a..92d8bb3 100644 --- a/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs +++ b/src/MessageDispatch.KurrentDB.Tests/SubscriberTests.cs @@ -495,7 +495,7 @@ private static EventStoreDbContainer BuildEventStoreContainer(string imageName, .WithEnvironment(new Dictionary { { "EVENTSTORE_DEV", "true" }, - { "EVENTSTORE_INSECURE", "true" }, + { "EVENTSTORE_INSECURE", "false" }, { "EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true" }, { "EVENTSTORE_HTTP_PORT", hostPort.ToString() }, { "EVENTSTORE_RUN_PROJECTIONS", "All" },