From 4ae3ebedafb43a2fe54e7564fcda3498bc5e7813 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Thu, 8 Dec 2022 16:43:48 +0000 Subject: [PATCH 01/20] Extract IEventStoreSubscriber --- src/eventstore/EventStoreSubscriber.cs | 2 +- src/eventstore/IEventStoreSubscriber.cs | 29 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/eventstore/IEventStoreSubscriber.cs diff --git a/src/eventstore/EventStoreSubscriber.cs b/src/eventstore/EventStoreSubscriber.cs index f870c2b..bd0d47b 100644 --- a/src/eventstore/EventStoreSubscriber.cs +++ b/src/eventstore/EventStoreSubscriber.cs @@ -15,7 +15,7 @@ namespace CorshamScience.MessageDispatch.EventStore /// /// Subscriber for event store. /// - public class EventStoreSubscriber + public class EventStoreSubscriber : IEventStoreSubscriber { private const string AllStreamName = "$all"; private readonly WriteThroughFileCheckpoint _checkpoint; diff --git a/src/eventstore/IEventStoreSubscriber.cs b/src/eventstore/IEventStoreSubscriber.cs new file mode 100644 index 0000000..b2b57c7 --- /dev/null +++ b/src/eventstore/IEventStoreSubscriber.cs @@ -0,0 +1,29 @@ +namespace CorshamScience.MessageDispatch.EventStore +{ + /// + /// Declares methods for interacting with an . + /// + public interface IEventStoreSubscriber + { + /// + /// Gets a new catchup progress object. + /// + CatchupProgress CatchupProgress { get; } + + /// + /// Gets a value indicating whether the view model is ready or not. + /// + /// Returns true if catchup is within threshold. + bool IsLive { get; } + + /// + /// Start the subscriber. + /// + void Start(); + + /// + /// Shut down the subscription. + /// + void ShutDown(); + } +} From 253b41cb2e19caae796e4b69fa41afd96cc26973 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 28 May 2025 09:37:05 +0100 Subject: [PATCH 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 16/20] 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 17/20] 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 3d56a38f354a8ca446675805c7f14ec45274c8a7 Mon Sep 17 00:00:00 2001 From: Sam Matthews <36134497+SamBucaMatthews@users.noreply.github.com> Date: Fri, 30 May 2025 10:10:14 +0100 Subject: [PATCH 18/20] Fix comments --- src/MessageDispatch.KurrentDB/IKurrentDbSubscriber.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/MessageDispatch.KurrentDB/IKurrentDbSubscriber.cs b/src/MessageDispatch.KurrentDB/IKurrentDbSubscriber.cs index 6913f7d..a4a8d4b 100644 --- a/src/MessageDispatch.KurrentDB/IKurrentDbSubscriber.cs +++ b/src/MessageDispatch.KurrentDB/IKurrentDbSubscriber.cs @@ -10,19 +10,20 @@ public interface IKurrentDbSubscriber /// /// Gets a new catchup progress object. /// -// ReSharper disable once UnusedMember.Global -CatchupProgress CatchupProgress { get; } + // ReSharper disable once UnusedMember.Global + CatchupProgress CatchupProgress { get; } /// /// Gets a value indicating whether the view model is ready or not. /// /// Returns true if catchup is within threshold. + // ReSharper disable once UnusedMemberInSuper.Global bool IsLive { get; set; } /// /// Start the subscriber. /// - // ReSharper disable once MemberCanBePrivate.Global + // ReSharper disable once UnusedMember.Global void Start(); /// From 16f30a08f74ea4089aa99401cfee36f0de7d0a95 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Fri, 30 May 2025 11:59:07 +0100 Subject: [PATCH 19/20] 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 9cbf3d217544bab85a3aada817895997deb27563 Mon Sep 17 00:00:00 2001 From: Chris Fraser Date: Wed, 4 Jun 2025 11:33:56 +0100 Subject: [PATCH 20/20] 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(); }; }