diff --git a/src/Benchmark/Benchmark.csproj b/src/Benchmark/Benchmark.csproj index 64b91b8..00731c2 100644 --- a/src/Benchmark/Benchmark.csproj +++ b/src/Benchmark/Benchmark.csproj @@ -2,14 +2,15 @@ Exe - net6.0 + net6.0;net7.0;net8.0;net9.0;net10.0 + latest enable disable - - + + diff --git a/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient.csproj b/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient.csproj index 57f1cd2..918f62e 100644 --- a/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient.csproj +++ b/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient.csproj @@ -2,14 +2,16 @@ Exe - net6.0 + net6.0;net7.0;net8.0;net9.0;net10.0 + latest - - - - + + + + + diff --git a/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/Program.cs b/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/Program.cs index db30cb8..300531c 100644 --- a/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/Program.cs +++ b/src/BullOak.Repositories.EventStore.ConsoleTestEventStoreClient/Program.cs @@ -1,12 +1,9 @@ using System; -using System.IO; using System.Linq; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading.Tasks; using EventStore.Client; -using EventStore.Client.Projections; namespace BullOak.Repositories.EventStore.ConsoleTestEventStoreClient { @@ -65,7 +62,7 @@ static async Task Main(string[] args) var isSuccess = await readResult.ReadState; var events = await readResult.Where(x=> x.Event != null).ToListAsync(); - + var eventCount = events.Count; var readResultStream = client.ReadStreamAsync(Direction.Forwards, "dokimi-2", StreamPosition.Start); @@ -107,4 +104,4 @@ await client.AppendToStreamAsync(idForTombstoneTest, StreamRevision.FromInt64(-1 // Softdeleted streams write with expected stream revision -1 // Categories throw if tried to read when they are empty // Categories only get created and populated with data writter AFTER they're created -// Tombstoned +// Tombstoned diff --git a/src/BullOak.Repositories.EventStore.Test.Integration/BullOak.Repositories.EventStore.Test.Integration.csproj b/src/BullOak.Repositories.EventStore.Test.Integration/BullOak.Repositories.EventStore.Test.Integration.csproj index 3a7adde..9f9a3e6 100644 --- a/src/BullOak.Repositories.EventStore.Test.Integration/BullOak.Repositories.EventStore.Test.Integration.csproj +++ b/src/BullOak.Repositories.EventStore.Test.Integration/BullOak.Repositories.EventStore.Test.Integration.csproj @@ -1,8 +1,8 @@  - net6.0 - 9.0 + net6.0;net7.0;net8.0;net9.0;net10.0 + latest false true true @@ -11,26 +11,17 @@ - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BullOak.Repositories.EventStore.Test.Integration/Contexts/EventStoreIntegrationContext.cs b/src/BullOak.Repositories.EventStore.Test.Integration/Contexts/EventStoreIntegrationContext.cs index e071356..0a02d4a 100644 --- a/src/BullOak.Repositories.EventStore.Test.Integration/Contexts/EventStoreIntegrationContext.cs +++ b/src/BullOak.Repositories.EventStore.Test.Integration/Contexts/EventStoreIntegrationContext.cs @@ -1,5 +1,6 @@ -namespace BullOak.Repositories.EventStore.Test.Integration.Contexts -{ +using EventStore.Client; + +namespace BullOak.Repositories.EventStore.Test.Integration.Contexts { using Config; using Components; using Session; @@ -10,12 +11,9 @@ using System.Reflection; using System.Threading.Tasks; using Events; - using ClientV5 = global::EventStore.ClientAPI; - using ClientV20 = global::EventStore.Client; using Streams; - internal class EventStoreIntegrationContext : IDisposable - { + internal class EventStoreIntegrationContext { private readonly PassThroughValidator validator; private static IHoldAllConfiguration configuration; @@ -23,53 +21,46 @@ internal class EventStoreIntegrationContext : IDisposable public IReadEventsFromStream EventReader { get; private set; } private EventStoreRepository repository; - private EventStoreRepository Repository - { - get - { + + private EventStoreRepository Repository { + get { if (repository == null) BuildRepositories().Wait(); return repository; } } - public EventStoreReadOnlyRepository ReadOnlyRepository - { - get - { + + public EventStoreReadOnlyRepository ReadOnlyRepository { + get { if (readOnlyRepository == null) BuildRepositories().Wait(); return readOnlyRepository; } } - public async Task BuildRepositories(Protocol chosenProtocol = Protocol.Tcp) - { - protocol = chosenProtocol; + public async Task BuildRepositories() { EventReader = await ConfigureReader(); EventWriter = await ConfigureWriter(); - repository = new EventStoreRepository(validator, configuration, EventReader, EventWriter, DateTimeProvider); + repository = new EventStoreRepository(validator, configuration, EventReader, + EventWriter, DateTimeProvider); readOnlyRepository = new EventStoreReadOnlyRepository(configuration, EventReader); } - private Protocol protocol; - private ClientV5.IEventStoreConnection connV5; - private ClientV20.EventStoreClient connV20; + private EventStoreClient connV20; private EventStoreReadOnlyRepository readOnlyRepository; public TestDateTimeProvider DateTimeProvider { get; } - public EventStoreIntegrationContext(PassThroughValidator validator) - { + public EventStoreIntegrationContext(PassThroughValidator validator) { this.validator = validator; DateTimeProvider = new TestDateTimeProvider(); } - public static void SetupNode() - { + public static void SetupNode() { configuration = Configuration.Begin() .WithDefaultCollection() .WithDefaultStateFactory() @@ -82,45 +73,32 @@ public static void SetupNode() } async Task ConfigureReader() - => protocol switch - { - Protocol.Tcp => new TcpEventReader(await ConfigureEventStoreTcp(), configuration), - Protocol.Grpc => new GrpcEventReader(await ConfigureEventStoreGrpc(), configuration), - _ => throw new ArgumentOutOfRangeException(nameof(protocol)) - }; + => new GrpcEventReader(await ConfigureEventStoreGrpc(), configuration); async Task ConfigureWriter() - => protocol switch - { - Protocol.Tcp => new TcpEventWriter(await ConfigureEventStoreTcp()), - Protocol.Grpc => new GrpcEventWriter(await ConfigureEventStoreGrpc()), - _ => throw new ArgumentOutOfRangeException(nameof(protocol)) - }; - - public static void TeardownNode() - { - } + => new GrpcEventWriter(await ConfigureEventStoreGrpc()); + + public static void TeardownNode() { } - public async Task> StartSession(string streamName, DateTime? appliesAt = null, bool optimizeForShortStreams = true) - { - var session = await Repository.BeginSessionFor(streamName, appliesAt: appliesAt, optimization: optimizeForShortStreams ? OptimizeFor.ShortStreams : OptimizeFor.LongStreams).ConfigureAwait(false); + public async Task> StartSession(string streamName, + DateTime? appliesAt = null, bool optimizeForShortStreams = true) { + var session = await Repository.BeginSessionFor(streamName, appliesAt: appliesAt, + optimization: optimizeForShortStreams ? OptimizeFor.ShortStreams : OptimizeFor.LongStreams) + .ConfigureAwait(false); return session; } - public async Task>> ReadAllEntitiesFromCategory(string categoryName, DateTime? appliesAt = null) - { - return await ReadOnlyRepository.ReadAllEntitiesFromCategory(categoryName, e => - { + public async Task>> ReadAllEntitiesFromCategory(string categoryName, + DateTime? appliesAt = null) { + return await ReadOnlyRepository.ReadAllEntitiesFromCategory(categoryName, e => { if (!appliesAt.HasValue || e.Metadata?.Properties == null) return true; return e.Metadata.TimeStamp <= appliesAt; }).ConfigureAwait(false); } - public async Task AppendEventsToCurrentStream(string id, IMyEvent[] events) - { - using (var session = await StartSession(id)) - { + public async Task AppendEventsToCurrentStream(string id, IMyEvent[] events) { + using (var session = await StartSession(id)) { session.AddEvents(events); await session.SaveChanges(); } @@ -131,116 +109,47 @@ public Task SoftDeleteStream(string id) public Task HardDeleteStream(string id) - => protocol switch - { - Protocol.Tcp => connV5.DeleteStreamAsync(id, -1, true), - Protocol.Grpc => connV20.TombstoneAsync(id, ClientV20.StreamState.Any), - _ => throw new ArgumentOutOfRangeException(nameof(protocol)) - }; + => connV20.TombstoneAsync(id,StreamState.Any); public Task ReadEventsFromStreamRaw(string id) - => protocol switch - { - Protocol.Tcp => TcpReadEventsFromStreamRaw(id), - Protocol.Grpc => GrpcReadEventsFromStreamRaw(id), - _ => throw new ArgumentOutOfRangeException(nameof(protocol)) - }; - - private async Task GrpcReadEventsFromStreamRaw(string id) - { - var readResults = connV20.ReadStreamAsync(ClientV20.Direction.Forwards, id, ClientV20.StreamPosition.Start); - return await readResults - .Where(e => e.Event != null) - .Select(e => e.Event.ToStoredEvent(configuration.StateFactory)) - .ToArrayAsync(); - } + => GrpcReadEventsFromStreamRaw(id); - private async Task TcpReadEventsFromStreamRaw(string id) - { - var result = new List(); - ClientV5.StreamEventsSlice currentSlice; - long nextSliceStart = ClientV5.StreamPosition.Start; - do - { - currentSlice = await connV5.ReadStreamEventsForwardAsync(id, nextSliceStart, 100, false); - nextSliceStart = currentSlice.NextEventNumber; - result.AddRange(currentSlice.Events); - } while (!currentSlice.IsEndOfStream); - - return result - .Where(e => e.Event != null) - .Select((e, _) => e.Event.ToStoredEvent(configuration.StateFactory)) - .TakeWhile(e => e.DeserializedEvent is not EntitySoftDeleted) - .ToArray(); + private async Task GrpcReadEventsFromStreamRaw(string id) { + var readResults = connV20.ReadStreamAsync(Direction.Forwards, id, StreamPosition.Start); + return await readResults + .Where(e => e.Event != null) + .Select(e => e.Event.ToStoredEvent(configuration.StateFactory)) + .ToArrayAsync(); } internal Task WriteEventsToStreamRaw(string currentStreamInUse, IEnumerable myEvents) - => protocol switch - { - Protocol.Tcp => TcpWriteEventsToStreamRaw(currentStreamInUse, myEvents), - Protocol.Grpc => GrpcWriteEventsToStreamRaw(currentStreamInUse, myEvents), - _ => throw new ArgumentOutOfRangeException(nameof(protocol)) - }; - - private async Task GrpcWriteEventsToStreamRaw(string currentStreamInUse, IEnumerable myEvents) - { - await connV20.AppendToStreamAsync(currentStreamInUse, ClientV20.StreamState.Any, - myEvents.Select(e => - { - var serialized = JsonConvert.SerializeObject(e); - var bytes = System.Text.Encoding.UTF8.GetBytes(serialized); - return new ClientV20.EventData(ClientV20.Uuid.NewUuid(), e.GetType().AssemblyQualifiedName, bytes, null); - })); + => GrpcWriteEventsToStreamRaw(currentStreamInUse, myEvents); + private async Task GrpcWriteEventsToStreamRaw(string currentStreamInUse, IEnumerable myEvents) { + await connV20.AppendToStreamAsync(currentStreamInUse, StreamState.Any, + myEvents.Select(e => { + var serialized = JsonConvert.SerializeObject(e); + var bytes = System.Text.Encoding.UTF8.GetBytes(serialized); + return new EventData(Uuid.NewUuid(), e.GetType().AssemblyQualifiedName, bytes, + null); + })); } - private async Task TcpWriteEventsToStreamRaw(string currentStreamInUse, IEnumerable myEvents) - { - await connV5.AppendToStreamAsync(currentStreamInUse, ClientV5.ExpectedVersion.Any, - myEvents.Select(e => - { - var serialized = JsonConvert.SerializeObject(e); - var bytes = System.Text.Encoding.UTF8.GetBytes(serialized); - return new ClientV5.EventData(Guid.NewGuid(), e.GetType().AssemblyQualifiedName, true, bytes, null); - })); - } - private async Task ConfigureEventStoreGrpc() - { + + private async Task ConfigureEventStoreGrpc() { if (connV20 != null) return connV20; - var settings = ClientV20.EventStoreClientSettings - .Create("esdb://localhost:2114?tls=false"); - var client = new ClientV20.EventStoreClient(settings); - var projectionsClient = new ClientV20.EventStoreProjectionManagementClient(settings); + var settings = EventStoreClientSettings + .Create("esdb://localhost:2113?tls=false"); + var client = new EventStoreClient(settings); + var projectionsClient = new EventStoreProjectionManagementClient(settings); + await projectionsClient.EnableAsync("$by_category"); await Task.Delay(TimeSpan.FromSeconds(3)); connV20 = client; return client; } - - private async Task ConfigureEventStoreTcp() - { - if (connV5 != null) - return connV5; - - var connectionString = - "ConnectTo=tcp://admin:changeit@localhost:1113;HeartBeatTimeout=500;UseSslConnection=false;"; - var builder = ClientV5.ConnectionSettings.Create() - .KeepReconnecting(); - - var connection = ClientV5.EventStoreConnection.Create(connectionString, builder); - - await connection.ConnectAsync(); - - await Task.Delay(TimeSpan.FromSeconds(3)); - connV5 = connection; - return connection; - } - public void Dispose() - { - connV5?.Dispose(); - } } } diff --git a/src/BullOak.Repositories.EventStore.Test.Integration/Specification/ProtocolSpecs.feature b/src/BullOak.Repositories.EventStore.Test.Integration/Specification/ProtocolSpecs.feature index 89cd7ef..3783ea6 100644 --- a/src/BullOak.Repositories.EventStore.Test.Integration/Specification/ProtocolSpecs.feature +++ b/src/BullOak.Repositories.EventStore.Test.Integration/Specification/ProtocolSpecs.feature @@ -12,5 +12,4 @@ Specs should be functional by both tcp and grpc protocols And stream position should be 2 Examples: | protocol | - | tcp | | grpc | diff --git a/src/BullOak.Repositories.EventStore.Test.Integration/StepDefinitions/TestsSetupAndTeardown.cs b/src/BullOak.Repositories.EventStore.Test.Integration/StepDefinitions/TestsSetupAndTeardown.cs index a091c32..dd3ff98 100644 --- a/src/BullOak.Repositories.EventStore.Test.Integration/StepDefinitions/TestsSetupAndTeardown.cs +++ b/src/BullOak.Repositories.EventStore.Test.Integration/StepDefinitions/TestsSetupAndTeardown.cs @@ -40,16 +40,10 @@ public static void TeardownNode() EventStoreIntegrationContext.TeardownNode(); } - [Given(@"the (tcp|grpc) protocol is being used")] - public Task GivenProtocolIsBeingUsed(string protocol) + [Given(@"the grpc protocol is being used")] + public Task GivenProtocolIsBeingUsed() { - var chosenProtocol = Enum.Parse(ToCamelCase(protocol)); - return context.BuildRepositories(chosenProtocol); - } - - private static string ToCamelCase(string text) - { - return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text); + return context.BuildRepositories(); } } } diff --git a/src/BullOak.Repositories.EventStore.Test.Unit/BullOak.Repositories.EventStore.Test.Unit.csproj b/src/BullOak.Repositories.EventStore.Test.Unit/BullOak.Repositories.EventStore.Test.Unit.csproj index dd921ab..002d6d9 100644 --- a/src/BullOak.Repositories.EventStore.Test.Unit/BullOak.Repositories.EventStore.Test.Unit.csproj +++ b/src/BullOak.Repositories.EventStore.Test.Unit/BullOak.Repositories.EventStore.Test.Unit.csproj @@ -1,23 +1,18 @@  - net6.0 - 9.0 + net6.0;net7.0;net8.0;net9.0;net10.0 + latest - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BullOak.Repositories.EventStore.TestingExtras/BullOak.Repositories.EventStore.TestingExtras.csproj b/src/BullOak.Repositories.EventStore.TestingExtras/BullOak.Repositories.EventStore.TestingExtras.csproj index 441a934..ebbada7 100644 --- a/src/BullOak.Repositories.EventStore.TestingExtras/BullOak.Repositories.EventStore.TestingExtras.csproj +++ b/src/BullOak.Repositories.EventStore.TestingExtras/BullOak.Repositories.EventStore.TestingExtras.csproj @@ -1,12 +1,9 @@ - netstandard2.1 + net6.0;net7.0;net8.0;net9.0;net10.0 + latest enable - - - - diff --git a/src/BullOak.Repositories.EventStore.sln b/src/BullOak.Repositories.EventStore.sln deleted file mode 100644 index 501cf21..0000000 --- a/src/BullOak.Repositories.EventStore.sln +++ /dev/null @@ -1,74 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32505.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BullOak.Repositories.EventStore", "BullOak.Repositories.EventStore\BullOak.Repositories.EventStore.csproj", "{4223C90A-73A2-4234-A240-10349B62C09C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BullOak.Repositories.EventStore.Test.Integration", "BullOak.Repositories.EventStore.Test.Integration\BullOak.Repositories.EventStore.Test.Integration.csproj", "{FC55EB9E-4CBB-4A0E-BBDF-1E022B7679C1}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{ACD302AF-8190-4A7B-8888-48D7ECCC4829}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BullOak.Repositories.EventStore.Test.Unit", "BullOak.Repositories.EventStore.Test.Unit\BullOak.Repositories.EventStore.Test.Unit.csproj", "{E4A24EAC-FED4-4ECC-B5E0-C083E82C2792}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BullOak.Repositories.EventStore.ConsoleTestEventStoreClient", "BullOak.Repositories.EventStore.ConsoleTestEventStoreClient\BullOak.Repositories.EventStore.ConsoleTestEventStoreClient.csproj", "{896680DA-C076-403E-B117-3FEF57A919FE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BullOak.Repositories.EventStore.TestingExtras", "BullOak.Repositories.EventStore.TestingExtras\BullOak.Repositories.EventStore.TestingExtras.csproj", "{C06585E1-718E-45D5-991D-E9B8ABD07FA9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C396DBC5-B973-4E0A-83A9-509801AFE85E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build scripts", "Build scripts", "{130AEC62-DF94-4C8B-90F3-89CEC4BBFD75}" - ProjectSection(SolutionItems) = preProject - ..\.github\workflows\build-on-push.yml = ..\.github\workflows\build-on-push.yml - ..\.github\workflows\build-pr.yml = ..\.github\workflows\build-pr.yml - ..\.github\workflows\build-version-tag.yml = ..\.github\workflows\build-version-tag.yml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "Benchmark\Benchmark.csproj", "{4A347281-E257-458A-91A4-9254EA8039EC}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4223C90A-73A2-4234-A240-10349B62C09C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4223C90A-73A2-4234-A240-10349B62C09C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4223C90A-73A2-4234-A240-10349B62C09C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4223C90A-73A2-4234-A240-10349B62C09C}.Release|Any CPU.Build.0 = Release|Any CPU - {FC55EB9E-4CBB-4A0E-BBDF-1E022B7679C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC55EB9E-4CBB-4A0E-BBDF-1E022B7679C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC55EB9E-4CBB-4A0E-BBDF-1E022B7679C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC55EB9E-4CBB-4A0E-BBDF-1E022B7679C1}.Release|Any CPU.Build.0 = Release|Any CPU - {E4A24EAC-FED4-4ECC-B5E0-C083E82C2792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E4A24EAC-FED4-4ECC-B5E0-C083E82C2792}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4A24EAC-FED4-4ECC-B5E0-C083E82C2792}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E4A24EAC-FED4-4ECC-B5E0-C083E82C2792}.Release|Any CPU.Build.0 = Release|Any CPU - {896680DA-C076-403E-B117-3FEF57A919FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {896680DA-C076-403E-B117-3FEF57A919FE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {896680DA-C076-403E-B117-3FEF57A919FE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {896680DA-C076-403E-B117-3FEF57A919FE}.Release|Any CPU.Build.0 = Release|Any CPU - {C06585E1-718E-45D5-991D-E9B8ABD07FA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C06585E1-718E-45D5-991D-E9B8ABD07FA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C06585E1-718E-45D5-991D-E9B8ABD07FA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C06585E1-718E-45D5-991D-E9B8ABD07FA9}.Release|Any CPU.Build.0 = Release|Any CPU - {4A347281-E257-458A-91A4-9254EA8039EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A347281-E257-458A-91A4-9254EA8039EC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A347281-E257-458A-91A4-9254EA8039EC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A347281-E257-458A-91A4-9254EA8039EC}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {FC55EB9E-4CBB-4A0E-BBDF-1E022B7679C1} = {ACD302AF-8190-4A7B-8888-48D7ECCC4829} - {E4A24EAC-FED4-4ECC-B5E0-C083E82C2792} = {ACD302AF-8190-4A7B-8888-48D7ECCC4829} - {896680DA-C076-403E-B117-3FEF57A919FE} = {ACD302AF-8190-4A7B-8888-48D7ECCC4829} - {C06585E1-718E-45D5-991D-E9B8ABD07FA9} = {ACD302AF-8190-4A7B-8888-48D7ECCC4829} - {130AEC62-DF94-4C8B-90F3-89CEC4BBFD75} = {C396DBC5-B973-4E0A-83A9-509801AFE85E} - {4A347281-E257-458A-91A4-9254EA8039EC} = {ACD302AF-8190-4A7B-8888-48D7ECCC4829} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9FEF1659-166B-4856-B77C-3ABF38899EEB} - EndGlobalSection -EndGlobal diff --git a/src/BullOak.Repositories.EventStore.slnx b/src/BullOak.Repositories.EventStore.slnx new file mode 100644 index 0000000..437f288 --- /dev/null +++ b/src/BullOak.Repositories.EventStore.slnx @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/BullOak.Repositories.EventStore.v3.ncrunchsolution b/src/BullOak.Repositories.EventStore.v3.ncrunchsolution new file mode 100644 index 0000000..5c70dc9 --- /dev/null +++ b/src/BullOak.Repositories.EventStore.v3.ncrunchsolution @@ -0,0 +1,8 @@ + + + False + False + True + True + + \ No newline at end of file diff --git a/src/BullOak.Repositories.EventStore/BullOak.Repositories.EventStore.csproj b/src/BullOak.Repositories.EventStore/BullOak.Repositories.EventStore.csproj index a2595e6..6cf215e 100644 --- a/src/BullOak.Repositories.EventStore/BullOak.Repositories.EventStore.csproj +++ b/src/BullOak.Repositories.EventStore/BullOak.Repositories.EventStore.csproj @@ -1,9 +1,9 @@  - - net6.0 - True + + net6.0;net7.0;net8.0;net9.0;net10.0 latest + True true @@ -28,12 +28,12 @@ - + 1701;1702;NU5104; - + 1701;1702;NU5104; @@ -42,13 +42,12 @@ - - - - - - - - + + + + + + + diff --git a/src/BullOak.Repositories.EventStore/EventStoreReadOnlyRepository.cs b/src/BullOak.Repositories.EventStore/EventStoreReadOnlyRepository.cs index dcf6866..fff8f37 100644 --- a/src/BullOak.Repositories.EventStore/EventStoreReadOnlyRepository.cs +++ b/src/BullOak.Repositories.EventStore/EventStoreReadOnlyRepository.cs @@ -1,12 +1,8 @@ -using BullOak.Repositories.EventStore.Events; -using BullOak.Repositories.EventStore.Metadata; -using EventStore.Client; - -namespace BullOak.Repositories.EventStore +namespace BullOak.Repositories.EventStore { + using Events; using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Streams; diff --git a/src/BullOak.Repositories.EventStore/Events/EventConversion.cs b/src/BullOak.Repositories.EventStore/Events/EventConversion.cs index b1ac424..fdaf302 100644 --- a/src/BullOak.Repositories.EventStore/Events/EventConversion.cs +++ b/src/BullOak.Repositories.EventStore/Events/EventConversion.cs @@ -1,15 +1,17 @@ -namespace BullOak.Repositories.EventStore.Events + + +namespace BullOak.Repositories.EventStore.Events { + using Metadata; using Newtonsoft.Json; using StateEmit; using System; using System.Linq; using System.Collections.Generic; - using global::EventStore.Client; - using global::EventStore.ClientAPI; using System.Collections.Concurrent; using System.Text.RegularExpressions; + using global::EventStore.Client; public static class EventConversion { @@ -26,19 +28,10 @@ public static StoredEvent ToStoredEvent(this EventRecord resolvedEvent, ICreateS stateFactory ); - public static StoredEvent ToStoredEvent(this RecordedEvent resolvedEvent, ICreateStateInstances stateFactory) - => ToStoredEvent( - resolvedEvent.EventStreamId, - resolvedEvent.EventNumber, - resolvedEvent.Data, - resolvedEvent.Metadata, - resolvedEvent.EventType, - stateFactory - ); - public static StoredEvent ToStoredEvent(string streamId, long eventNumber, ReadOnlyMemory data, ReadOnlyMemory meta, string eventTypeName, ICreateStateInstances stateFactory) { + var serializedEvent = System.Text.Encoding.UTF8.GetString(data.Span); var metadata = LoadMetadata(eventTypeName, meta); diff --git a/src/BullOak.Repositories.EventStore/ItemWithTypeExtensions.cs b/src/BullOak.Repositories.EventStore/ItemWithTypeExtensions.cs index 78b7cdf..ff007e8 100644 --- a/src/BullOak.Repositories.EventStore/ItemWithTypeExtensions.cs +++ b/src/BullOak.Repositories.EventStore/ItemWithTypeExtensions.cs @@ -1,13 +1,10 @@ namespace BullOak.Repositories.EventStore { - using System; - using System.Globalization; using Events; using Metadata; using Newtonsoft.Json.Linq; using StateEmit; using EsClientV20 = global::EventStore.Client; - using EsClientV5 = global::EventStore.ClientAPI; public static class ItemWithTypeExtensions { @@ -35,22 +32,6 @@ public static EsClientV20.EventData CreateV20EventData(this ItemWithType @event, MetadataSerializer.Serialize(metadata)); } - public static EsClientV5.EventData CreateV5EventData(this ItemWithType @event, IDateTimeProvider dateTimeProvider) - { - var metadata = EventMetadata_V2.From(@event); - metadata.TimeStamp = dateTimeProvider.UtcNow; - - var eventAsJson = JObject.FromObject(@event.instance); - eventAsJson.Remove(CanEditJsonFieldName); - - return new EsClientV5.EventData( - Guid.NewGuid(), - @event.type.Name, - true, - System.Text.Encoding.UTF8.GetBytes(eventAsJson.ToString()), - MetadataSerializer.Serialize(metadata)); - } - public static bool IsSoftDeleteEvent(this ItemWithType @event) => @event.type == DefaultSoftDeleteEvent.Type || @event.type.IsSubclassOf(DefaultSoftDeleteEvent.Type); } diff --git a/src/BullOak.Repositories.EventStore/Protocol.cs b/src/BullOak.Repositories.EventStore/Protocol.cs index 026cb00..13f769f 100644 --- a/src/BullOak.Repositories.EventStore/Protocol.cs +++ b/src/BullOak.Repositories.EventStore/Protocol.cs @@ -2,6 +2,5 @@ namespace BullOak.Repositories.EventStore; public enum Protocol { - Tcp, Grpc } diff --git a/src/BullOak.Repositories.EventStore/Streams/TcpEventReader.cs b/src/BullOak.Repositories.EventStore/Streams/TcpEventReader.cs deleted file mode 100644 index 56a9a87..0000000 --- a/src/BullOak.Repositories.EventStore/Streams/TcpEventReader.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace BullOak.Repositories.EventStore.Streams -{ - using Events; - using StateEmit; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using global::EventStore.ClientAPI; - using System.Threading; - using global::EventStore.ClientAPI.Exceptions; - - public class TcpEventReader : IReadEventsFromStream - { - private readonly ICreateStateInstances stateFactory; - private readonly IEventStoreConnection connection; - private readonly int pageSize; - private static readonly IAsyncEnumerable EmptyReadResult = Array.Empty().ToAsyncEnumerable(); - private static readonly StoredEvent[] EmptyStoredEvents = new StoredEvent[0]; - - public TcpEventReader(IEventStoreConnection client, IHoldAllConfiguration configuration, int pageSize = 4096) - { - stateFactory = configuration?.StateFactory ?? throw new ArgumentNullException(nameof(configuration)); - this.connection = client ?? throw new ArgumentNullException(nameof(client)); - this.pageSize = pageSize; - } - - public async Task ReadToMemoryFrom(string streamId, Func predicate = null, StreamReadDirection direction = StreamReadDirection.Forwards, CancellationToken cancellationToken = default) - { - if (predicate == null) - direction = StreamReadDirection.Forwards; - - predicate ??= _ => true; - - StoredEvent[] storedEvents; - if (direction == StreamReadDirection.Backwards) - { - var readResult = await connection.ReadStreamEventsBackwardAsync(streamId, StreamPosition.End, pageSize, true); - - if (!StreamExists(readResult)) - return new StreamReadToMemoryResults(EmptyStoredEvents, false); - - storedEvents = readResult.Events - // Trust me, resharper is wrong in this one. Event can be null - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - .Where(e => e.Event != null) - .Select((e, _) => e.Event.ToStoredEvent(stateFactory)) - .TakeWhile(e => e.DeserializedEvent is not EntitySoftDeleted) - .Where(e => predicate(e)) - .ToArray(); - } - else - { - var readResult = await connection.ReadStreamEventsForwardAsync(streamId, StreamPosition.Start, pageSize, true); - - if (!StreamExists(readResult)) - return new StreamReadToMemoryResults(EmptyStoredEvents, false); - - storedEvents = readResult.Events - // Trust me, resharper is wrong in this one. Event can be null - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - .Where(e => e.Event != null) - .Select((e, c) => e.Event.ToStoredEvent(stateFactory)) - .Where(e => predicate(e)) - .ToArray(); - } - - return new StreamReadToMemoryResults(storedEvents, true); - } - - public async Task ReadFrom(string streamId, Func predicate = null, StreamReadDirection direction = StreamReadDirection.Forwards, CancellationToken cancellationToken = default) - { - var readToMemResults = await ReadToMemoryFrom(streamId, predicate, direction, cancellationToken); - return new StreamReadResults(readToMemResults.Events.ToAsyncEnumerable(), readToMemResults.StreamExists); - } - - private static bool StreamExists(StreamEventsSlice readResult) - { - bool streamExists = false; - try - { - var readState = readResult.Status; - streamExists = readState == SliceReadStatus.Success; - } -#pragma warning disable 168 - catch (StreamDeletedException ex) - // This happens when the stream is hard-deleted. We don't want to throw in that case -#pragma warning restore 168 - { - streamExists = false; - } - - return streamExists; - } - } -} diff --git a/src/BullOak.Repositories.EventStore/Streams/TcpEventWriter.cs b/src/BullOak.Repositories.EventStore/Streams/TcpEventWriter.cs deleted file mode 100644 index aff16e7..0000000 --- a/src/BullOak.Repositories.EventStore/Streams/TcpEventWriter.cs +++ /dev/null @@ -1,111 +0,0 @@ -namespace BullOak.Repositories.EventStore.Streams; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Exceptions; -using global::EventStore.ClientAPI; - -public class TcpEventWriter : IStoreEventsToStream -{ - private readonly IEventStoreConnection connection; - - public TcpEventWriter(IEventStoreConnection connection) - { - this.connection = connection; - } - - public async Task Add - ( - string streamId, - ItemWithType[] eventsToAdd, - IDateTimeProvider dateTimeProvider, - CancellationToken cancellationToken = default - ) => await AppendToStream(streamId, -1, eventsToAdd, dateTimeProvider, cancellationToken); - - - public async Task AppendTo - ( - string streamId, - long revision, - ItemWithType[] eventsToAdd, - IDateTimeProvider dateTimeProvider, - CancellationToken cancellationToken = default - ) => await AppendToStream(streamId, revision, eventsToAdd, dateTimeProvider, cancellationToken); - - public async Task SoftDelete(string streamId) - { - var expectedVersion = await GetLastEventNumber(streamId); - await connection.DeleteStreamAsync(streamId, expectedVersion); - } - - public async Task SoftDeleteByEvent(string streamId, IEnumerable eventData) - { - var events = eventData as EventData[] ?? eventData.Cast().ToArray(); - - var expectedVersion = await GetLastEventNumber(streamId); - var writeResult = await connection.ConditionalAppendToStreamAsync - ( - streamId, - expectedVersion, - events - ) - .ConfigureAwait(false); - - CheckConditionalWriteResultStatus(writeResult, streamId); - } - - private async Task GetLastEventNumber(string id) - { - var eventsTail = await GetLastEvent(id); - return eventsTail.LastEventNumber; - } - - private Task GetLastEvent(string id) - => connection.ReadStreamEventsBackwardAsync(id, StreamPosition.End, 1, false); - - private async Task AppendToStream - ( - string streamId, - long revision, - ItemWithType[] eventsToAdd, - IDateTimeProvider dateTimeProvider, - CancellationToken cancellationToken - ) - { - var writeResult = await connection.ConditionalAppendToStreamAsync( - streamId, - revision, - eventsToAdd.Select(eventObject => eventObject.CreateV5EventData(dateTimeProvider)) - ) - .ConfigureAwait(false); - - CheckConditionalWriteResultStatus(writeResult, streamId); - - if (!writeResult.NextExpectedVersion.HasValue) - { - throw new InvalidOperationException( - "EventStore data write outcome unexpected. NextExpectedVersion is null"); - } - - return (int)writeResult.NextExpectedVersion; - } - - - private static void CheckConditionalWriteResultStatus(ConditionalWriteResult writeResult, string id) - { - switch (writeResult.Status) - { - case ConditionalWriteStatus.Succeeded: - break; - case ConditionalWriteStatus.VersionMismatch: - throw new ConcurrencyException(id, null); - case ConditionalWriteStatus.StreamDeleted: - throw new InvalidOperationException($"Stream was deleted. StreamId: {id}"); - default: - throw new InvalidOperationException($"Unexpected write result: {writeResult.Status}"); - } - } -} diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 0000000..4c07f19 --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,29 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/dotnet-tools.json b/src/dotnet-tools.json new file mode 100644 index 0000000..316872e --- /dev/null +++ b/src/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-purge": { + "version": "0.0.13", + "commands": [ + "dotnet-purge" + ], + "rollForward": false + } + } +} \ No newline at end of file