diff --git a/.editorconfig b/.editorconfig index 409be9c..e8cdc18 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,14 @@ [*] charset = utf-8 insert_final_newline = true +end_of_line = crlf +tab_width = 4 +indent_size = 4 +indent_style = space [*.cs] -charset = utf-8-bom \ No newline at end of file +charset = utf-8-bom + +[*.{yaml,yml}] +tab_width = 2 +indent_size = 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8b4fd4..cc37b08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,12 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: '0' + - name: Start RabbitMQ + uses: namoshek/rabbitmq-github-action@v1 + with: + version: '3.12' + ports: '5672:5672 5552:5552' + plugins: rabbitmq_stream - name: Setup .NET Core SDK 7, 8 uses: actions/setup-dotnet@v3 with: @@ -21,7 +27,7 @@ jobs: - name: Build .NET Solution run: dotnet build --configuration Release --no-restore - name: Test .NET Solution - run: dotnet test --configuration Release --no-build --filter=Category=UnitTest --logger "trx;LogFilePrefix=test-results" + run: dotnet test --configuration Release --no-build --filter="Category=UnitTest|Category=IntegrationTest" --logger "trx;LogFilePrefix=test-results" - uses: actions/upload-artifact@v2 if: success() || failure() with: diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index dc41381..8db3f28 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -75,9 +75,9 @@ jobs: dotnet test ` --configuration Release ` --no-build ` - --filter=Category=UnitTest ` + --filter="Category=UnitTest|Category=IntegrationTest" ` --collect "XPlat Code Coverage" ` --results-directory TestResults/ ` --logger "trx;LogFilePrefix=test-results" ` -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/Directory.Build.props b/Directory.Build.props index ec258df..623e32f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,47 @@ - net7.0 + true + net8.0;net7.0 enable enable true + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 18cebac..a0fdcbd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,38 +1,42 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Escendit.Orleans.Streaming.RabbitMQ.sln b/Escendit.Orleans.Streaming.RabbitMQ.sln deleted file mode 100644 index 8fe2747..0000000 --- a/Escendit.Orleans.Streaming.RabbitMQ.sln +++ /dev/null @@ -1,36 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EFD3DA34-B04F-4C90-B3E0-BEB2402B2D6A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Escendit.Orleans.Streaming.RabbitMQ", "src\Escendit.Orleans.Streaming.RabbitMQ\Escendit.Orleans.Streaming.RabbitMQ.csproj", "{A5BAACBF-4FA9-4720-B73D-D6DE7DB33815}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A5880995-120F-422E-9B46-4302F74C87FE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Escendit.Orleans.Streaming.RabbitMQ.Tests", "test\Escendit.Orleans.Streaming.RabbitMQ.Tests\Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj", "{D73F5B22-EF89-48F6-8538-A23B1A22B052}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A5BAACBF-4FA9-4720-B73D-D6DE7DB33815} = {EFD3DA34-B04F-4C90-B3E0-BEB2402B2D6A} - {D73F5B22-EF89-48F6-8538-A23B1A22B052} = {A5880995-120F-422E-9B46-4302F74C87FE} - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A5BAACBF-4FA9-4720-B73D-D6DE7DB33815}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5BAACBF-4FA9-4720-B73D-D6DE7DB33815}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5BAACBF-4FA9-4720-B73D-D6DE7DB33815}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5BAACBF-4FA9-4720-B73D-D6DE7DB33815}.Release|Any CPU.Build.0 = Release|Any CPU - {D73F5B22-EF89-48F6-8538-A23B1A22B052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D73F5B22-EF89-48F6-8538-A23B1A22B052}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D73F5B22-EF89-48F6-8538-A23B1A22B052}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D73F5B22-EF89-48F6-8538-A23B1A22B052}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/rabbitmq-orleans-extensions.sln b/rabbitmq-orleans-extensions.sln new file mode 100644 index 0000000..6f557a8 --- /dev/null +++ b/rabbitmq-orleans-extensions.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EFD3DA34-B04F-4C90-B3E0-BEB2402B2D6A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A5880995-120F-422E-9B46-4302F74C87FE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Orleans", "Orleans", "{3947EC73-A7D9-482C-86B1-8E4F6387B5FC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspNetCore", "AspNetCore", "{275062C9-CF0B-4445-8940-1D6106966163}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Escendit.Orleans.Streaming.RabbitMQ", "src\Orleans\RabbitMQ\Escendit.Orleans.Streaming.RabbitMQ.csproj", "{EFA84B10-485C-4410-A201-E5F717404678}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol", "src\Orleans\AmqpProtocol\Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.csproj", "{AF8058B1-5AD2-4E0F-B483-12FE69C7ABBD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol", "src\Orleans\StreamProtocol\Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.csproj", "{F7431962-0830-47B5-8E51-8EED823E8794}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Orleans", "Orleans", "{5C89E98A-C88E-4478-9AD5-7FFE87AFD903}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests", "test\Orleans\AmqpProtocol\Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.csproj", "{A0D36FA0-B374-4702-B51D-D7BBB8A764A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Escendit.Orleans.Streaming.RabbitMQ.Tests", "test\Orleans\RabbitMQ\Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj", "{D3385699-829C-4D03-A5CA-788D1470FD2A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests", "test\Orleans\StreamProtocol\Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.csproj", "{2437B71B-0685-4428-93F2-8A1C472AEC63}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EFA84B10-485C-4410-A201-E5F717404678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EFA84B10-485C-4410-A201-E5F717404678}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EFA84B10-485C-4410-A201-E5F717404678}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EFA84B10-485C-4410-A201-E5F717404678}.Release|Any CPU.Build.0 = Release|Any CPU + {AF8058B1-5AD2-4E0F-B483-12FE69C7ABBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF8058B1-5AD2-4E0F-B483-12FE69C7ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF8058B1-5AD2-4E0F-B483-12FE69C7ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF8058B1-5AD2-4E0F-B483-12FE69C7ABBD}.Release|Any CPU.Build.0 = Release|Any CPU + {F7431962-0830-47B5-8E51-8EED823E8794}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7431962-0830-47B5-8E51-8EED823E8794}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7431962-0830-47B5-8E51-8EED823E8794}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7431962-0830-47B5-8E51-8EED823E8794}.Release|Any CPU.Build.0 = Release|Any CPU + {A0D36FA0-B374-4702-B51D-D7BBB8A764A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0D36FA0-B374-4702-B51D-D7BBB8A764A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0D36FA0-B374-4702-B51D-D7BBB8A764A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0D36FA0-B374-4702-B51D-D7BBB8A764A8}.Release|Any CPU.Build.0 = Release|Any CPU + {D3385699-829C-4D03-A5CA-788D1470FD2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3385699-829C-4D03-A5CA-788D1470FD2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3385699-829C-4D03-A5CA-788D1470FD2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3385699-829C-4D03-A5CA-788D1470FD2A}.Release|Any CPU.Build.0 = Release|Any CPU + {2437B71B-0685-4428-93F2-8A1C472AEC63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2437B71B-0685-4428-93F2-8A1C472AEC63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2437B71B-0685-4428-93F2-8A1C472AEC63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2437B71B-0685-4428-93F2-8A1C472AEC63}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3947EC73-A7D9-482C-86B1-8E4F6387B5FC} = {EFD3DA34-B04F-4C90-B3E0-BEB2402B2D6A} + {275062C9-CF0B-4445-8940-1D6106966163} = {EFD3DA34-B04F-4C90-B3E0-BEB2402B2D6A} + {EFA84B10-485C-4410-A201-E5F717404678} = {3947EC73-A7D9-482C-86B1-8E4F6387B5FC} + {AF8058B1-5AD2-4E0F-B483-12FE69C7ABBD} = {3947EC73-A7D9-482C-86B1-8E4F6387B5FC} + {F7431962-0830-47B5-8E51-8EED823E8794} = {3947EC73-A7D9-482C-86B1-8E4F6387B5FC} + {5C89E98A-C88E-4478-9AD5-7FFE87AFD903} = {A5880995-120F-422E-9B46-4302F74C87FE} + {A0D36FA0-B374-4702-B51D-D7BBB8A764A8} = {5C89E98A-C88E-4478-9AD5-7FFE87AFD903} + {D3385699-829C-4D03-A5CA-788D1470FD2A} = {5C89E98A-C88E-4478-9AD5-7FFE87AFD903} + {2437B71B-0685-4428-93F2-8A1C472AEC63} = {5C89E98A-C88E-4478-9AD5-7FFE87AFD903} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C067AF04-0718-4504-9C39-F953038CFF86} + EndGlobalSection +EndGlobal diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitClusterClientQueueConfigurator.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitClusterClientQueueConfigurator.cs deleted file mode 100644 index f2f044f..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitClusterClientQueueConfigurator.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using Configuration; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Escendit.Orleans.Streaming.RabbitMQ.Queue; -using Microsoft.Extensions.DependencyInjection; -using Runtime; - -/// -/// Cluster Client Rabbit MQ Queue Configurator. -/// -internal class RabbitClusterClientQueueConfigurator : ClusterClientPersistentStreamConfigurator -{ - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The client builder. - public RabbitClusterClientQueueConfigurator( - string name, - IClientBuilder clientBuilder) - : base(name, clientBuilder, DefaultQueueAdapterFactory.Create) - { - ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(clientBuilder); - - clientBuilder - .Services - .AddClientStreaming(); - - clientBuilder - .ConfigureServices(configure => - { - configure - .AddSingletonNamedService(name, DefaultQueueAdapterFactory.Create) - .ConfigureNamedOptionForLogging(name) - .AddRabbitMq(name) - .WithConnection(); - }); - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitClusterClientStreamConfigurator.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitClusterClientStreamConfigurator.cs deleted file mode 100644 index 092b092..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitClusterClientStreamConfigurator.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using Configuration; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Escendit.Orleans.Streaming.RabbitMQ.Stream; -using Runtime; - -/// -/// Cluster Client RabbitMQ Stream Configurator. -/// -internal class RabbitClusterClientStreamConfigurator : ClusterClientPersistentStreamConfigurator -{ - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The client builder. - public RabbitClusterClientStreamConfigurator( - string name, - IClientBuilder clientBuilder) - : base(name, clientBuilder, DefaultStreamAdapterFactory.Create) - { - ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(clientBuilder); - - clientBuilder - .Services - .AddClientStreaming(); - - clientBuilder - .ConfigureServices(configure => - { - configure - .AddSingletonNamedService(name, DefaultStreamAdapterFactory.Create) - .ConfigureNamedOptionForLogging(name); - }); - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitSiloQueueConfigurator.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitSiloQueueConfigurator.cs deleted file mode 100644 index fb15aa6..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitSiloQueueConfigurator.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using Configuration; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Escendit.Orleans.Streaming.RabbitMQ.Queue; -using Microsoft.Extensions.DependencyInjection; -using Runtime; - -/// -/// Silo Rabbit MQ Queue Configurator. -/// -internal class RabbitSiloQueueConfigurator : SiloPersistentStreamConfigurator -{ - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The configure delegate. - public RabbitSiloQueueConfigurator( - string name, - Action> configureDelegate) - : base(name, configureDelegate, DefaultQueueAdapterFactory.Create) - { - ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(configureDelegate); - - ConfigureDelegate(services => - { - services - .AddSingletonNamedService(name, DefaultQueueAdapterFactory.Create) - .ConfigureNamedOptionForLogging(name) - .AddRabbitMq(name) - .WithConnection(); - }); - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Core/RabbitBatchContainer.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Core/RabbitBatchContainer.cs deleted file mode 100644 index d603cb9..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Core/RabbitBatchContainer.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Core; - -using System.Text.Json.Serialization; -using global::Orleans; -using global::Orleans.Runtime; -using global::Orleans.Streams; - -/// -/// Rabbit MQ Batch Container. -/// -[Serializable] -[GenerateSerializer] -internal class RabbitBatchContainer : IBatchContainer, IComparable -{ - [Id(0)] - [JsonPropertyName("sequenceToken")] - private RabbitStreamSequenceToken _sequenceToken; - - [Id(1)] - [JsonPropertyName("events")] - private ICollection _events; - - [Id(2)] - [JsonPropertyName("requestContext")] - private Dictionary? _requestContext; - - /// - /// Initializes a new instance of the class. - /// - /// The stream id. - /// The events. - /// The request context. - /// The sequence token. - [JsonConstructor] - public RabbitBatchContainer( - StreamId streamId, - ICollection events, - Dictionary requestContext, - RabbitStreamSequenceToken sequenceToken) - { - StreamId = streamId; - _events = events; - _requestContext = requestContext; - _sequenceToken = sequenceToken; - } - - /// - public StreamSequenceToken SequenceToken => _sequenceToken; - - /// - [Id(3)] - [JsonPropertyName("streamId")] - public StreamId StreamId { get; } - - /// - public IEnumerable> GetEvents() - { - return _events.OfType().Select((e, i) => Tuple.Create(e, new RabbitStreamSequenceToken(_sequenceToken.SequenceNumber, i))); - } - - /// - public bool ImportRequestContext() - { - if (_requestContext is null) - { - return false; - } - - RequestContextExtensions.Import(_requestContext); - return true; - } - - /// - public int CompareTo(RabbitBatchContainer? other) - { - return other?.CompareTo(this) ?? 0; - } - - /// - /// Update Sequence Token. - /// - /// The stream sequence token. - internal void UpdateSequenceToken(RabbitStreamSequenceToken streamSequenceToken) - { - _sequenceToken = - new RabbitStreamSequenceToken( - streamSequenceToken.SequenceNumber, - streamSequenceToken.EventIndex); - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Core/RabbitStreamSequenceToken.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Core/RabbitStreamSequenceToken.cs deleted file mode 100644 index 23ab630..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Core/RabbitStreamSequenceToken.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Core; - -using System.Globalization; -using System.Text.Json.Serialization; -using global::Orleans.Streams; - -/// -/// Rabbit MQ Stream Sequence Token. -/// -[Serializable] -[GenerateSerializer] -public sealed class RabbitStreamSequenceToken : StreamSequenceToken -{ - /// - /// Initializes a new instance of the class. - /// - /// The sequence number. - /// The event index. - public RabbitStreamSequenceToken(long sequenceNumber, int eventIndex = 0) - { - SequenceNumber = sequenceNumber; - EventIndex = eventIndex; - } - - /// - /// Initializes a new instance of the class. - /// - public RabbitStreamSequenceToken() - { - } - - /// - /// Gets or sets the sequence number. - /// - /// The sequence number. - [Id(0)] - [JsonPropertyName("sequenceNumber")] - public override long SequenceNumber { get; protected set; } - - /// - /// Gets or sets the event index. - /// - /// The event index. - [Id(1)] - [JsonPropertyName("eventIndex")] - public override int EventIndex { get; protected set; } - - /// - public override bool Equals(object? obj) - { - return Equals(obj as RabbitStreamSequenceToken); - } - - /// - public override bool Equals(StreamSequenceToken? other) - { - var token = other as RabbitStreamSequenceToken; - return token is not null - && token.SequenceNumber == SequenceNumber - && token.EventIndex == EventIndex; - } - - /// - public override int CompareTo(StreamSequenceToken? other) - { - if (other is null) - { - return 1; - } - - if (other is not RabbitStreamSequenceToken token) - { - throw new ArgumentOutOfRangeException(nameof(other)); - } - - var difference = SequenceNumber.CompareTo(token.SequenceNumber); - return difference != 0 ? difference : EventIndex.CompareTo(token.EventIndex); - } - - /// - public override int GetHashCode() - { - return (EventIndex * 397) ^ SequenceNumber.GetHashCode(); - } - - /// - public override string ToString() - { - return string.Format( - CultureInfo.InvariantCulture, - "[RabbitMQStreamSequenceToken: Num: {0}, Index: {1}", - SequenceNumber.ToString(CultureInfo.InvariantCulture), - EventIndex.ToString(CultureInfo.InvariantCulture)); - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.csproj b/src/Escendit.Orleans.Streaming.RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.csproj deleted file mode 100644 index 27366e4..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - true - RabbitMQ Stream Provider for Orleans Streaming - - - - - - - - - - - diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/ClientBuilderExtensions.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/ClientBuilderExtensions.cs deleted file mode 100644 index 0151f59..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/ClientBuilderExtensions.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using System.Diagnostics.CodeAnalysis; -using Configuration; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Escendit.Orleans.Streaming.RabbitMQ.Queue; -using Escendit.Orleans.Streaming.RabbitMQ.Stream; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -/// -/// Client Builder Extensions. -/// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.All)] -public static class ClientBuilderExtensions -{ - /// - /// Add Rabbit MQ. - /// - /// The rabbit mq. - /// The name. - /// The client builder. - public static RabbitClientBuilder AddRabbitMqStreaming( - this IClientBuilder builder, - string name) - { - return new RabbitClientBuilder(builder, name); - } - - /// - /// Add Stream System. - /// - /// The initial client builder. - /// The updated client builder. - public static RabbitClientBuilder WithStream( - this RabbitClientBuilder clientBuilder) - { - ArgumentNullException.ThrowIfNull(clientBuilder); - return clientBuilder - .WithStream(_ => { }); - } - - /// - /// Add Stream for Rabbit MQ. - /// - /// The builder. - /// The configure options. - /// The rabbit client builder. - public static RabbitClientBuilder WithStream( - this RabbitClientBuilder clientBuilder, - Action streamOptions) - { - ArgumentNullException.ThrowIfNull(clientBuilder); - - clientBuilder - .ConfigureServices(services => - { - var rabbitStreamOptions = new RabbitStreamOptions(); - - streamOptions.Invoke(rabbitStreamOptions); - - services - .AddOptions(clientBuilder.Name) - .Configure(streamOptions); - - services - .AddOptions(clientBuilder.Name) - .Configure(options => - { - options.CacheSize = rabbitStreamOptions.CacheSize; - options.ClientProvidedName = rabbitStreamOptions.ClientProvidedName; - options.Endpoints.Clear(); - - foreach (var endpoint in rabbitStreamOptions.Endpoints) - { - options.Endpoints.Add(endpoint); - } - - options.Heartbeat = rabbitStreamOptions.Heartbeat; - options.Password = rabbitStreamOptions.Password; - options.SslOptions = rabbitStreamOptions.SslOptions; - options.StreamFailureHandlerFactory = rabbitStreamOptions.StreamFailureHandlerFactory; - options.TotalQueueCount = rabbitStreamOptions.TotalQueueCount; - options.UserName = rabbitStreamOptions.UserName; - options.VirtualHost = rabbitStreamOptions.VirtualHost; - }); - - services - .ConfigureNamedOptionForLogging(clientBuilder.Name) - .ConfigureNamedOptionForLogging(clientBuilder.Name); - }); - - _ = new RabbitClusterClientStreamConfigurator( - clientBuilder.Name, - clientBuilder); - - return clientBuilder; - } - - /// - /// Add Classic Queue System. - /// - /// The initial client builder. - /// The updated client builder. - public static RabbitClientBuilder WithQueue( - this RabbitClientBuilder clientBuilder) - { - ArgumentNullException.ThrowIfNull(clientBuilder); - return clientBuilder - .WithQueue(_ => { }); - } - - /// - /// Add Queue for Rabbit MQ. - /// - /// The builder. - /// The options. - /// The rabbit client builder. - public static RabbitClientBuilder WithQueue( - this RabbitClientBuilder clientBuilder, - Action queueOptions) - { - ArgumentNullException.ThrowIfNull(clientBuilder); - - clientBuilder - .ConfigureServices(services => - { - var rabbitQueueOptions = new RabbitQueueOptions(); - - queueOptions.Invoke(rabbitQueueOptions); - - services - .AddOptions(clientBuilder.Name) - .Configure(queueOptions); - - services - .AddOptions(clientBuilder.Name) - .Configure(options => - { - options.CacheSize = rabbitQueueOptions.CacheSize; - options.ClientProvidedName = rabbitQueueOptions.ClientProvidedName; - options.Endpoints.Clear(); - - foreach (var endpoint in rabbitQueueOptions.Endpoints) - { - options.Endpoints.Add(endpoint); - } - - options.Heartbeat = rabbitQueueOptions.Heartbeat; - options.Password = rabbitQueueOptions.Password; - options.SslOptions = rabbitQueueOptions.SslOptions; - options.StreamFailureHandlerFactory = rabbitQueueOptions.StreamFailureHandlerFactory; - options.TotalQueueCount = rabbitQueueOptions.TotalQueueCount; - options.UserName = rabbitQueueOptions.UserName; - options.VirtualHost = rabbitQueueOptions.VirtualHost; - }); - - services - .ConfigureNamedOptionForLogging(clientBuilder.Name) - .ConfigureNamedOptionForLogging(clientBuilder.Name); - }); - - _ = new RabbitClusterClientQueueConfigurator( - clientBuilder.Name, - clientBuilder); - - return clientBuilder; - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitClientBuilder.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitClientBuilder.cs deleted file mode 100644 index 0a698f1..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitClientBuilder.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using Microsoft.Extensions.DependencyInjection; - -/// -public class RabbitClientBuilder : IClientBuilder -{ - /// - /// Initializes a new instance of the class. - /// - /// The client builder. - /// The name. - internal RabbitClientBuilder( - IClientBuilder clientBuilder, - string name) - { - ArgumentNullException.ThrowIfNull(clientBuilder); - Services = clientBuilder.Services; - Name = name; - } - - /// - public IServiceCollection Services { get; } - - /// - /// Gets the name. - /// - /// The name. - public string Name { get; } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitConnectionFactoryBuilder.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitConnectionFactoryBuilder.cs deleted file mode 100644 index 4af89cf..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitConnectionFactoryBuilder.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Microsoft.Extensions.DependencyInjection; - -using Escendit.Orleans.Streaming.RabbitMQ.Hosting; - -/// -/// Rabbit Connection Factory Builder. -/// -public class RabbitConnectionFactoryBuilder : IRabbitConnectionFactoryBuilder -{ - /// - /// Initializes a new instance of the class. - /// - /// The service collection. - /// The name. - internal RabbitConnectionFactoryBuilder(IServiceCollection services, string name) - { - Services = services; - Name = name; - } - - /// - public IServiceCollection Services { get; } - - /// - public string Name { get; } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitSiloBuilder.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitSiloBuilder.cs deleted file mode 100644 index 8f8b8a8..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/RabbitSiloBuilder.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using Microsoft.Extensions.DependencyInjection; - -/// -public class RabbitSiloBuilder : ISiloBuilder -{ - /// - /// Initializes a new instance of the class. - /// - /// The silo builder. - /// The name. - internal RabbitSiloBuilder(ISiloBuilder siloBuilder, string name) - { - ArgumentNullException.ThrowIfNull(siloBuilder); - Services = siloBuilder.Services; - Name = name; - } - - /// - public IServiceCollection Services { get; } - - /// - /// Gets the name. - /// - /// The name. - public string Name { get; } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/ServiceCollectionExtensions.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/ServiceCollectionExtensions.cs deleted file mode 100644 index 92437a2..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Microsoft.Extensions.DependencyInjection; - -using System.Diagnostics.CodeAnalysis; -using Escendit.Orleans.Streaming.RabbitMQ.Hosting; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Options; -using Orleans.Configuration; -using Orleans.Runtime; -using RabbitMQ.Client; - -/// -/// Service Collection Extensions. -/// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.All)] -public static class ServiceCollectionExtensions -{ - /// - /// Add Named Rabbit MQ. - /// - /// The initial service collection. - /// The name. - /// The updated rabbit mq connection factory builder. - public static IRabbitConnectionFactoryBuilder AddRabbitMq(this IServiceCollection services, string name) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(name); - - services - .AddRabbitNamedConnectionFactory(name); - - return new RabbitConnectionFactoryBuilder(services, name); - } - - /// - /// Add Named Rabbit MQ. - /// - /// The initial service collection. - /// The name. - /// The options. - /// The updated rabbit mq connection factory builder. - public static IRabbitConnectionFactoryBuilder AddRabbitMq( - this IServiceCollection services, - string name, - Action options) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(options); - - services - .AddRabbitMq(name, configure => - { - configure.Configure(options); - }); - - return new RabbitConnectionFactoryBuilder(services, name); - } - - /// - /// Add Named Rabbit MQ. - /// - /// The initial service collection. - /// The name. - /// The configure options. - /// The updated rabbit mq connection factory builder. - public static IRabbitConnectionFactoryBuilder AddRabbitMq( - this IServiceCollection services, - string name, - Action> configureOptions) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(configureOptions); - - configureOptions - .Invoke(services.AddOptions(name)); - - services - .ConfigureNamedOptionForLogging(name) - .AddRabbitNamedConnectionFactory(name); - return new RabbitConnectionFactoryBuilder(services, name); - } - - /// - /// Use with single connection. - /// - /// The builder. - public static void WithConnection(this IRabbitConnectionFactoryBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - builder - .Services - .AddRabbitNamedConnection(builder.Name); - } - - /// - /// Add Rabbit MQ Named Connection Factory. - /// - /// The initial service collection. - /// The name. - /// The updated service collection. - private static IServiceCollection AddRabbitNamedConnectionFactory(this IServiceCollection services, string name) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(name); - - return services - .AddSingletonNamedService( - name, - (serviceProvider, providerName) => - { - var options = serviceProvider.GetOptionsByName(providerName); - return new ConnectionFactory - { - Password = options.Password, - UserName = options.UserName, - VirtualHost = options.VirtualHost, - UseBackgroundThreadsForIO = true, - DispatchConsumersAsync = true, - Ssl = options.SslOptions is null - ? null - : new SslOption - { - AcceptablePolicyErrors = options.SslOptions.AcceptablePolicyErrors, - Certs = options.SslOptions.Certificates, - Enabled = options.SslOptions.Enabled, - Version = options.SslOptions.Version, - CertPassphrase = options.SslOptions.CertPassphrase, - CertPath = options.SslOptions.CertPath, - ServerName = options.SslOptions.ServerName, - CertificateSelectionCallback = options.SslOptions.CertificateSelectionCallback, - CertificateValidationCallback = options.SslOptions.CertificateValidationCallback, - }, - }; - }); - } - - /// - /// Add Rabbit MQ Named Connection. - /// - /// The initial service collection. - /// The name. - /// The updated service collection. - private static IServiceCollection AddRabbitNamedConnection(this IServiceCollection services, string name) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(name); - - return services - .AddSingletonNamedService(name, (serviceProvider, providerName) => - { - var options = serviceProvider.GetOptionsByName(providerName); - var clusterOptions = serviceProvider.GetRequiredService>(); - var connectionFactory = serviceProvider.GetRequiredServiceByName(providerName); - var clusterId = clusterOptions.Value.ClusterId; - var serviceId = clusterOptions.Value.ServiceId; - - return Task - .Run(() => - connectionFactory - .CreateConnection( - options - .Endpoints - .Select(endpoint => - new AmqpTcpEndpoint( - endpoint.HostName, - endpoint.Port ?? 5672)) - .ToList(), - $"queue-client-{clusterId}-{serviceId}-{name}")) - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); - }); - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/SiloBuilderExtensions.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/SiloBuilderExtensions.cs deleted file mode 100644 index 627aa70..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/SiloBuilderExtensions.cs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Orleans.Hosting; - -using System.Diagnostics.CodeAnalysis; -using Configuration; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Escendit.Orleans.Streaming.RabbitMQ.Queue; -using Escendit.Orleans.Streaming.RabbitMQ.Stream; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -/// -/// Silo Builder Extensions. -/// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.All)] -public static class SiloBuilderExtensions -{ - /// - /// Add Rabbit MQ. - /// - /// The rabbit mq. - /// The name. - /// The client builder. - public static RabbitSiloBuilder AddRabbitMqStreaming( - this ISiloBuilder builder, - string name) - { - return new RabbitSiloBuilder(builder, name); - } - - /// - /// Add Stream System. - /// - /// The initial silo builder. - /// The updated silo builder. - public static RabbitSiloBuilder WithStream( - this RabbitSiloBuilder siloBuilder) - { - ArgumentNullException.ThrowIfNull(siloBuilder); - return siloBuilder - .WithStream(_ => { }); - } - - /// - /// Add Stream. - /// - /// The initial silo builder. - /// The options. - /// The updated silo builder. - public static RabbitSiloBuilder WithStream( - this RabbitSiloBuilder siloBuilder, - Action streamOptions) - { - ArgumentNullException.ThrowIfNull(siloBuilder); - ArgumentNullException.ThrowIfNull(streamOptions); - siloBuilder - .ConfigureServices(services => - { - var rabbitStreamOptions = new RabbitStreamOptions(); - - streamOptions.Invoke(rabbitStreamOptions); - - services - .AddOptions(siloBuilder.Name) - .Configure(streamOptions); - - services - .AddOptions(siloBuilder.Name) - .Configure(options => - { - options.CacheSize = rabbitStreamOptions.CacheSize; - options.ClientProvidedName = rabbitStreamOptions.ClientProvidedName; - options.Endpoints.Clear(); - - foreach (var endpoint in rabbitStreamOptions.Endpoints) - { - options.Endpoints.Add(endpoint); - } - - options.Heartbeat = rabbitStreamOptions.Heartbeat; - options.Password = rabbitStreamOptions.Password; - options.SslOptions = rabbitStreamOptions.SslOptions; - options.StreamFailureHandlerFactory = rabbitStreamOptions.StreamFailureHandlerFactory; - options.TotalQueueCount = rabbitStreamOptions.TotalQueueCount; - options.UserName = rabbitStreamOptions.UserName; - options.VirtualHost = rabbitStreamOptions.VirtualHost; - }); - - services - .ConfigureNamedOptionForLogging(siloBuilder.Name) - .ConfigureNamedOptionForLogging(siloBuilder.Name); - }); - - _ = new RabbitSiloStreamConfigurator( - siloBuilder.Name, - configureServicesDelegate => - siloBuilder - .ConfigureServices(configureServicesDelegate)); - - return siloBuilder; - } - - /// - /// Add Classic Queue System. - /// - /// The initial silo builder. - /// The updated silo builder. - public static RabbitSiloBuilder WithQueue( - this RabbitSiloBuilder siloBuilder) - { - ArgumentNullException.ThrowIfNull(siloBuilder); - return siloBuilder - .WithQueue(_ => { }); - } - - /// - /// Add Classic Queue System. - /// - /// The initial silo builder. - /// The queue options. - /// The rabbit silo builder. - public static RabbitSiloBuilder WithQueue( - this RabbitSiloBuilder siloBuilder, - Action queueOptions) - { - ArgumentNullException.ThrowIfNull(siloBuilder); - ArgumentNullException.ThrowIfNull(queueOptions); - - siloBuilder - .ConfigureServices(services => - { - var rabbitQueueOptions = new RabbitQueueOptions(); - - queueOptions.Invoke(rabbitQueueOptions); - - services - .AddOptions(siloBuilder.Name) - .Configure(queueOptions); - - services - .AddOptions(siloBuilder.Name) - .Configure(options => - { - options.CacheSize = rabbitQueueOptions.CacheSize; - options.ClientProvidedName = rabbitQueueOptions.ClientProvidedName; - options.Endpoints.Clear(); - - foreach (var endpoint in rabbitQueueOptions.Endpoints) - { - options.Endpoints.Add(endpoint); - } - - options.Heartbeat = rabbitQueueOptions.Heartbeat; - options.Password = rabbitQueueOptions.Password; - options.SslOptions = rabbitQueueOptions.SslOptions; - options.StreamFailureHandlerFactory = rabbitQueueOptions.StreamFailureHandlerFactory; - options.TotalQueueCount = rabbitQueueOptions.TotalQueueCount; - options.UserName = rabbitQueueOptions.UserName; - options.VirtualHost = rabbitQueueOptions.VirtualHost; - }); - - services - .ConfigureNamedOptionForLogging(siloBuilder.Name) - .ConfigureNamedOptionForLogging(siloBuilder.Name); - }); - - _ = new RabbitSiloQueueConfigurator( - siloBuilder.Name, - configureServicesDelegate => - siloBuilder - .ConfigureServices(configureServicesDelegate)); - - return siloBuilder; - } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitEndpoint.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitEndpoint.cs deleted file mode 100644 index 7f2a3c1..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitEndpoint.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Options; - -/// -/// Rabbit Endpoint. -/// -public class RabbitEndpoint -{ - /// - /// Gets or sets the host name. - /// - /// The host name. - public string HostName { get; set; } = default!; - - /// - /// Gets or sets the port. - /// - /// The port. - public int? Port { get; set; } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitOptionsBase.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitOptionsBase.cs deleted file mode 100644 index ea69b05..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitOptionsBase.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Options; - -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using global::Orleans.Configuration; -using global::Orleans.Streams; - -/// -/// Shared RabbitMQ Options. -/// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.All)] -public record RabbitOptionsBase -{ - /// - /// Section Name. - /// - public const string SectionName = "Escendit.Orleans.Streaming.RabbitMQ"; - - /// - /// Gets the endpoints. - /// - /// The endpoints. - public IList Endpoints { get; init; } = new List(); - - /// - /// Gets or sets the virtual host. - /// - /// The virtual host. - public string? VirtualHost { get; set; } - - /// - /// Gets or sets the username. - /// - /// The username. - public string? UserName { get; set; } - - /// - /// Gets or sets the password. - /// - /// The password. - public string? Password { get; set; } - - /// - /// Gets or sets the heartbeat. - /// - /// The heartbeat. - public TimeSpan Heartbeat { get; set; } = TimeSpan.FromMinutes(1); - - /// - /// Gets or sets the ssl options. - /// - /// The ssl options. - public RabbitSslOptions? SslOptions { get; set; } - - /// - /// Gets or sets the client provided name. - /// - /// The client provided name. - public string? ClientProvidedName { get; set; } = SectionName; - - /// - /// Gets or sets the client provided name. Default is defined here . - /// - /// The total queue count. - public int TotalQueueCount { get; set; } = HashRingStreamQueueMapperOptions.DEFAULT_NUM_QUEUES; - - /// - /// Gets or sets the cache size. Default is defined here: . - /// - /// The cache size. - public int CacheSize { get; set; } = SimpleQueueCacheOptions.DEFAULT_CACHE_SIZE; - - /// - /// Sets the stream failure handler factory. - /// - /// The stream failure handler factory. - [Browsable(false)] - public Func> StreamFailureHandlerFactory { internal get; set; } = _ => Task.FromResult(new NoOpStreamDeliveryFailureHandler()); -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitSslOptions.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitSslOptions.cs deleted file mode 100644 index 77ffe63..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitSslOptions.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Options; - -using System.Diagnostics.CodeAnalysis; -using System.Net.Security; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; - -/// -/// Rabbit SSL Option. -/// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.All)] -public class RabbitSslOptions -{ - /// - /// Gets or sets the acceptable policy errors. - /// - /// The acceptable policy errors. - public SslPolicyErrors AcceptablePolicyErrors { get; set; } - - /// - /// Gets or sets the cert passphrase. - /// - /// The cert passphrase. - public string? CertPassphrase { get; set; } - - /// - /// Gets or sets the cert path. - /// - /// The cert path. - public string? CertPath { get; set; } - - /// - /// Gets or sets the certificate selection callback. - /// - /// The certificate selection callback. - public LocalCertificateSelectionCallback? CertificateSelectionCallback { get; set; } - - /// - /// Gets or sets the certificate validation callback. - /// - /// The certificate validation callback. - public RemoteCertificateValidationCallback? CertificateValidationCallback { get; set; } - - /// - /// Gets or sets a value indicating whether check for certificate revocation. - /// - /// The flag if we check certificate revocation. - public bool CheckCertificateRevocation { get; set; } - - /// - /// Gets the certificates. - /// - /// The certificates. - public X509CertificateCollection? Certificates { get; } = new(); - - /// - /// Gets or sets a value indicating whether ssl is enabled. - /// - /// The flag if enabled. - public bool Enabled { get; set; } - - /// - /// Gets or sets the server name. - /// - /// The server name. - public string? ServerName { get; set; } = string.Empty; - - /// - /// Gets or sets the version. - /// - /// The version. - public SslProtocols Version { get; set; } -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapterFactory.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapterFactory.cs deleted file mode 100644 index 574a961..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapterFactory.cs +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Queue; - -using Core; -using global::Orleans.Configuration; -using global::Orleans.Configuration.Overrides; -using global::Orleans.Providers.Streams.Common; -using global::Orleans.Runtime; -using global::Orleans.Serialization; -using global::Orleans.Streams; -using global::RabbitMQ.Client; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Options; -using Stream; - -#pragma warning disable CA1812 - -/// -/// Default Queue Adapter Factory. -/// -internal partial class DefaultQueueAdapterFactory : IQueueAdapterFactory -{ - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly string _name; - private readonly IConnection _connection; - private readonly RabbitQueueOptions _options; - private readonly ClusterOptions _clusterOptions; - private readonly Serializer _serializer; - private readonly IConsistentRingStreamQueueMapper _streamQueueMapper; - private readonly IQueueAdapterCache _queueAdapterCache; - private readonly Func> _streamFailureHandlerFactory; - - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The connection. - /// The options. - /// The cluster options. - /// The serializer. - /// The logger factory. - public DefaultQueueAdapterFactory( - string name, - IConnection connection, - RabbitQueueOptions options, - ClusterOptions clusterOptions, - Serializer serializer, - ILoggerFactory loggerFactory) - { - ArgumentNullException.ThrowIfNull(name); - ArgumentNullException.ThrowIfNull(connection); - ArgumentNullException.ThrowIfNull(options); - ArgumentNullException.ThrowIfNull(clusterOptions); - ArgumentNullException.ThrowIfNull(serializer); - ArgumentNullException.ThrowIfNull(loggerFactory); - - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _name = name; - _connection = connection; - _options = options; - _clusterOptions = clusterOptions; - _serializer = serializer.GetSerializer(); - _streamQueueMapper = - new HashRingBasedStreamQueueMapper( - new HashRingStreamQueueMapperOptions - { - TotalQueueCount = _options.TotalQueueCount, - }, - _name); - _queueAdapterCache = new SimpleQueueAdapterCache( - new SimpleQueueCacheOptions - { - CacheSize = _options.CacheSize, - }, - _name, - _loggerFactory); - _streamFailureHandlerFactory = _options.StreamFailureHandlerFactory; - } - - /// - public Task CreateAdapter() - { - LogCreateAdapter(_name); - - return Task - .FromResult(new DefaultQueueAdapter( - _name, - _connection, - _loggerFactory, - _options, - _clusterOptions, - _serializer, - _streamQueueMapper)); - } - - /// - public IQueueAdapterCache GetQueueAdapterCache() - { - LogGetQueueAdapterCache(_name); - return _queueAdapterCache; - } - - /// - public IStreamQueueMapper GetStreamQueueMapper() - { - LogGetStreamQueueMapper(_name); - return _streamQueueMapper; - } - - /// - public Task GetDeliveryFailureHandler(QueueId queueId) - { - LogGetDeliveryFailureHandler(_name, queueId); - return _streamFailureHandlerFactory(queueId); - } - - /// - /// Create Queue Adapter Factory. - /// - /// The service provider. - /// The name. - /// The queue adapter factory. - internal static DefaultQueueAdapterFactory Create(IServiceProvider serviceProvider, string name) - { - ArgumentNullException.ThrowIfNull(serviceProvider); - ArgumentNullException.ThrowIfNull(name); - var clusterOptions = serviceProvider.GetProviderClusterOptions(name); - var options = serviceProvider.GetOptionsByName(name); - var connection = serviceProvider.GetRequiredServiceByName(name); - - return ActivatorUtilities - .CreateInstance( - serviceProvider, - name, - connection, - options, - clusterOptions.Value); - } - - [LoggerMessage( - EventId = 100, - EventName = nameof(CreateAdapter), - Level = LogLevel.Debug, - Message = "Creating Queue Adapter for ProviderName: {name}")] - private partial void LogCreateAdapter(string name); - - [LoggerMessage( - EventId = 101, - EventName = nameof(GetQueueAdapterCache), - Level = LogLevel.Debug, - Message = "Setting Queue Adapter Cache for ProviderName: {name}")] - private partial void LogGetQueueAdapterCache(string name); - - [LoggerMessage( - EventId = 102, - EventName = nameof(GetStreamQueueMapper), - Level = LogLevel.Debug, - Message = "Getting Stream Queue Mapper for ProviderName: {name}")] - private partial void LogGetStreamQueueMapper(string name); - - [LoggerMessage( - EventId = 500, - EventName = nameof(GetDeliveryFailureHandler), - Level = LogLevel.Debug, - Message = "Getting Delivery Failure Handler for ProviderName: {name}, QueueId: {queueId}")] - private partial void LogGetDeliveryFailureHandler(string name, QueueId queueId); -} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapterFactory.cs b/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapterFactory.cs deleted file mode 100644 index 0d5297c..0000000 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapterFactory.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Stream; - -using System.Net; -using Core; -using global::Orleans; -using global::Orleans.Configuration; -using global::Orleans.Configuration.Overrides; -using global::Orleans.Providers.Streams.Common; -using global::Orleans.Serialization; -using global::Orleans.Streams; -using global::RabbitMQ.Stream.Client; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Options; - -#pragma warning disable CA1812 - -/// -/// Default Stream Adapter Factory. -/// -internal partial class DefaultStreamAdapterFactory : IQueueAdapterFactory -{ - private readonly ILoggerFactory _loggerFactory; - private readonly ILogger _logger; - private readonly string _name; - private readonly RabbitStreamOptions _options; - private readonly ClusterOptions _clusterOptions; - private readonly Serializer _serializer; - private readonly IConsistentRingStreamQueueMapper _streamQueueMapper; - private readonly IQueueAdapterCache _queueAdapterCache; - private readonly Func> _streamFailureHandlerFactory; - - /// - /// Initializes a new instance of the class. - /// - /// The name. - /// The options. - /// The cluster options. - /// The serializer. - /// The logger factory. - public DefaultStreamAdapterFactory( - string name, - RabbitStreamOptions options, - ClusterOptions clusterOptions, - Serializer serializer, - ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory; - _logger = _loggerFactory.CreateLogger(); - _name = name; - _options = options; - _clusterOptions = clusterOptions; - _serializer = serializer.GetSerializer(); - _streamQueueMapper = - new HashRingBasedStreamQueueMapper( - new HashRingStreamQueueMapperOptions - { - TotalQueueCount = _options.TotalQueueCount, - }, - _name); - _queueAdapterCache = new SimpleQueueAdapterCache( - new SimpleQueueCacheOptions - { - CacheSize = _options.CacheSize, - }, - _name, - _loggerFactory); - _streamFailureHandlerFactory = _options.StreamFailureHandlerFactory; - } - - /// - public async Task CreateAdapter() - { - LogCreateAdapter(_name); - - var streamSystem = await StreamSystem - .Create(new StreamSystemConfig - { - Endpoints = _options.Endpoints.Select(s => new DnsEndPoint(s.HostName, s.Port ?? 5552) as EndPoint) - .ToList(), - Heartbeat = _options.Heartbeat, - Password = _options.Password, - UserName = _options.UserName, - VirtualHost = _options.VirtualHost, - ClientProvidedName = _options.ClientProvidedName, - Ssl = _options.SslOptions is null - ? null - : new SslOption - { - AcceptablePolicyErrors = _options.SslOptions.AcceptablePolicyErrors, - ServerName = _options.SslOptions.ServerName, - CertificateSelectionCallback = _options.SslOptions.CertificateSelectionCallback, - CertificateValidationCallback = _options.SslOptions.CertificateValidationCallback, - CertPassphrase = _options.SslOptions.CertPassphrase, - CertPath = _options.SslOptions.CertPath, - Certs = _options.SslOptions.Certificates, - CheckCertificateRevocation = _options.SslOptions.CheckCertificateRevocation, - Enabled = _options.SslOptions.Enabled, - Version = _options.SslOptions.Version, - }, - }); - - return new DefaultStreamAdapter(_name, _loggerFactory, _clusterOptions, _serializer, _streamQueueMapper, streamSystem); - } - - /// - public IQueueAdapterCache GetQueueAdapterCache() - { - LogGetQueueAdapterCache(_name); - return _queueAdapterCache; - } - - /// - public IStreamQueueMapper GetStreamQueueMapper() - { - LogGetStreamQueueMapper(_name); - return _streamQueueMapper; - } - - /// - public Task GetDeliveryFailureHandler(QueueId queueId) - { - LogGetDeliveryFailureHandler(_name, queueId); - return _streamFailureHandlerFactory(queueId); - } - - /// - /// Create Queue Adapter Factory. - /// - /// The service provider. - /// The name. - /// The queue adapter factory. - internal static DefaultStreamAdapterFactory Create(IServiceProvider serviceProvider, string name) - { - ArgumentNullException.ThrowIfNull(serviceProvider); - ArgumentNullException.ThrowIfNull(name); - - var options = serviceProvider.GetOptionsByName(name); - var clusterOptions = serviceProvider.GetProviderClusterOptions(name); - return ActivatorUtilities.CreateInstance(serviceProvider, name, options, clusterOptions.Value); - } - - [LoggerMessage( - EventId = 100, - EventName = nameof(CreateAdapter), - Level = LogLevel.Debug, - Message = "Creating Queue Adapter for ProviderName: {name}")] - private partial void LogCreateAdapter(string name); - - [LoggerMessage( - EventId = 101, - EventName = nameof(GetQueueAdapterCache), - Level = LogLevel.Debug, - Message = "Setting Queue Adapter Cache for ProviderName: {name}")] - private partial void LogGetQueueAdapterCache(string name); - - [LoggerMessage( - EventId = 102, - EventName = nameof(GetStreamQueueMapper), - Level = LogLevel.Debug, - Message = "Getting Stream Queue Mapper for ProviderName: {name}")] - private partial void LogGetStreamQueueMapper(string name); - - [LoggerMessage( - EventId = 500, - EventName = nameof(GetDeliveryFailureHandler), - Level = LogLevel.Debug, - Message = "Getting Delivery Failure Handler for ProviderName: {name}, QueueId: {queueId}")] - private partial void LogGetDeliveryFailureHandler(string name, QueueId queueId); -} diff --git a/src/Orleans/AmqpProtocol/Builder/RabbitMqClientOptionsBuilder.cs b/src/Orleans/AmqpProtocol/Builder/RabbitMqClientOptionsBuilder.cs new file mode 100644 index 0000000..5a6b78b --- /dev/null +++ b/src/Orleans/AmqpProtocol/Builder/RabbitMqClientOptionsBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Builder; + +using Microsoft.Extensions.DependencyInjection; +using RabbitMQ.Builder; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Client Options Builder. +/// +internal class RabbitMqClientOptionsBuilder : IRabbitMqClientOptionsBuilder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The services. + /// The configurator. + public RabbitMqClientOptionsBuilder( + string name, + IServiceCollection services, + ClusterClientPersistentStreamConfigurator configurator) + { + Name = name; + Services = services; + Configurator = configurator; + } + + /// + public string Name { get; } + + /// + public IServiceCollection Services { get; } + + /// + public ClusterClientPersistentStreamConfigurator Configurator { get; } + + /// + public IServiceCollection Build() + { + return Services; + } +} diff --git a/src/Orleans/AmqpProtocol/Builder/RabbitMqSiloOptionsBuilder.cs b/src/Orleans/AmqpProtocol/Builder/RabbitMqSiloOptionsBuilder.cs new file mode 100644 index 0000000..4f3d6f8 --- /dev/null +++ b/src/Orleans/AmqpProtocol/Builder/RabbitMqSiloOptionsBuilder.cs @@ -0,0 +1,41 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Builder; + +using Microsoft.Extensions.DependencyInjection; +using RabbitMQ.Builder; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Silo Options Builder. +/// +internal class RabbitMqSiloOptionsBuilder : IRabbitMqSiloOptionsBuilder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The services. + /// The configurator. + public RabbitMqSiloOptionsBuilder(string name, IServiceCollection services, SiloPersistentStreamConfigurator configurator) + { + Name = name; + Services = services; + Configurator = configurator; + } + + /// + public string Name { get; } + + /// + public IServiceCollection Services { get; } + + /// + public SiloPersistentStreamConfigurator Configurator { get; } + + /// + public IServiceCollection Build() + { + return Services; + } +} diff --git a/src/Orleans/AmqpProtocol/Configuration/ClusterClientConfigurator.cs b/src/Orleans/AmqpProtocol/Configuration/ClusterClientConfigurator.cs new file mode 100644 index 0000000..0375d35 --- /dev/null +++ b/src/Orleans/AmqpProtocol/Configuration/ClusterClientConfigurator.cs @@ -0,0 +1,37 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Configuration; + +using Hosting; +using Microsoft.Extensions.DependencyInjection; +using Provider; + +/// +/// Cluster Client Rabbit MQ AMQP Configurator. +/// +internal class ClusterClientConfigurator : ClusterClientPersistentStreamConfigurator +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The client builder. + public ClusterClientConfigurator( + string name, + IClientBuilder clientBuilder) + : base(name, clientBuilder, AmqpProtocolAdapterFactory.Create) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(clientBuilder); + clientBuilder + .ConfigureServices(configure => + { + configure + .AddClientStreaming(); + configure + .AddOrleansNamedSingletonFactory(name, AmqpProtocolAdapterFactory.Create) + .AddOptions(name); + }); + } +} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitQueueOptions.cs b/src/Orleans/AmqpProtocol/Configuration/QueueOptions.cs similarity index 56% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitQueueOptions.cs rename to src/Orleans/AmqpProtocol/Configuration/QueueOptions.cs index 7c4a25a..1df5559 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitQueueOptions.cs +++ b/src/Orleans/AmqpProtocol/Configuration/QueueOptions.cs @@ -1,22 +1,17 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Options; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Configuration; -using System.Diagnostics.CodeAnalysis; using global::RabbitMQ.Client; +using RabbitMQ.Configuration; /// -/// Rabbit MQ Stream Provider Options. +/// Escendit.Orleans.Streaming.RabbitMQ.Tests AMQP Queue Options. /// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicProperties)] -public record RabbitQueueOptions : RabbitOptionsBase +public class QueueOptions : OptionsBase { - /// - /// Default Exchange Key. - /// - private const string DefaultExchangekey = "exchange"; + private const string DefaultExchangeKey = "queue"; /// /// Gets or sets the exchange type. @@ -29,7 +24,7 @@ public record RabbitQueueOptions : RabbitOptionsBase /// Gets or sets the exchange name. /// /// The exchange name. - public string Name { get; set; } = DefaultExchangekey; + public string Name { get; set; } = DefaultExchangeKey; /// /// Gets or sets a value indicating whether the queue is durable. @@ -42,21 +37,4 @@ public record RabbitQueueOptions : RabbitOptionsBase /// /// The flag if the queue is exclusive. public bool IsExclusive { get; set; } - - /// - /// Gets the default Port. - /// - /// The default port. - public int DefaultPort - { - get - { - if (SslOptions is null) - { - return 5672; - } - - return SslOptions.Enabled ? 5671 : 5672; - } - } } diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitSiloStreamConfigurator.cs b/src/Orleans/AmqpProtocol/Configuration/SiloConfigurator.cs similarity index 51% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitSiloStreamConfigurator.cs rename to src/Orleans/AmqpProtocol/Configuration/SiloConfigurator.cs index 7ee7253..b76b4c0 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Configuration/RabbitSiloStreamConfigurator.cs +++ b/src/Orleans/AmqpProtocol/Configuration/SiloConfigurator.cs @@ -1,28 +1,26 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Orleans.Hosting; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Configuration; -using Configuration; -using Escendit.Orleans.Streaming.RabbitMQ.Options; -using Escendit.Orleans.Streaming.RabbitMQ.Stream; +using Hosting; using Microsoft.Extensions.DependencyInjection; -using Runtime; +using Provider; /// -/// Silo Rabbit MQ Stream Configurator. +/// Silo Configurator. /// -internal class RabbitSiloStreamConfigurator : SiloPersistentStreamConfigurator +internal class SiloConfigurator : SiloPersistentStreamConfigurator { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name. /// The configure delegate. - public RabbitSiloStreamConfigurator( + public SiloConfigurator( string name, Action> configureDelegate) - : base(name, configureDelegate, DefaultStreamAdapterFactory.Create) + : base(name, configureDelegate, AmqpProtocolAdapterFactory.Create) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(configureDelegate); @@ -30,8 +28,10 @@ public RabbitSiloStreamConfigurator( ConfigureDelegate(services => { services - .AddSingletonNamedService(name, DefaultStreamAdapterFactory.Create) - .ConfigureNamedOptionForLogging(name); + .AddSiloStreaming(); + services + .AddOrleansNamedSingletonFactory(name, AmqpProtocolAdapterFactory.Create) + .AddOptions(name); }); } } diff --git a/src/Orleans/AmqpProtocol/Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.csproj b/src/Orleans/AmqpProtocol/Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.csproj new file mode 100644 index 0000000..8fce237 --- /dev/null +++ b/src/Orleans/AmqpProtocol/Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.csproj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/Orleans/AmqpProtocol/Hosting/ClientBuilderExtensions.cs b/src/Orleans/AmqpProtocol/Hosting/ClientBuilderExtensions.cs new file mode 100644 index 0000000..db71168 --- /dev/null +++ b/src/Orleans/AmqpProtocol/Hosting/ClientBuilderExtensions.cs @@ -0,0 +1,50 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Orleans.Hosting; + +using Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Configuration; +using Escendit.Orleans.Streaming.RabbitMQ.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Streams; +using ConnectionOptions = Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions.ConnectionOptions; + +/// +/// Client Builder Extensions. +/// +public static class ClientBuilderExtensions +{ + /// + /// Use AMQP Protocol. + /// + /// The client builder. + /// The configure options. + /// The client options builder. + public static IRabbitMqClientOptionsBuilder UseAmqpProtocol(this IRabbitMqClientProtocolBuilder clientBuilder, Action configureOptions) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + var configurator = new ClusterClientConfigurator(clientBuilder.Name, clientBuilder); + configurator.Configure(options => options.Configure(configureOptions)); + clientBuilder.ConfigureServices(services => services + .AddRabbitMqConnection(clientBuilder.Name, configureOptions)); + return new RabbitMqClientOptionsBuilder(clientBuilder.Name, clientBuilder.Services, configurator) + .AddSimpleQueueCache() + .AddHashRingStreamQueueMapper(); + } + + /// + /// Configure Stream Pub Sub. + /// + /// The initial client options builder. + /// The stream pub sub type. + /// The rabbitmq client options builder. + public static IRabbitMqClientOptionsBuilder ConfigureStreamPubSub(this IRabbitMqClientOptionsBuilder clientBuilder, StreamPubSubType streamPubSubType = StreamPubSubType.ExplicitGrainBasedAndImplicit) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + clientBuilder.Configurator.ConfigureStreamPubSub(streamPubSubType); + return clientBuilder; + } +} diff --git a/src/Orleans/AmqpProtocol/Hosting/SiloBuilderExtensions.cs b/src/Orleans/AmqpProtocol/Hosting/SiloBuilderExtensions.cs new file mode 100644 index 0000000..5d0be58 --- /dev/null +++ b/src/Orleans/AmqpProtocol/Hosting/SiloBuilderExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Orleans.Hosting; + +using Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions; +using Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Configuration; +using Escendit.Orleans.Streaming.RabbitMQ.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Streams; + +/// +/// Silo Builder Extensions. +/// +public static class SiloBuilderExtensions +{ + /// + /// Use AMQP Protocol. + /// + /// The silo builder. + /// The configure options. + /// The client options builder. + public static IRabbitMqSiloOptionsBuilder UseAmqpProtocol(this IRabbitMqSiloProtocolBuilder siloBuilder, Action configureOptions) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + return new RabbitMqSiloOptionsBuilder( + siloBuilder.Name, + siloBuilder.Services, + new SiloConfigurator( + siloBuilder.Name, + configureDelegate => siloBuilder + .ConfigureServices(configureDelegate) + .ConfigureServices(services => services + .AddRabbitMqConnection(siloBuilder.Name, configureOptions)))) + .AddHashRingStreamQueueMapper() + .AddSimpleQueueCache(); + } + + /// + /// Configure Stream Pub Sub. + /// + /// The initial silo options builder. + /// The stream pub sub type. + /// The rabbitmq client options builder. + public static IRabbitMqSiloOptionsBuilder ConfigureStreamPubSub(this IRabbitMqSiloOptionsBuilder siloBuilder, StreamPubSubType streamPubSubType = StreamPubSubType.ExplicitGrainBasedAndImplicit) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + siloBuilder.Configurator.ConfigureStreamPubSub(streamPubSubType); + return siloBuilder; + } +} diff --git a/src/Orleans/AmqpProtocol/Properties/AssemblyInfo.cs b/src/Orleans/AmqpProtocol/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e804d2c --- /dev/null +++ b/src/Orleans/AmqpProtocol/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(false)] +[assembly: InternalsVisibleTo("Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests")] diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapter.cs b/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapter.cs similarity index 63% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapter.cs rename to src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapter.cs index dc9b967..e542fc5 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapter.cs +++ b/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapter.cs @@ -1,8 +1,9 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Queue; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Provider; +using Configuration; using Core; using global::Orleans.Configuration; using global::Orleans.Runtime; @@ -10,109 +11,106 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Queue; using global::Orleans.Streams; using global::RabbitMQ.Client; using Microsoft.Extensions.Logging; -using Options; /// -/// Default Queue Adapter. +/// Escendit.Orleans.Streaming.RabbitMQ.Tests AMQP Protocol Adapter. /// -internal partial class DefaultQueueAdapter : IQueueAdapter +internal sealed partial class AmqpProtocolAdapter : IQueueAdapter { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; - private readonly RabbitQueueOptions _options; + private readonly QueueOptions _queueOptions; private readonly ClusterOptions _clusterOptions; - private readonly Serializer _serializer; - private readonly IConsistentRingStreamQueueMapper _consistentRingStreamQueueMapper; + private readonly Serializer _serializer; + private readonly IStreamQueueMapper _streamQueueMapper; private readonly IModel _publisherChannel; private readonly IModel _consumerChannel; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name. /// The connection. /// The logger factory. - /// The options. + /// The queue options. /// The cluster options. /// The serializer. - /// The consistent ring stream queue mapper. - public DefaultQueueAdapter( + /// The stream queue mapper. + public AmqpProtocolAdapter( string name, IConnection connection, ILoggerFactory loggerFactory, - RabbitQueueOptions options, + QueueOptions queueOptions, ClusterOptions clusterOptions, - Serializer serializer, - IConsistentRingStreamQueueMapper consistentRingStreamQueueMapper) + Serializer serializer, + IStreamQueueMapper streamQueueMapper) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(loggerFactory); - ArgumentNullException.ThrowIfNull(options); + ArgumentNullException.ThrowIfNull(queueOptions); ArgumentNullException.ThrowIfNull(serializer); - ArgumentNullException.ThrowIfNull(consistentRingStreamQueueMapper); + ArgumentNullException.ThrowIfNull(streamQueueMapper); Name = name; IsRewindable = false; + Direction = StreamProviderDirection.ReadWrite; _loggerFactory = loggerFactory; - _logger = loggerFactory.CreateLogger(); - _options = options; + _logger = loggerFactory.CreateLogger(); + _queueOptions = queueOptions; _clusterOptions = clusterOptions; _serializer = serializer; - _consistentRingStreamQueueMapper = consistentRingStreamQueueMapper; + _streamQueueMapper = streamQueueMapper; _publisherChannel = connection.CreateModel(); _consumerChannel = connection.CreateModel(); - _publisherChannel.ExchangeDeclare(NamingUtility.CreateNameForQueue(_clusterOptions, _options.Name), _options.Type, _options.IsDurable); + _publisherChannel.ExchangeDeclare(NamingUtility.CreateNameForQueue(_clusterOptions, _queueOptions.Name), _queueOptions.Type, _queueOptions.IsDurable); } /// public string Name { get; } - /// + /// public bool IsRewindable { get; } - /// - public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite; + /// + public StreamProviderDirection Direction { get; } - /// + /// public Task QueueMessageBatchAsync( StreamId streamId, IEnumerable events, - StreamSequenceToken token, + StreamSequenceToken? token, Dictionary requestContext) { + ArgumentNullException.ThrowIfNull(streamId); + ArgumentNullException.ThrowIfNull(events); LogQueueMessageBatch(Name, streamId, token); - if (token is not null) - { - throw new InvalidOperationException("stream sequence token is not supported"); - } - - var queueId = _consistentRingStreamQueueMapper.GetQueueForStream(streamId); + var queueId = _streamQueueMapper.GetQueueForStream(streamId); var queueName = NamingUtility.CreateNameForQueue(_clusterOptions, queueId); - var exchangeName = NamingUtility.CreateNameForQueue(_clusterOptions, _options.Name); + var exchangeName = NamingUtility.CreateNameForQueue(_clusterOptions, _queueOptions.Name); - var container = new RabbitBatchContainer( + var container = new RabbitMqBatchContainer( streamId, events.Cast().ToList(), requestContext, - new RabbitStreamSequenceToken(0)); + token is null ? default : new RabbitMqStreamSequenceToken(token)); var data = _serializer .SerializeToArray(container); - _publisherChannel.BasicPublish(exchangeName, queueName, false, null, data); + _publisherChannel.BasicPublish(exchangeName, queueName, false, default, data); return Task.CompletedTask; } - /// + /// public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { LogCreateReceiver(Name, queueId); - return new DefaultQueueAdapterReceiver( + return new AmqpProtocolAdapterReceiver( Name, - _options, + _queueOptions, _clusterOptions, queueId, _loggerFactory, @@ -125,7 +123,7 @@ public IQueueAdapterReceiver CreateReceiver(QueueId queueId) EventName = "Queue Message Batch", Level = LogLevel.Debug, Message = "Queueing Message Batch for ProviderName: {name}, StreamId: {streamId}, StreamSequenceToken: {sequenceToken}")] - private partial void LogQueueMessageBatch(string name, StreamId streamId, StreamSequenceToken sequenceToken); + private partial void LogQueueMessageBatch(string name, StreamId streamId, StreamSequenceToken? sequenceToken); [LoggerMessage( EventId = 101, diff --git a/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapterFactory.cs b/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapterFactory.cs new file mode 100644 index 0000000..b0a5ed1 --- /dev/null +++ b/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapterFactory.cs @@ -0,0 +1,143 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +#pragma warning disable CA1812 + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Provider; + +using Configuration; +using Core; +using global::Orleans.Configuration; +using global::Orleans.Configuration.Overrides; +using global::Orleans.Serialization; +using global::Orleans.Streams; +using global::RabbitMQ.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using RabbitMQ.Provider; + +/// +/// AMQP Protocol Adapter Factory. +/// +internal sealed class AmqpProtocolAdapterFactory : AdapterFactoryBase +{ + private readonly ILoggerFactory _loggerFactory; + private readonly string _name; + private readonly IConnection _connection; + private readonly IStreamQueueMapper _streamQueueMapper; + private readonly IQueueAdapterCache _queueAdapterCache; + private readonly QueueOptions _queueOptions; + private readonly ClusterOptions _clusterOptions; + private readonly Serializer _serializer; + private readonly Func> _streamFailureHandlerFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The connection. + /// The stream queue mapper. + /// The queue adapter cache. + /// The queue options. + /// The cluster options. + /// The serializer. + /// The logger factory. + public AmqpProtocolAdapterFactory( + string name, + IConnection connection, + IStreamQueueMapper streamQueueMapper, + IQueueAdapterCache queueAdapterCache, + QueueOptions queueOptions, + ClusterOptions clusterOptions, + Serializer serializer, + ILoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(connection); + ArgumentNullException.ThrowIfNull(streamQueueMapper); + ArgumentNullException.ThrowIfNull(clusterOptions); + ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(loggerFactory); + + _loggerFactory = loggerFactory; + _name = name; + _connection = connection; + _queueOptions = queueOptions; + _clusterOptions = clusterOptions; + _serializer = serializer.GetSerializer(); + _streamQueueMapper = streamQueueMapper; + _queueAdapterCache = queueAdapterCache; + _streamFailureHandlerFactory = queueOptions.StreamFailureHandler; + } + + /// + public override Task CreateAdapter() + { + LogCreateAdapter(_name); + + return Task + .FromResult(new AmqpProtocolAdapter( + _name, + _connection, + _loggerFactory, + _queueOptions, + _clusterOptions, + _serializer, + _streamQueueMapper)); + } + + /// + public override IQueueAdapterCache GetQueueAdapterCache() + { + LogGetQueueAdapterCache(_name); + return _queueAdapterCache; + } + + /// + public override IStreamQueueMapper GetStreamQueueMapper() + { + LogGetStreamQueueMapper(_name); + return _streamQueueMapper; + } + + /// + public override Task GetDeliveryFailureHandler(QueueId queueId) + { + LogGetDeliveryFailureHandler(_name, queueId); + return _streamFailureHandlerFactory(queueId); + } + + /// + /// Create Queue Adapter Factory. + /// + /// The service provider. + /// The name. + /// The queue adapter factory. + internal static AmqpProtocolAdapterFactory Create(IServiceProvider serviceProvider, object? name) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(name); + + if (name is not string factoryName) + { + throw new ArgumentException("invalid name"); + } + + var clusterOptions = serviceProvider.GetProviderClusterOptions(factoryName); + var queueOptions = serviceProvider.GetOptionsByName(factoryName); + var connection = serviceProvider.GetRequiredOrleansServiceByName(factoryName); + var streamQueueMapper = serviceProvider.GetRequiredOrleansServiceByName(factoryName); + var adapterCache = serviceProvider.GetRequiredOrleansServiceByName(factoryName); + + return ActivatorUtilities + .CreateInstance( + serviceProvider, + name, + connection, + streamQueueMapper, + adapterCache, + queueOptions, + clusterOptions.Value); + } +} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapterReceiver.cs b/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapterReceiver.cs similarity index 50% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapterReceiver.cs rename to src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapterReceiver.cs index 128e446..b9f904c 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Queue/DefaultQueueAdapterReceiver.cs +++ b/src/Orleans/AmqpProtocol/Provider/AmqpProtocolAdapterReceiver.cs @@ -1,74 +1,74 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Queue; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Provider; +using Configuration; using Core; using global::Orleans.Configuration; using global::Orleans.Serialization; using global::Orleans.Streams; using global::RabbitMQ.Client; using Microsoft.Extensions.Logging; -using Options; +using RabbitMQ.Provider; /// -/// Default Queue Adapter Receiver. +/// Escendit.Orleans.Streaming.RabbitMQ.Tests AMQP Protocol Adapter Receiver. /// -internal partial class DefaultQueueAdapterReceiver : IQueueAdapterReceiver +internal sealed class AmqpProtocolAdapterReceiver : AdapterReceiverBase { private readonly string _name; - private readonly RabbitQueueOptions _options; + private readonly QueueOptions _queueOptions; private readonly ClusterOptions _clusterOptions; private readonly QueueId _queueId; - private readonly ILogger _logger; - private readonly Serializer _serializer; + private readonly Serializer _serializer; private readonly IModel _channel; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name. - /// The options. + /// The options. /// The cluster options. /// The queue id. /// The logger factory. /// The serializer. /// The channel. - public DefaultQueueAdapterReceiver( + public AmqpProtocolAdapterReceiver( string name, - RabbitQueueOptions options, + QueueOptions queueOptions, ClusterOptions clusterOptions, QueueId queueId, ILoggerFactory loggerFactory, - Serializer serializer, + Serializer serializer, IModel channel) + : base(loggerFactory.CreateLogger($"Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.{queueId}")) { ArgumentNullException.ThrowIfNull(name); ArgumentNullException.ThrowIfNull(loggerFactory); ArgumentNullException.ThrowIfNull(serializer); ArgumentNullException.ThrowIfNull(channel); _name = name; - _options = options; + _queueOptions = queueOptions; _clusterOptions = clusterOptions; _queueId = queueId; - _logger = loggerFactory.CreateLogger(); _serializer = serializer; _channel = channel; } /// - public Task Initialize(TimeSpan timeout) + public override Task Initialize(TimeSpan timeout) { LogInitialize(_name, _queueId); var queueName = NamingUtility.CreateNameForQueue(_clusterOptions, _queueId); - var exchangeName = NamingUtility.CreateNameForQueue(_clusterOptions, _options.Name); - _channel.QueueDeclare(queueName, _options.IsDurable, _options.IsExclusive); + var exchangeName = NamingUtility.CreateNameForQueue(_clusterOptions, _queueOptions.Name); + _channel.QueueDeclare(queueName, _queueOptions.IsDurable, _queueOptions.IsExclusive); _channel.QueueBind(queueName, exchangeName, queueName); return Task.CompletedTask; } /// - public Task> GetQueueMessagesAsync(int maxCount) + public override Task> GetQueueMessagesAsync(int maxCount) { LogGetQueueMessages(_name, _queueId, maxCount); ArgumentNullException.ThrowIfNull(_channel); @@ -83,11 +83,7 @@ public Task> GetQueueMessagesAsync(int maxCount) { LogMessageHandlerIncomingMessage(_name, _queueId, response.Body.Length); var container = _serializer.Deserialize(response.Body); - - container.UpdateSequenceToken( - new RabbitStreamSequenceToken( - Convert.ToInt64(response.DeliveryTag))); - + container.UpdateDeliveryTag(response.DeliveryTag); batchContainers.Add(container); continue; } @@ -100,57 +96,22 @@ public Task> GetQueueMessagesAsync(int maxCount) } /// - public Task MessagesDeliveredAsync(IList messages) + public override Task MessagesDeliveredAsync(IList messages) { LogMessagesDelivered(_name, _queueId, messages.Count); - foreach (var batchContainer in messages) + foreach (var deliveryTag in messages.Cast().Select(s => s.DeliveryTag)) { - _channel.BasicAck(Convert.ToUInt64(batchContainer.SequenceToken.SequenceNumber), false); + _channel.BasicAck(deliveryTag, false); } return Task.CompletedTask; } /// - public Task Shutdown(TimeSpan timeout) + public override Task Shutdown(TimeSpan timeout) { LogShutdown(_name, _queueId); return Task.CompletedTask; } - - [LoggerMessage( - EventId = 100, - EventName = "Log Initialize", - Level = LogLevel.Debug, - Message = "Initializing Receiver for ProviderName: {name}, QueueId: {queueId}")] - private partial void LogInitialize(string name, QueueId queueId); - - [LoggerMessage( - EventId = 101, - EventName = "Log Message Handler Incoming Message", - Level = LogLevel.Debug, - Message = "Incoming Message for ProviderName: {name}, QueueId: {queueId}, Size: {size}")] - private partial void LogMessageHandlerIncomingMessage(string name, QueueId queueId, int size); - - [LoggerMessage( - EventId = 102, - EventName = "Log Get Queue Messages", - Level = LogLevel.Debug, - Message = "Getting Queue Messages for ProviderName: {name}, QueueId {queueId}, MaxCount: {maxCount}")] - private partial void LogGetQueueMessages(string name, QueueId queueId, int maxCount); - - [LoggerMessage( - EventId = 103, - EventName = "Log Messages Delivered", - Level = LogLevel.Debug, - Message = "Delivered Messages for ProviderName: {name}, QueueId: {queueId}, Count: {count}")] - private partial void LogMessagesDelivered(string name, QueueId queueId, int count); - - [LoggerMessage( - EventId = 104, - EventName = "Log Shutdown", - Level = LogLevel.Debug, - Message = "Shutting down for ProviderName: {name}, QueueId: {queueId}")] - private partial void LogShutdown(string name, QueueId queueId); } diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/IRabbitConnectionFactoryBuilder.cs b/src/Orleans/RabbitMQ/Builder/IRabbitMqBuilder.cs similarity index 60% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/IRabbitConnectionFactoryBuilder.cs rename to src/Orleans/RabbitMQ/Builder/IRabbitMqBuilder.cs index 363ef32..9218f11 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Hosting/IRabbitConnectionFactoryBuilder.cs +++ b/src/Orleans/RabbitMQ/Builder/IRabbitMqBuilder.cs @@ -1,24 +1,24 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Hosting; +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; using Microsoft.Extensions.DependencyInjection; /// -/// Rabbit Connection Factory Builder. +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Builder. /// -public interface IRabbitConnectionFactoryBuilder +public interface IRabbitMqBuilder { - /// - /// Gets the service collection. - /// - /// The services. - IServiceCollection Services { get; } - /// /// Gets the name. /// /// The name. string Name { get; } + + /// + /// Finish builder. + /// + /// The updated service collection. + IServiceCollection Build(); } diff --git a/src/Orleans/RabbitMQ/Builder/IRabbitMqClientOptionsBuilder.cs b/src/Orleans/RabbitMQ/Builder/IRabbitMqClientOptionsBuilder.cs new file mode 100644 index 0000000..a4b6d55 --- /dev/null +++ b/src/Orleans/RabbitMQ/Builder/IRabbitMqClientOptionsBuilder.cs @@ -0,0 +1,16 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Client Options Builder. +/// +public interface IRabbitMqClientOptionsBuilder : IRabbitMqBuilder, IClientBuilder +{ + /// + /// Gets the configurator. + /// + /// The configurator. + internal ClusterClientPersistentStreamConfigurator Configurator { get; } +} diff --git a/src/Orleans/RabbitMQ/Builder/IRabbitMqClientProtocolBuilder.cs b/src/Orleans/RabbitMQ/Builder/IRabbitMqClientProtocolBuilder.cs new file mode 100644 index 0000000..ea33f50 --- /dev/null +++ b/src/Orleans/RabbitMQ/Builder/IRabbitMqClientProtocolBuilder.cs @@ -0,0 +1,11 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Client Protocol Builder. +/// +public interface IRabbitMqClientProtocolBuilder : IRabbitMqBuilder, IClientBuilder +{ +} diff --git a/src/Orleans/RabbitMQ/Builder/IRabbitMqSiloOptionsBuilder.cs b/src/Orleans/RabbitMQ/Builder/IRabbitMqSiloOptionsBuilder.cs new file mode 100644 index 0000000..60da54a --- /dev/null +++ b/src/Orleans/RabbitMQ/Builder/IRabbitMqSiloOptionsBuilder.cs @@ -0,0 +1,16 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Silo Options Builder. +/// +public interface IRabbitMqSiloOptionsBuilder : IRabbitMqBuilder, ISiloBuilder +{ + /// + /// Gets the configurator. + /// + /// The configurator. + internal SiloPersistentStreamConfigurator Configurator { get; } +} diff --git a/src/Orleans/RabbitMQ/Builder/IRabbitMqSiloProtocolBuilder.cs b/src/Orleans/RabbitMQ/Builder/IRabbitMqSiloProtocolBuilder.cs new file mode 100644 index 0000000..15c1664 --- /dev/null +++ b/src/Orleans/RabbitMQ/Builder/IRabbitMqSiloProtocolBuilder.cs @@ -0,0 +1,11 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Silo Protocol Builder. +/// +public interface IRabbitMqSiloProtocolBuilder : IRabbitMqBuilder, ISiloBuilder +{ +} diff --git a/src/Orleans/RabbitMQ/Builder/RabbitMqClientProtocolBuilder.cs b/src/Orleans/RabbitMQ/Builder/RabbitMqClientProtocolBuilder.cs new file mode 100644 index 0000000..aa19c61 --- /dev/null +++ b/src/Orleans/RabbitMQ/Builder/RabbitMqClientProtocolBuilder.cs @@ -0,0 +1,35 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; + +using Microsoft.Extensions.DependencyInjection; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Client Protocol Builder. +/// +public class RabbitMqClientProtocolBuilder : IRabbitMqClientProtocolBuilder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The services. + public RabbitMqClientProtocolBuilder(string name, IServiceCollection services) + { + Name = name; + Services = services; + } + + /// + public string Name { get; } + + /// + public IServiceCollection Services { get; } + + /// + public IServiceCollection Build() + { + return Services; + } +} diff --git a/src/Orleans/RabbitMQ/Builder/RabbitMqSiloProtocolBuilder.cs b/src/Orleans/RabbitMQ/Builder/RabbitMqSiloProtocolBuilder.cs new file mode 100644 index 0000000..f74ac76 --- /dev/null +++ b/src/Orleans/RabbitMQ/Builder/RabbitMqSiloProtocolBuilder.cs @@ -0,0 +1,35 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Builder; + +using Microsoft.Extensions.DependencyInjection; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Silo Protocol Builder. +/// +public class RabbitMqSiloProtocolBuilder : IRabbitMqSiloProtocolBuilder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The initial services. + public RabbitMqSiloProtocolBuilder(string name, IServiceCollection services) + { + Name = name; + Services = services; + } + + /// + public string Name { get; } + + /// + public IServiceCollection Services { get; } + + /// + public IServiceCollection Build() + { + return Services; + } +} diff --git a/src/Orleans/RabbitMQ/Configuration/OptionsBase.cs b/src/Orleans/RabbitMQ/Configuration/OptionsBase.cs new file mode 100644 index 0000000..f119956 --- /dev/null +++ b/src/Orleans/RabbitMQ/Configuration/OptionsBase.cs @@ -0,0 +1,21 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Configuration; + +using System.ComponentModel; +using global::Orleans.Streams; + +/// +/// Options Base. +/// +public abstract class OptionsBase +{ + /// + /// Gets the stream failure handler. + /// + /// The stream failure handler. + [Browsable(false)] + public Func> StreamFailureHandler { get; internal set; } = _ => + Task.FromResult(new NoOpStreamDeliveryFailureHandler()); +} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Core/NamingUtility.cs b/src/Orleans/RabbitMQ/Core/NamingUtility.cs similarity index 100% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Core/NamingUtility.cs rename to src/Orleans/RabbitMQ/Core/NamingUtility.cs diff --git a/src/Orleans/RabbitMQ/Core/RabbitMqBatchContainer.cs b/src/Orleans/RabbitMQ/Core/RabbitMqBatchContainer.cs new file mode 100644 index 0000000..9a967c1 --- /dev/null +++ b/src/Orleans/RabbitMQ/Core/RabbitMqBatchContainer.cs @@ -0,0 +1,188 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Core; + +using System.Text.Json.Serialization; +using global::Orleans.Runtime; +using global::Orleans.Streams; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Batch Container. +/// +[Serializable] +[GenerateSerializer] +[Alias("rabbitmq-batch-container")] +internal class RabbitMqBatchContainer : IBatchContainer, IComparable +{ + [Id(0)] + [JsonPropertyName("events")] + private readonly ICollection _events; + + [Id(1)] + [JsonPropertyName("requestContext")] + private readonly Dictionary? _requestContext; + + [Id(2)] + [JsonPropertyName("sequenceToken")] + private RabbitMqStreamSequenceToken? _sequenceToken; + + [Id(4)] + [JsonPropertyName("deliveryTag")] + private ulong? _deliveryTag; + + /// + /// Initializes a new instance of the class. + /// + /// The stream id. + /// The events. + /// The request context. + /// The sequence token. + /// The delivery tag. + [JsonConstructor] + public RabbitMqBatchContainer( + StreamId streamId, + ICollection events, + Dictionary requestContext, + RabbitMqStreamSequenceToken? sequenceToken, + ulong? deliveryTag = default) + { + StreamId = streamId; + _events = events; + _requestContext = requestContext; + _sequenceToken = sequenceToken; + _deliveryTag = deliveryTag; + } + + /// + [JsonIgnore] + public StreamSequenceToken? SequenceToken => _sequenceToken; + + /// + [Id(3)] + [JsonPropertyName("streamId")] + public StreamId StreamId { get; } + + /// + /// Gets the delivery tag. + /// + /// The delivery tag. + [JsonIgnore] + public ulong DeliveryTag => _deliveryTag ?? 0; + + public static bool operator ==(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + return left?.Equals(right) ?? ReferenceEquals(left, right); + } + + public static bool operator !=(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + return !(left == right); + } + + public static bool operator >(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + return Compare(left, right) > 0; + } + + public static bool operator >=(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + return Compare(left, right) >= 0; + } + + public static bool operator <(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + return Compare(left, right) < 0; + } + + public static bool operator <=(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + return Compare(left, right) <= 0; + } + + /// + public IEnumerable> GetEvents() + { + var events = _events.OfType(); + var returns = events.Select((@event, i) => + Tuple.Create( + @event, + new RabbitMqStreamSequenceToken(Convert.ToInt64(_deliveryTag ?? 0), i))); + return returns; + } + + /// + public bool ImportRequestContext() + { + if (_requestContext is null) + { + return false; + } + + RequestContextExtensions.Import(_requestContext); + return true; + } + + /// + public int CompareTo(RabbitMqBatchContainer? other) + { + return Compare(this, other); + } + + /// + public override bool Equals(object? obj) + { + if (obj is not RabbitMqBatchContainer other) + { + return false; + } + + return CompareTo(other) == 0; + } + + /// + /// Get Hash Code. + /// + /// The hash code. + public override int GetHashCode() + { + return 397 * _events.GetHashCode() ^ (_requestContext?.GetHashCode() ?? 17) ^ DeliveryTag.GetHashCode(); + } + + /// + /// Update Delivery Tag. + /// + /// The delivery tag. + internal void UpdateDeliveryTag(ulong deliveryTag) + { + _deliveryTag = deliveryTag; + _sequenceToken = new RabbitMqStreamSequenceToken(deliveryTag); + } + + private static int Compare(RabbitMqBatchContainer? left, RabbitMqBatchContainer? right) + { + switch (left) + { + case null when right is null: + return 0; + case null: + return -1; + default: + { + if (right is null) + { + return 1; + } + + break; + } + } + + if (left.DeliveryTag.Equals(right.DeliveryTag)) + { + return 0; + } + + return left.DeliveryTag > right.DeliveryTag ? 1 : -1; + } +} diff --git a/src/Orleans/RabbitMQ/Core/RabbitMqStreamSequenceToken.cs b/src/Orleans/RabbitMQ/Core/RabbitMqStreamSequenceToken.cs new file mode 100644 index 0000000..6705cde --- /dev/null +++ b/src/Orleans/RabbitMQ/Core/RabbitMqStreamSequenceToken.cs @@ -0,0 +1,139 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Core; + +using System.Globalization; +using System.Text.Json.Serialization; +using global::Orleans.Streams; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Stream Sequence Token. +/// +[GenerateSerializer] +[Serializable] +[Alias("rabbitmq-stream-sequence-token")] +public class RabbitMqStreamSequenceToken : StreamSequenceToken +{ + [Id(0)] + [JsonPropertyName("sequenceNumber")] + private long _sequenceNumber; + + [Id(1)] + [JsonPropertyName("eventIndex")] + private int _eventIndex; + + /// + /// Initializes a new instance of the class. + /// + /// The sequence number. + /// The event index. + [JsonConstructor] + public RabbitMqStreamSequenceToken(long sequenceNumber, int eventIndex) + { + _sequenceNumber = sequenceNumber; + _eventIndex = eventIndex; + } + + /// + /// Initializes a new instance of the class. + /// + /// The sequence token. + public RabbitMqStreamSequenceToken(StreamSequenceToken sequenceToken) + { + ArgumentNullException.ThrowIfNull(sequenceToken); + _sequenceNumber = sequenceToken.SequenceNumber; + _eventIndex = sequenceToken.EventIndex; + } + + /// + /// Initializes a new instance of the class. + /// + /// The sequence number. + public RabbitMqStreamSequenceToken(ulong sequenceNumber) + { + _sequenceNumber = Convert.ToInt64(sequenceNumber); + _eventIndex = 0; + } + + /// + /// Gets or sets the sequence number. + /// + /// The sequence number. + [JsonIgnore] + public override long SequenceNumber + { + get => _sequenceNumber; + protected set => _sequenceNumber = value; + } + + /// + /// Gets or sets the event index. + /// + /// The event index. + [JsonIgnore] + public override int EventIndex + { + get => _eventIndex; + protected set => _eventIndex = value; + } + + /// + public override bool Equals(object? obj) + { + return Equals(obj as RabbitMqStreamSequenceToken); + } + + /// + public override bool Equals(StreamSequenceToken? other) + { + var token = other as RabbitMqStreamSequenceToken; + return token is not null + && token.SequenceNumber == SequenceNumber + && token.EventIndex == EventIndex; + } + + /// + public override int CompareTo(StreamSequenceToken? other) + { + if (other is null) + { + return 1; + } + + if (other is not RabbitMqStreamSequenceToken token) + { + throw new ArgumentOutOfRangeException(nameof(other)); + } + + var difference = SequenceNumber.CompareTo(token.SequenceNumber); + return difference != 0 ? difference : EventIndex.CompareTo(token.EventIndex); + } + + /// + public override int GetHashCode() + { + return (EventIndex * 397) ^ SequenceNumber.GetHashCode(); + } + + /// + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "[RabbitMQStreamSequenceToken: Num: {0}, Index: {1}]", + SequenceNumber.ToString(CultureInfo.InvariantCulture), + EventIndex.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Update Sequence Token. + /// + /// The sequence token. + /// The event index. + internal void Update(long sequenceNumber, int eventIndex) + { + _sequenceNumber = sequenceNumber; + _eventIndex = eventIndex; + } +} diff --git a/src/Orleans/RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.csproj b/src/Orleans/RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.csproj new file mode 100644 index 0000000..05a1fed --- /dev/null +++ b/src/Orleans/RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.csproj @@ -0,0 +1,12 @@ + + + + + + + + + README.md + + + diff --git a/src/Orleans/RabbitMQ/Hosting/ClientBuilderExtensions.cs b/src/Orleans/RabbitMQ/Hosting/ClientBuilderExtensions.cs new file mode 100644 index 0000000..9bb66e2 --- /dev/null +++ b/src/Orleans/RabbitMQ/Hosting/ClientBuilderExtensions.cs @@ -0,0 +1,93 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Hosting; + +using Builder; +using global::Orleans.Configuration; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Service Collection Extensions. +/// +public static class ClientBuilderExtensions +{ + /// + /// Add Rabbit MQ on Silo. + /// + /// The initial client builder. + /// The name. + /// The protocol builder. + public static IRabbitMqClientProtocolBuilder AddRabbitMq( + this IClientBuilder clientBuilder, + string name) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + ArgumentNullException.ThrowIfNull(name); + return new RabbitMqClientProtocolBuilder(name, clientBuilder.Services); + } + + /// + /// Add Hash Ring Stream Queue Mapper. + /// + /// The client builder. + /// The configure options. + /// The rabbitmq client options builder. + internal static IRabbitMqClientOptionsBuilder AddHashRingStreamQueueMapper( + this IRabbitMqClientOptionsBuilder clientBuilder, + Action configureOptions) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + clientBuilder + .ConfigureServices(services => services + .AddOrleansNamedSingletonFactory(clientBuilder.Name, ServiceProviderExtensions.CreateDefaultStreamQueueMapper) + .AddOptions(clientBuilder.Name) + .Configure(configureOptions)); + return clientBuilder; + } + + /// + /// Add Hash Ring Stream Queue Mapper. + /// + /// The client builder. + /// The rabbitmq client options builder. + internal static IRabbitMqClientOptionsBuilder AddHashRingStreamQueueMapper( + this IRabbitMqClientOptionsBuilder clientBuilder) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + return AddHashRingStreamQueueMapper(clientBuilder, _ => { }); + } + + /// + /// Add Simple Queue Cache. + /// + /// The client builder. + /// The configure options. + /// The rabbitmq client options builder. + internal static IRabbitMqClientOptionsBuilder AddSimpleQueueCache( + this IRabbitMqClientOptionsBuilder clientBuilder, + Action configureOptions) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + clientBuilder + .ConfigureServices(services => services + .AddOrleansNamedSingletonFactory(clientBuilder.Name, ServiceProviderExtensions.CreateDefaultQueueAdapterCache) + .AddOptions(clientBuilder.Name) + .Configure(configureOptions)); + return clientBuilder; + } + + /// + /// Add Simple Queue Cache. + /// + /// The client builder. + /// The rabbitmq client options builder. + internal static IRabbitMqClientOptionsBuilder AddSimpleQueueCache( + this IRabbitMqClientOptionsBuilder clientBuilder) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + return AddSimpleQueueCache(clientBuilder, _ => { }); + } +} diff --git a/src/Orleans/RabbitMQ/Hosting/OrleansExtensions.cs b/src/Orleans/RabbitMQ/Hosting/OrleansExtensions.cs new file mode 100644 index 0000000..b266461 --- /dev/null +++ b/src/Orleans/RabbitMQ/Hosting/OrleansExtensions.cs @@ -0,0 +1,58 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Hosting; + +using Microsoft.Extensions.DependencyInjection; + +#if !NET8_0_OR_GREATER +using global::Orleans.Runtime; +#endif + +/// +/// Orleans Extensions. +/// +internal static class OrleansExtensions +{ + /// + /// Register Keyed Singleton Factory. + /// + /// The initial service collection. + /// The name. + /// The implementation factory. + /// The service type. + /// The updated service collection. + public static IServiceCollection AddOrleansNamedSingletonFactory( + this IServiceCollection services, + string name, + Func implementationFactory) + where TService : class + { +#if NET8_0_OR_GREATER + return services.AddKeyedSingleton(name, implementationFactory); +#else + return services.AddSingletonKeyedService(name, implementationFactory); +#endif + } + + /// + /// Register Keyed Singleton Implementation. + /// + /// The initial service collection. + /// The name. + /// The service type. + /// The instance type. + /// The updated service collection. + public static IServiceCollection AddOrleansNamedSingleton( + this IServiceCollection services, + string name) + where TInstance : class, TService + where TService : class + { +#if NET8_0_OR_GREATER + return services.AddKeyedSingleton(name); +#else + return services.AddSingletonKeyedService(name); +#endif + } +} diff --git a/src/Orleans/RabbitMQ/Hosting/ServiceProviderExtensions.cs b/src/Orleans/RabbitMQ/Hosting/ServiceProviderExtensions.cs new file mode 100644 index 0000000..d650e41 --- /dev/null +++ b/src/Orleans/RabbitMQ/Hosting/ServiceProviderExtensions.cs @@ -0,0 +1,87 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace System; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Orleans.Configuration; +using Orleans.Providers.Streams.Common; +using Orleans.Streams; +#if !NET8_0_OR_GREATER +using Orleans.Runtime; +#endif + +/// +/// Service Provider Extensions. +/// +public static class ServiceProviderExtensions +{ + /// + /// Get Required Orleans Service By Name. + /// + /// The service provider. + /// The name. + /// The service type. + /// The service. + public static TService GetRequiredOrleansServiceByName(this IServiceProvider serviceProvider, string name) +#if NET8_0_OR_GREATER + where TService : notnull +#else + where TService : class +#endif + { + ArgumentNullException.ThrowIfNull(name); +#if NET8_0_OR_GREATER + return serviceProvider.GetRequiredKeyedService(name); +#else + return serviceProvider.GetRequiredServiceByKey(name); +#endif + } + + /// + /// Create Stream Queue Mapper. + /// + /// The service provider. + /// The name. + /// The stream queue mapper. + /// The argument exception. + internal static IStreamQueueMapper CreateDefaultStreamQueueMapper( + this IServiceProvider serviceProvider, + object? name) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(name); + + if (name is not string factoryName) + { + throw new ArgumentException("Invalid name"); + } + + var options = serviceProvider.GetOptionsByName(factoryName); + return new HashRingBasedStreamQueueMapper(options, factoryName); + } + + /// + /// Create Queue Adapter Cache. + /// + /// The service provider. + /// The name. + /// The queue adapter cache. + /// The argument exception when name is not string. + internal static IQueueAdapterCache CreateDefaultQueueAdapterCache( + this IServiceProvider serviceProvider, + object? name) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(name); + if (name is not string factoryName) + { + throw new ArgumentException("Invalid name"); + } + + var loggerFactory = serviceProvider.GetRequiredService(); + var options = serviceProvider.GetOptionsByName(factoryName); + return new SimpleQueueAdapterCache(options, factoryName, loggerFactory); + } +} diff --git a/src/Orleans/RabbitMQ/Hosting/SiloBuilderExtensions.cs b/src/Orleans/RabbitMQ/Hosting/SiloBuilderExtensions.cs new file mode 100644 index 0000000..2dc3147 --- /dev/null +++ b/src/Orleans/RabbitMQ/Hosting/SiloBuilderExtensions.cs @@ -0,0 +1,94 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Hosting; + +using Builder; +using global::Orleans.Configuration; +using global::Orleans.Streams; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Service Collection Extensions. +/// +public static class SiloBuilderExtensions +{ + /// + /// Add Rabbit MQ on Silo. + /// + /// The initial silo builder. + /// The name. + /// The protocol builder. + public static IRabbitMqSiloProtocolBuilder AddRabbitMq( + this ISiloBuilder siloBuilder, + string name) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + ArgumentNullException.ThrowIfNull(name); + return new RabbitMqSiloProtocolBuilder(name, siloBuilder.Services); + } + + /// + /// Add Hash Ring Stream Queue Mapper. + /// + /// The silo builder. + /// The configure options. + /// The rabbitmq silo options builder. + internal static IRabbitMqSiloOptionsBuilder AddHashRingStreamQueueMapper( + this IRabbitMqSiloOptionsBuilder siloBuilder, + Action configureOptions) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + siloBuilder + .ConfigureServices(services => services + .AddOrleansNamedSingletonFactory(siloBuilder.Name, ServiceProviderExtensions.CreateDefaultStreamQueueMapper) + .AddOptions(siloBuilder.Name) + .Configure(configureOptions)); + return siloBuilder; + } + + /// + /// Add Hash Ring Stream Queue Mapper. + /// + /// The silo builder. + /// The rabbitmq silo options builder. + internal static IRabbitMqSiloOptionsBuilder AddHashRingStreamQueueMapper( + this IRabbitMqSiloOptionsBuilder siloBuilder) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + return AddHashRingStreamQueueMapper(siloBuilder, _ => { }); + } + + /// + /// Add Simple Queue Cache. + /// + /// The silo builder. + /// The configure options. + /// The rabbitmq silo options builder. + internal static IRabbitMqSiloOptionsBuilder AddSimpleQueueCache( + this IRabbitMqSiloOptionsBuilder siloBuilder, + Action configureOptions) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + siloBuilder + .ConfigureServices(services => services + .AddOrleansNamedSingletonFactory(siloBuilder.Name, ServiceProviderExtensions.CreateDefaultQueueAdapterCache) + .AddOptions(siloBuilder.Name) + .Configure(configureOptions)); + return siloBuilder; + } + + /// + /// Add Simple Queue Cache. + /// + /// The silo builder. + /// The rabbitmq silo options builder. + internal static IRabbitMqSiloOptionsBuilder AddSimpleQueueCache( + this IRabbitMqSiloOptionsBuilder siloBuilder) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + return AddSimpleQueueCache(siloBuilder, _ => { }); + } +} diff --git a/src/Orleans/RabbitMQ/Properties/AssemblyInfo.cs b/src/Orleans/RabbitMQ/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..22a15c4 --- /dev/null +++ b/src/Orleans/RabbitMQ/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +using System.Runtime.CompilerServices; + +[assembly: CLSCompliant(false)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] +[assembly: InternalsVisibleTo("Escendit.Orleans.Streaming.RabbitMQ.Tests")] +[assembly: InternalsVisibleTo("Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol")] +[assembly: InternalsVisibleTo("Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol")] +[assembly: InternalsVisibleTo("Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests")] diff --git a/src/Orleans/RabbitMQ/Provider/AdapterFactoryBase.cs b/src/Orleans/RabbitMQ/Provider/AdapterFactoryBase.cs new file mode 100644 index 0000000..01f1335 --- /dev/null +++ b/src/Orleans/RabbitMQ/Provider/AdapterFactoryBase.cs @@ -0,0 +1,79 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Provider; + +using global::Orleans.Streams; +using Microsoft.Extensions.Logging; + +/// +public abstract partial class AdapterFactoryBase : IQueueAdapterFactory +{ + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + protected AdapterFactoryBase(ILogger logger) + { + _logger = logger; + } + + /// + public abstract Task CreateAdapter(); + + /// + public abstract IQueueAdapterCache GetQueueAdapterCache(); + + /// + public abstract IStreamQueueMapper GetStreamQueueMapper(); + + /// + public abstract Task GetDeliveryFailureHandler(QueueId queueId); + + /// + /// Log Create Adapter. + /// + /// The name. + [LoggerMessage( + EventId = 100, + EventName = nameof(CreateAdapter), + Level = LogLevel.Debug, + Message = "Creating Queue Adapter for ProviderName: {name}")] + protected partial void LogCreateAdapter(string name); + + /// + /// Log Get Queue Adapter Cache. + /// + /// The name. + [LoggerMessage( + EventId = 101, + EventName = nameof(GetQueueAdapterCache), + Level = LogLevel.Debug, + Message = "Setting Queue Adapter Cache for ProviderName: {name}")] + protected partial void LogGetQueueAdapterCache(string name); + + /// + /// Log Get Stream Queue Mapper. + /// + /// The name. + [LoggerMessage( + EventId = 102, + EventName = nameof(GetStreamQueueMapper), + Level = LogLevel.Debug, + Message = "Getting Stream Queue Mapper for ProviderName: {name}")] + protected partial void LogGetStreamQueueMapper(string name); + + /// + /// Log Get Delivery Failure Handler. + /// + /// + /// + [LoggerMessage( + EventId = 500, + EventName = nameof(GetDeliveryFailureHandler), + Level = LogLevel.Debug, + Message = "Getting Delivery Failure Handler for ProviderName: {name}, QueueId: {queueId}")] + protected partial void LogGetDeliveryFailureHandler(string name, QueueId queueId); +} diff --git a/src/Orleans/RabbitMQ/Provider/AdapterReceiverBase.cs b/src/Orleans/RabbitMQ/Provider/AdapterReceiverBase.cs new file mode 100644 index 0000000..fe901be --- /dev/null +++ b/src/Orleans/RabbitMQ/Provider/AdapterReceiverBase.cs @@ -0,0 +1,97 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.Provider; + +using global::Orleans.Streams; +using Microsoft.Extensions.Logging; + +/// +public abstract partial class AdapterReceiverBase : IQueueAdapterReceiver +{ + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + protected AdapterReceiverBase(ILogger logger) + { + _logger = logger; + } + + /// + public abstract Task Initialize(TimeSpan timeout); + + /// + public abstract Task> GetQueueMessagesAsync(int maxCount); + + /// + public abstract Task MessagesDeliveredAsync(IList messages); + + /// + public abstract Task Shutdown(TimeSpan timeout); + + /// + /// Log Initialize. + /// + /// The name. + /// The queue id. + [LoggerMessage( + EventId = 100, + EventName = "Log Initialize", + Level = LogLevel.Debug, + Message = "Initializing Receiver for ProviderName: {name}, QueueId: {queueId}")] + protected partial void LogInitialize(string name, QueueId queueId); + + /// + /// Log Message Handler Incoming Message. + /// + /// The name. + /// The queue id. + /// The size. + [LoggerMessage( + EventId = 101, + EventName = "Log Message Handler Incoming Message", + Level = LogLevel.Debug, + Message = "Incoming Message for ProviderName: {name}, QueueId: {queueId}, Size: {size}")] + protected partial void LogMessageHandlerIncomingMessage(string name, QueueId queueId, int size); + + /// + /// Log Get Queue Messages. + /// + /// The name. + /// The queue id. + /// The max count. + [LoggerMessage( + EventId = 102, + EventName = "Log Get Queue Messages", + Level = LogLevel.Debug, + Message = "Getting Queue Messages for ProviderName: {name}, QueueId {queueId}, MaxCount: {maxCount}")] + protected partial void LogGetQueueMessages(string name, QueueId queueId, int maxCount); + + /// + /// Log Messages Delivered. + /// + /// The name. + /// The queue id. + /// The count. + [LoggerMessage( + EventId = 103, + EventName = "Log Messages Delivered", + Level = LogLevel.Debug, + Message = "Delivered Messages for ProviderName: {name}, QueueId: {queueId}, Count: {count}")] + protected partial void LogMessagesDelivered(string name, QueueId queueId, int count); + + /// + /// Log Shutdown. + /// + /// The name. + /// The queue id. + [LoggerMessage( + EventId = 104, + EventName = "Log Shutdown", + Level = LogLevel.Debug, + Message = "Shutting down for ProviderName: {name}, QueueId: {queueId}")] + protected partial void LogShutdown(string name, QueueId queueId); +} diff --git a/src/Orleans/StreamProtocol/Builder/RabbitMqClientOptionsBuilder.cs b/src/Orleans/StreamProtocol/Builder/RabbitMqClientOptionsBuilder.cs new file mode 100644 index 0000000..fda9fa5 --- /dev/null +++ b/src/Orleans/StreamProtocol/Builder/RabbitMqClientOptionsBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Builder; + +using Escendit.Orleans.Streaming.RabbitMQ.Builder; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Client Options Builder. +/// +internal sealed class RabbitMqClientOptionsBuilder : IRabbitMqClientOptionsBuilder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The services. + /// The configurator. + public RabbitMqClientOptionsBuilder( + string name, + IServiceCollection services, + ClusterClientPersistentStreamConfigurator configurator) + { + Name = name; + Services = services; + Configurator = configurator; + } + + /// + public string Name { get; } + + /// + public IServiceCollection Services { get; } + + /// + public ClusterClientPersistentStreamConfigurator Configurator { get; } + + /// + public IServiceCollection Build() + { + return Services; + } +} diff --git a/src/Orleans/StreamProtocol/Builder/RabbitMqSiloOptionsBuilder.cs b/src/Orleans/StreamProtocol/Builder/RabbitMqSiloOptionsBuilder.cs new file mode 100644 index 0000000..69be023 --- /dev/null +++ b/src/Orleans/StreamProtocol/Builder/RabbitMqSiloOptionsBuilder.cs @@ -0,0 +1,41 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Builder; + +using Escendit.Orleans.Streaming.RabbitMQ.Builder; +using Microsoft.Extensions.DependencyInjection; + +/// +/// Escendit.Orleans.Streaming.RabbitMQ.Tests Silo Options Builder. +/// +internal sealed class RabbitMqSiloOptionsBuilder : IRabbitMqSiloOptionsBuilder +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The services. + /// The configurator. + public RabbitMqSiloOptionsBuilder(string name, IServiceCollection services, SiloPersistentStreamConfigurator configurator) + { + Name = name; + Services = services; + Configurator = configurator; + } + + /// + public string Name { get; } + + /// + public IServiceCollection Services { get; } + + /// + public SiloPersistentStreamConfigurator Configurator { get; } + + /// + public IServiceCollection Build() + { + return Services; + } +} diff --git a/src/Orleans/StreamProtocol/Configuration/ClusterClientConfigurator.cs b/src/Orleans/StreamProtocol/Configuration/ClusterClientConfigurator.cs new file mode 100644 index 0000000..781bf3c --- /dev/null +++ b/src/Orleans/StreamProtocol/Configuration/ClusterClientConfigurator.cs @@ -0,0 +1,37 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Configuration; + +using Hosting; +using Microsoft.Extensions.DependencyInjection; +using Provider; + +/// +/// Cluster Client Rabbit MQ AMQP Configurator. +/// +internal class ClusterClientConfigurator : ClusterClientPersistentStreamConfigurator +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The client builder. + public ClusterClientConfigurator( + string name, + IClientBuilder clientBuilder) + : base(name, clientBuilder, StreamProtocolAdapterFactory.Create) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(clientBuilder); + clientBuilder + .ConfigureServices(configure => + { + configure + .AddClientStreaming(); + configure + .AddOrleansNamedSingletonFactory(name, StreamProtocolAdapterFactory.Create) + .AddOptions(name); + }); + } +} diff --git a/src/Orleans/StreamProtocol/Configuration/SiloConfigurator.cs b/src/Orleans/StreamProtocol/Configuration/SiloConfigurator.cs new file mode 100644 index 0000000..1f91c4b --- /dev/null +++ b/src/Orleans/StreamProtocol/Configuration/SiloConfigurator.cs @@ -0,0 +1,37 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Configuration; + +using Hosting; +using Microsoft.Extensions.DependencyInjection; +using Provider; + +/// +/// Silo Configurator. +/// +internal class SiloConfigurator : SiloPersistentStreamConfigurator +{ + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The configure delegate. + public SiloConfigurator( + string name, + Action> configureDelegate) + : base(name, configureDelegate, StreamProtocolAdapterFactory.Create) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(configureDelegate); + + ConfigureDelegate(services => + { + services + .AddSiloStreaming(); + services + .AddOrleansNamedSingletonFactory(name, StreamProtocolAdapterFactory.Create) + .AddOptions(name); + }); + } +} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitStreamOptions.cs b/src/Orleans/StreamProtocol/Configuration/StreamOptions.cs similarity index 52% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitStreamOptions.cs rename to src/Orleans/StreamProtocol/Configuration/StreamOptions.cs index 858e6d5..fab2892 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Options/RabbitStreamOptions.cs +++ b/src/Orleans/StreamProtocol/Configuration/StreamOptions.cs @@ -1,11 +1,13 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Options; +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Configuration; + +using RabbitMQ.Configuration; /// -/// Rabbit Options for Streaming Protocol. +/// Stream Options. /// -public record RabbitStreamOptions : RabbitOptionsBase +public class StreamOptions : OptionsBase { } diff --git a/src/Orleans/StreamProtocol/Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.csproj b/src/Orleans/StreamProtocol/Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.csproj new file mode 100644 index 0000000..7b83d8f --- /dev/null +++ b/src/Orleans/StreamProtocol/Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.csproj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Orleans/StreamProtocol/Hosting/ClientBuilderExtensions.cs b/src/Orleans/StreamProtocol/Hosting/ClientBuilderExtensions.cs new file mode 100644 index 0000000..bab7353 --- /dev/null +++ b/src/Orleans/StreamProtocol/Hosting/ClientBuilderExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Orleans.Hosting; + +using Escendit.Orleans.Streaming.RabbitMQ.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.Hosting; +using Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Configuration; +using Streams; +using ConnectionOptions = Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions.ConnectionOptions; + +/// +/// Client Builder Extensions. +/// +public static class ClientBuilderExtensions +{ + /// + /// Use AMQP Protocol. + /// + /// The client builder. + /// The configure options. + /// The client options builder. + public static IRabbitMqClientOptionsBuilder UseStreamProtocol(this IRabbitMqClientProtocolBuilder clientBuilder, Action configureOptions) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + var configurator = new ClusterClientConfigurator(clientBuilder.Name, clientBuilder); + configurator + .Configure(options => options.Configure(configureOptions)); + return new RabbitMqClientOptionsBuilder(clientBuilder.Name, clientBuilder.Services, configurator) + .AddSimpleQueueCache() + .AddHashRingStreamQueueMapper(); + } + + /// + /// Configure Stream Pub Sub. + /// + /// The initial client options builder. + /// The stream pub sub type. + /// The rabbitmq client options builder. + public static IRabbitMqClientOptionsBuilder ConfigureStreamPubSub(this IRabbitMqClientOptionsBuilder clientBuilder, StreamPubSubType streamPubSubType = StreamPubSubType.ExplicitGrainBasedAndImplicit) + { + ArgumentNullException.ThrowIfNull(clientBuilder); + clientBuilder.Configurator.ConfigureStreamPubSub(streamPubSubType); + return clientBuilder; + } +} diff --git a/src/Orleans/StreamProtocol/Hosting/SiloBuilderExtensions.cs b/src/Orleans/StreamProtocol/Hosting/SiloBuilderExtensions.cs new file mode 100644 index 0000000..b365fac --- /dev/null +++ b/src/Orleans/StreamProtocol/Hosting/SiloBuilderExtensions.cs @@ -0,0 +1,54 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Orleans.Hosting; + +using Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions; +using Escendit.Orleans.Streaming.RabbitMQ.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.Hosting; +using Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Builder; +using Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Configuration; +using Streams; + +/// +/// Silo Builder Extensions. +/// +public static class SiloBuilderExtensions +{ + /// + /// Use AMQP Protocol. + /// + /// The silo builder. + /// The configure options. + /// The client options builder. + public static IRabbitMqSiloOptionsBuilder UseStreamProtocol(this IRabbitMqSiloProtocolBuilder siloBuilder, Action configureOptions) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + ArgumentNullException.ThrowIfNull(configureOptions); + var configurator = new SiloConfigurator( + siloBuilder.Name, + configureDelegate => siloBuilder + .ConfigureServices(configureDelegate)); + configurator + .Configure(options => options.Configure(configureOptions)); + return new RabbitMqSiloOptionsBuilder( + siloBuilder.Name, + siloBuilder.Services, + configurator) + .AddSimpleQueueCache() + .AddHashRingStreamQueueMapper(); + } + + /// + /// Configure Stream Pub Sub. + /// + /// The initial silo options builder. + /// The stream pub sub type. + /// The rabbitmq client options builder. + public static IRabbitMqSiloOptionsBuilder ConfigureStreamPubSub(this IRabbitMqSiloOptionsBuilder siloBuilder, StreamPubSubType streamPubSubType = StreamPubSubType.ExplicitGrainBasedAndImplicit) + { + ArgumentNullException.ThrowIfNull(siloBuilder); + siloBuilder.Configurator.ConfigureStreamPubSub(streamPubSubType); + return siloBuilder; + } +} diff --git a/src/Orleans/StreamProtocol/Properties/AssemblyInfo.cs b/src/Orleans/StreamProtocol/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..14aeb7e --- /dev/null +++ b/src/Orleans/StreamProtocol/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +[assembly: CLSCompliant(false)] diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapter.cs b/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapter.cs similarity index 66% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapter.cs rename to src/Orleans/StreamProtocol/Provider/StreamProtocolAdapter.cs index 76f68f8..2ac3d70 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapter.cs +++ b/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapter.cs @@ -1,7 +1,7 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Stream; +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Provider; using Core; using global::Orleans.Configuration; @@ -13,54 +13,57 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Stream; using Microsoft.Extensions.Logging; /// -/// Default Stream Adapter. +/// Stream Protocol Adapter. /// -internal partial class DefaultStreamAdapter : IQueueAdapter +internal sealed partial class StreamProtocolAdapter : IQueueAdapter { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly ClusterOptions _clusterOptions; - private readonly Serializer _serializer; - private readonly IConsistentRingStreamQueueMapper _consistentRingStreamQueueMapper; + private readonly Serializer _serializer; + private readonly IStreamQueueMapper _streamQueueMapper; private readonly StreamSystem _streamSystem; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name. /// The logger factory. /// The cluster options. /// The serializer. - /// The consistent ring stream queue mapper. + /// The consistent ring stream queue mapper. /// The stream system. - public DefaultStreamAdapter( + public StreamProtocolAdapter( string name, ILoggerFactory loggerFactory, ClusterOptions clusterOptions, - Serializer serializer, - IConsistentRingStreamQueueMapper consistentRingStreamQueueMapper, + Serializer serializer, + IStreamQueueMapper streamQueueMapper, StreamSystem streamSystem) { + Name = name; + IsRewindable = true; + Direction = StreamProviderDirection.ReadWrite; + _loggerFactory = loggerFactory; - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); _clusterOptions = clusterOptions; _serializer = serializer; - _consistentRingStreamQueueMapper = consistentRingStreamQueueMapper; - Name = name; - IsRewindable = true; + _streamQueueMapper = streamQueueMapper; + _streamSystem = streamSystem; } - /// + /// public string Name { get; } - /// + /// public bool IsRewindable { get; } - /// - public StreamProviderDirection Direction => StreamProviderDirection.ReadWrite; + /// + public StreamProviderDirection Direction { get; } - /// + /// public async Task QueueMessageBatchAsync( StreamId streamId, IEnumerable events, @@ -74,12 +77,15 @@ public async Task QueueMessageBatchAsync( throw new InvalidOperationException("stream sequence token is not supported."); } - var queueId = _consistentRingStreamQueueMapper.GetQueueForStream(streamId); + var queueId = _streamQueueMapper.GetQueueForStream(streamId); var streamName = NamingUtility.CreateNameForStream(_clusterOptions, queueId); - await _streamSystem - .CreateStream( - new StreamSpec(streamName)); + if (!await _streamSystem.StreamExists(streamName)) + { + await _streamSystem + .CreateStream( + new StreamSpec(streamName)); + } var producer = await _streamSystem .CreateRawProducer( @@ -87,13 +93,12 @@ await _streamSystem _loggerFactory.CreateLogger()); var lastPublishingId = await producer.GetLastPublishingId(); - var lastPublishingIdLong = Convert.ToInt64(lastPublishingId); - var container = new RabbitBatchContainer( + var container = new RabbitMqBatchContainer( streamId, events.Cast().ToList(), requestContext, - new RabbitStreamSequenceToken(lastPublishingIdLong)); + new RabbitMqStreamSequenceToken(lastPublishingId)); var data = _serializer .SerializeToArray(container); @@ -105,7 +110,7 @@ await producer /// public IQueueAdapterReceiver CreateReceiver(QueueId queueId) { - return new DefaultStreamAdapterReceiver(Name, _clusterOptions, queueId, _loggerFactory, _serializer, _streamSystem); + return new StreamProtocolAdapterReceiver(Name, _clusterOptions, queueId, _loggerFactory, _serializer, _streamSystem); } [LoggerMessage( diff --git a/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapterFactory.cs b/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapterFactory.cs new file mode 100644 index 0000000..03476d3 --- /dev/null +++ b/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapterFactory.cs @@ -0,0 +1,168 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Provider; + +using System.Net; +using Configuration; +using Core; +using global::Orleans.Configuration; +using global::Orleans.Configuration.Overrides; +using global::Orleans.Serialization; +using global::Orleans.Streams; +using global::RabbitMQ.Stream.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using RabbitMQ.Provider; +using ConnectionOptions = Extensions.DependencyInjection.RabbitMQ.Abstractions.ConnectionOptions; + +/// +/// Stream Protocol Adapter Factory. +/// +public sealed class StreamProtocolAdapterFactory : AdapterFactoryBase +{ + private readonly ILoggerFactory _loggerFactory; + private readonly string _name; + private readonly ConnectionOptions _connectionOptions; + private readonly ClusterOptions _clusterOptions; + private readonly Serializer _serializer; + private readonly IStreamQueueMapper _streamQueueMapper; + private readonly IQueueAdapterCache _queueAdapterCache; + private readonly Func> _streamFailureHandlerFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// stream queue mapper. + /// The queue adapter cache. + /// The connection options. + /// The stream options. + /// The cluster options. + /// The serializer. + /// The logger factory. + public StreamProtocolAdapterFactory( + string name, + IStreamQueueMapper streamQueueMapper, + IQueueAdapterCache queueAdapterCache, + ConnectionOptions connectionOptions, + StreamOptions streamOptions, + ClusterOptions clusterOptions, + Serializer serializer, + ILoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(streamQueueMapper); + ArgumentNullException.ThrowIfNull(queueAdapterCache); + ArgumentNullException.ThrowIfNull(connectionOptions); + ArgumentNullException.ThrowIfNull(streamOptions); + ArgumentNullException.ThrowIfNull(clusterOptions); + ArgumentNullException.ThrowIfNull(serializer); + ArgumentNullException.ThrowIfNull(loggerFactory); + _loggerFactory = loggerFactory; + _name = name; + _streamQueueMapper = streamQueueMapper; + _queueAdapterCache = queueAdapterCache; + _connectionOptions = connectionOptions; + _clusterOptions = clusterOptions; + _serializer = serializer.GetSerializer(); + _streamFailureHandlerFactory = streamOptions.StreamFailureHandler; + } + + /// + public override async Task CreateAdapter() + { + LogCreateAdapter(_name); + var streamSystem = await CreateStreamSystem(_connectionOptions, _loggerFactory.CreateLogger()); + return new StreamProtocolAdapter(_name, _loggerFactory, _clusterOptions, _serializer, _streamQueueMapper, streamSystem); + } + + /// + public override IQueueAdapterCache GetQueueAdapterCache() + { + LogGetQueueAdapterCache(_name); + return _queueAdapterCache; + } + + /// + public override IStreamQueueMapper GetStreamQueueMapper() + { + LogGetStreamQueueMapper(_name); + return _streamQueueMapper; + } + + /// + public override Task GetDeliveryFailureHandler(QueueId queueId) + { + LogGetDeliveryFailureHandler(_name, queueId); + return _streamFailureHandlerFactory(queueId); + } + + /// + /// Create Queue Adapter Factory. + /// + /// The service provider. + /// The name. + /// The queue adapter factory. + internal static StreamProtocolAdapterFactory Create(IServiceProvider serviceProvider, object? name) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(name); + + if (name is not string factoryName) + { + throw new ArgumentException("invalid name"); + } + + var clusterOptions = serviceProvider.GetProviderClusterOptions(factoryName); + var streamQueueMapper = serviceProvider.GetRequiredOrleansServiceByName(factoryName); + var queueAdapterCache = serviceProvider.GetRequiredOrleansServiceByName(factoryName); + var connectionOptions = serviceProvider.GetOptionsByName(factoryName); + var streamOptions = serviceProvider.GetOptionsByName(factoryName); + return ActivatorUtilities.CreateInstance( + serviceProvider, + name, + streamQueueMapper, + queueAdapterCache, + connectionOptions, + streamOptions, + clusterOptions.Value); + } + + private static Task CreateStreamSystem(ConnectionOptions connectionOptions, ILogger logger) + { + return StreamSystem.Create(BuildStreamSystemConfig(connectionOptions), logger); + } + + private static StreamSystemConfig BuildStreamSystemConfig(ConnectionOptions options) + { + return new StreamSystemConfig + { + Endpoints = options + .Endpoints + .Select(s => new DnsEndPoint(s.HostName, s.Port ?? 5552) as EndPoint) + .ToList(), + Heartbeat = options.Heartbeat, + Password = options.Password, + UserName = options.UserName, + VirtualHost = options.VirtualHost, + ClientProvidedName = options.ClientProvidedName, + Ssl = options.SslOptions is null + ? null + : new SslOption + { + AcceptablePolicyErrors = options.SslOptions.AcceptablePolicyErrors, + ServerName = options.SslOptions.ServerName, + CertificateSelectionCallback = options.SslOptions.CertificateSelectionCallback, + CertificateValidationCallback = options.SslOptions.CertificateValidationCallback, + CertPassphrase = options.SslOptions.CertPassphrase, + CertPath = options.SslOptions.CertPath, + Certs = options.SslOptions.Certificates, + CheckCertificateRevocation = options.SslOptions.CheckCertificateRevocation, + Enabled = options.SslOptions.Enabled, + Version = options.SslOptions.Version, + }, + }; + } +} diff --git a/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapterReceiver.cs b/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapterReceiver.cs similarity index 50% rename from src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapterReceiver.cs rename to src/Orleans/StreamProtocol/Provider/StreamProtocolAdapterReceiver.cs index dd903de..683b2f0 100644 --- a/src/Escendit.Orleans.Streaming.RabbitMQ/Stream/DefaultStreamAdapterReceiver.cs +++ b/src/Orleans/StreamProtocol/Provider/StreamProtocolAdapterReceiver.cs @@ -1,7 +1,7 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Stream; +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Provider; using System.Threading.Channels; using Core; @@ -10,23 +10,23 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Stream; using global::Orleans.Streams; using global::RabbitMQ.Stream.Client; using Microsoft.Extensions.Logging; +using RabbitMQ.Provider; /// -/// Default Stream Adapter Receiver. +/// Stream Protocol Adapter Receiver. /// -internal partial class DefaultStreamAdapterReceiver : IQueueAdapterReceiver +internal sealed class StreamProtocolAdapterReceiver : AdapterReceiverBase { - private readonly ILogger _logger; private readonly string _name; private readonly ClusterOptions _clusterOptions; private readonly QueueId _queueId; - private readonly Serializer _serializer; + private readonly Serializer _serializer; private readonly StreamSystem _streamSystem; - private readonly Channel _inboundChannel; + private readonly Channel _inboundChannel; private IConsumer? _consumer; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name. /// The cluster options. @@ -34,43 +34,50 @@ internal partial class DefaultStreamAdapterReceiver : IQueueAdapterReceiver /// The logger factory. /// The serializer. /// The stream system. - public DefaultStreamAdapterReceiver( + public StreamProtocolAdapterReceiver( string name, ClusterOptions clusterOptions, QueueId queueId, ILoggerFactory loggerFactory, - Serializer serializer, + Serializer serializer, StreamSystem streamSystem) + : base(loggerFactory.CreateLogger($"Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.{queueId}")) { _name = name; _clusterOptions = clusterOptions; _queueId = queueId; - _logger = loggerFactory.CreateLogger($"Escendit.Orleans.Streaming.RabbitMQ.Stream.{_queueId}"); _serializer = serializer; _streamSystem = streamSystem; - _inboundChannel = Channel.CreateBounded(16); + _inboundChannel = Channel.CreateBounded(16); } - /// - public async Task Initialize(TimeSpan timeout) + /// + public override async Task Initialize(TimeSpan timeout) { LogInitialize(_name, _queueId); var streamName = NamingUtility.CreateNameForStream(_clusterOptions, _queueId); + + if (!await _streamSystem.StreamExists(streamName)) + { + await _streamSystem.CreateStream(new StreamSpec(streamName)); + } + _consumer = await _streamSystem .CreateRawConsumer(new RawConsumerConfig(streamName) { - MessageHandler = async (_, _, message) => + MessageHandler = async (_, context, message) => { LogMessageHandlerIncomingMessage(_name, _queueId, message.Data.Size); await _inboundChannel.Writer.WaitToWriteAsync(); // Wait for queue var container = _serializer.Deserialize(message.Data.Contents); + container.UpdateDeliveryTag(context.Offset); await _inboundChannel.Writer.WriteAsync(container); }, }); } - /// - public Task> GetQueueMessagesAsync(int maxCount) + /// + public override Task> GetQueueMessagesAsync(int maxCount) { LogGetQueueMessages(_name, _queueId, maxCount); var countdown = maxCount; @@ -93,22 +100,19 @@ public Task> GetQueueMessagesAsync(int maxCount) return Task.FromResult>(batchContainers); } - /// - public async Task MessagesDeliveredAsync(IList messages) + /// + public override Task MessagesDeliveredAsync(IList messages) { ArgumentNullException.ThrowIfNull(messages); LogMessagesDelivered(_name, _queueId, messages.Count); - var maxNumber = Convert.ToUInt64(messages.Max(p => p.SequenceToken.SequenceNumber)); + var maxNumber = messages.Cast().Max(p => p.DeliveryTag); - if (maxNumber > 0) - { - await _consumer!.StoreOffset(maxNumber); - } + return maxNumber > 0 ? _consumer!.StoreOffset(maxNumber) : Task.CompletedTask; } - /// - public async Task Shutdown(TimeSpan timeout) + /// + public override async Task Shutdown(TimeSpan timeout) { LogShutdown(_name, _queueId); @@ -117,39 +121,4 @@ public async Task Shutdown(TimeSpan timeout) await _consumer.Close(); } } - - [LoggerMessage( - EventId = 100, - EventName = "Log Initialize", - Level = LogLevel.Debug, - Message = "Initializing Receiver for ProviderName: {name}, QueueId: {queueId}")] - private partial void LogInitialize(string name, QueueId queueId); - - [LoggerMessage( - EventId = 101, - EventName = "Log Message Handler Incoming Message", - Level = LogLevel.Debug, - Message = "Incoming Message for ProviderName: {name}, QueueId: {queueId}, Size: {size}")] - private partial void LogMessageHandlerIncomingMessage(string name, QueueId queueId, int size); - - [LoggerMessage( - EventId = 102, - EventName = "Log Get Queue Messages", - Level = LogLevel.Debug, - Message = "Getting Queue Messages for ProviderName: {name}, QueueId {queueId}, MaxCount: {maxCount}")] - private partial void LogGetQueueMessages(string name, QueueId queueId, int maxCount); - - [LoggerMessage( - EventId = 103, - EventName = "Log Messages Delivered", - Level = LogLevel.Debug, - Message = "Delivered Messages for ProviderName: {name}, QueueId: {queueId}, Count: {count}")] - private partial void LogMessagesDelivered(string name, QueueId queueId, int count); - - [LoggerMessage( - EventId = 104, - EventName = "Log Shutdown", - Level = LogLevel.Debug, - Message = "Shutting down for ProviderName: {name}, QueueId: {queueId}")] - private partial void LogShutdown(string name, QueueId queueId); } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 92f64e3..869700d 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -4,10 +4,33 @@ $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\')) - + false true + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + diff --git a/test/Directory.Packages.props b/test/Directory.Packages.props deleted file mode 100644 index a26337d..0000000 --- a/test/Directory.Packages.props +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/RabbitMqClientConfigurator.cs b/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/RabbitMqClientConfigurator.cs deleted file mode 100644 index c417543..0000000 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/RabbitMqClientConfigurator.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Configuration; - -using global::Orleans.TestingHost; -using Microsoft.Extensions.Configuration; -using Options; - -/// -/// Test Client Configurator. -/// -public class RabbitMqClientConfigurator : IClientBuilderConfigurator -{ - /// - public void Configure(IConfiguration configuration, IClientBuilder clientBuilder) - { - clientBuilder - .AddStreaming() - /*.AddRabbitMqStreaming("Stream") - .WithStream(options => - { - options.Endpoints.Add(new RabbitEndpoint { HostName = "localhost", Port = 5552 }); - options.UserName = "test"; - options.Password = "test"; - options.VirtualHost = "testing"; - options.ClientProvidedName = "Client-Stream"; - })*/ - .AddRabbitMqStreaming("Queue") - .WithQueue(options => - { - options.Endpoints.Add(new RabbitEndpoint { HostName = "localhost", Port = 5672 }); - options.UserName = "test"; - options.Password = "test"; - options.VirtualHost = "testing"; - options.ClientProvidedName = "Client-Queue"; - }); - } -} diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/RabbitMqSiloConfigurator.cs b/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/RabbitMqSiloConfigurator.cs deleted file mode 100644 index b62ad0c..0000000 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/RabbitMqSiloConfigurator.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Configuration; - -using global::Orleans.TestingHost; -using Options; - -/// -/// Rabbit MQ Silo Configurator. -/// -public class RabbitMqSiloConfigurator : ISiloConfigurator -{ - /// - public void Configure(ISiloBuilder siloBuilder) - { - siloBuilder - .AddMemoryGrainStorageAsDefault() - .AddMemoryGrainStorage("PubSubStore") - .AddStreaming() - /*.AddRabbitMqStreaming("Stream") - .WithStream(options => - { - options.Endpoints.Add(new RabbitEndpoint { HostName = "localhost", Port = 5552 }); - options.UserName = "test"; - options.Password = "test"; - options.VirtualHost = "testing"; - options.ClientProvidedName = "Silo-Stream"; - })*/ - .AddRabbitMqStreaming("Queue") - .WithQueue(options => - { - options.Endpoints.Add(new RabbitEndpoint { HostName = "localhost", Port = 5672 }); - options.UserName = "test"; - options.Password = "test"; - options.VirtualHost = "testing"; - options.ClientProvidedName = "Silo-Queue"; - }); - } -} diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj b/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj deleted file mode 100644 index 8bc1127..0000000 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/QueueClientTests.cs b/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/QueueClientTests.cs deleted file mode 100644 index 32c1512..0000000 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/QueueClientTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests; - -using System.Text; -using global::RabbitMQ.Client; -using Xunit.Categories; - -/// -/// Queue Client Tests. -/// -public class QueueClientTests -{ - /// - /// Connect. - /// - /// A representing the result of the asynchronous operation. - [Fact] - [IntegrationTest] - public Task ConnectAsync() - { - var connectionFactory = new ConnectionFactory - { - HostName = "localhost", - Port = 5672, - VirtualHost = "testing", - UserName = "test", - Password = "test", - ClientProvidedName = "queue-client-test", - }; - - var connection = connectionFactory.CreateConnection(); - var model = connection.CreateModel(); - - model.ExchangeDeclare("exchange.test", ExchangeType.Fanout, true, false, new Dictionary()); - model.QueueDeclare("queue.test", true, false, false, null); - model.QueueBind("queue.test", "exchange.test", "RK", null); - model.BasicPublish("exchange.test", "RK", false, null, Encoding.UTF8.GetBytes("THIS I A STRING")); - Assert.NotNull(connectionFactory); - - return Task.CompletedTask; - } -} diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/StreamClientTests.cs b/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/StreamClientTests.cs deleted file mode 100644 index 676f526..0000000 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/StreamClientTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Escendit Ltd. All Rights Reserved. -// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. - -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests; - -using System.Net; -using global::RabbitMQ.Stream.Client; -using Xunit.Abstractions; -using Xunit.Categories; - -/// -/// Stream Client Tests. -/// -public class StreamClientTests -{ - private readonly ITestOutputHelper _testOutputHelper; - - /// - /// Initializes a new instance of the class. - /// - /// The test output helper. - public StreamClientTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - /// - /// Connect. - /// - /// A representing the result of the asynchronous operation. - [Fact] - [IntegrationTest] - public async Task ConnectAsync() - { - var streamSystem = await StreamSystem.Create(new StreamSystemConfig - { - Endpoints = new List - { - new DnsEndPoint("localhost", 5552), - }, - UserName = "test", - Password = "test", - VirtualHost = "testing", - ClientProvidedName = "stream-client-test", - }); - - await streamSystem.CreateStream(new StreamSpec("stream.test") - { - MaxSegmentSizeBytes = 20_000_000, - }).ConfigureAwait(false); - - var rawProducer = await streamSystem.CreateRawProducer(new RawProducerConfig("stream.test")); - var lastPublishingId = await rawProducer.GetLastPublishingId(); - await rawProducer.Send(lastPublishingId + 1, new Message("I AM A RAW STREAM!"u8.ToArray())); - await streamSystem.Close().ConfigureAwait(false); - } -} diff --git a/test/Orleans/AmqpProtocol/ClientBuilderTests.cs b/test/Orleans/AmqpProtocol/ClientBuilderTests.cs new file mode 100644 index 0000000..3beb4a0 --- /dev/null +++ b/test/Orleans/AmqpProtocol/ClientBuilderTests.cs @@ -0,0 +1,87 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests; + +using Collections; +using Fixtures; +using global::Orleans.Streams; +using global::RabbitMQ.Tests.Extensions; +using global::RabbitMQ.Tests.Generators; +using Hosting; + +/// +/// Client Builder Tests. +/// +[Collection(HostCollectionFixture.Name)] +public class ClientBuilderTests +{ + private readonly HostBuilderFixture _fixture; + + /// + /// Initializes a new instance of the class. + /// + /// The cluster fixture. + public ClientBuilderTests(HostBuilderFixture fixture) + { + ArgumentNullException.ThrowIfNull(fixture); + _fixture = fixture; + } + + /// + /// Add Rabbit MQ. + /// + [Fact] + [UnitTest] + public void AddRabbitMq() + { + var host = _fixture + .CreateClusterClientHostBuilder(clientBuilder => + { + clientBuilder + .AddRabbitMq("test"); + }) + .Build(); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ Use Stream Protocol. + /// + [Fact] + [UnitTest] + public void AddRabbitMqUseStreamProtocol() + { + var host = _fixture + .CreateClusterClientHostBuilder(clientBuilder => + { + clientBuilder + .AddRabbitMq("test") + .UseAmqpProtocol(ConnectionExtensions.Setup) + .Build(); + }); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ UseStreamProtocol UsePubSubType. + /// + /// The type. + [Theory] + [ClassData(typeof(StreamPubSubTypeGenerator))] + [UnitTest] + public void AddRabbitMqUseStreamProtocolUsePubSubType(StreamPubSubType type) + { + var host = _fixture + .CreateClusterClientHostBuilder(clientBuilder => + { + clientBuilder + .AddRabbitMq("test") + .UseAmqpProtocol(ConnectionExtensions.Setup) + .ConfigureStreamPubSub(type) + .Build(); + }) + .Build(); + Assert.NotNull(host); + } +} diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Collections/ClusterCollectionFixture.cs b/test/Orleans/AmqpProtocol/Collections/ClusterCollectionFixture.cs similarity index 78% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Collections/ClusterCollectionFixture.cs rename to test/Orleans/AmqpProtocol/Collections/ClusterCollectionFixture.cs index bf2fac9..7e502fc 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Collections/ClusterCollectionFixture.cs +++ b/test/Orleans/AmqpProtocol/Collections/ClusterCollectionFixture.cs @@ -1,9 +1,9 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Collections; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.Collections; -using Escendit.Orleans.Streaming.RabbitMQ.Tests.Fixtures; +using Fixtures; /// /// Cluster Collection. diff --git a/test/Orleans/AmqpProtocol/Collections/HostCollectionFixture.cs b/test/Orleans/AmqpProtocol/Collections/HostCollectionFixture.cs new file mode 100644 index 0000000..6abc21e --- /dev/null +++ b/test/Orleans/AmqpProtocol/Collections/HostCollectionFixture.cs @@ -0,0 +1,18 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.Collections; + +using Fixtures; + +/// +/// Host Collection Fixture. +/// +[CollectionDefinition(Name)] +public class HostCollectionFixture : ICollectionFixture +{ + /// + /// Name. + /// + public const string Name = "HostBuilderCollection"; +} diff --git a/test/Orleans/AmqpProtocol/Configuration/RabbitMqClientConfigurator.cs b/test/Orleans/AmqpProtocol/Configuration/RabbitMqClientConfigurator.cs new file mode 100644 index 0000000..402d016 --- /dev/null +++ b/test/Orleans/AmqpProtocol/Configuration/RabbitMqClientConfigurator.cs @@ -0,0 +1,34 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.Configuration; + +using Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions; +using global::Orleans.TestingHost; +using Hosting; +using Microsoft.Extensions.Configuration; + +/// +/// Test Client Configurator. +/// +public class RabbitMqClientConfigurator : IClientBuilderConfigurator +{ + /// + public void Configure(IConfiguration configuration, IClientBuilder clientBuilder) + { + clientBuilder + .AddRabbitMq("client") + .UseAmqpProtocol(options => + { + options.UserName = "guest"; + options.Password = "guest"; + options.VirtualHost = "/"; + options.Endpoints.Add(new Endpoint + { + HostName = "localhost", + Port = 5672, + }); + }) + .Build(); + } +} diff --git a/test/Orleans/AmqpProtocol/Configuration/RabbitMqSiloConfigurator.cs b/test/Orleans/AmqpProtocol/Configuration/RabbitMqSiloConfigurator.cs new file mode 100644 index 0000000..2dda3d2 --- /dev/null +++ b/test/Orleans/AmqpProtocol/Configuration/RabbitMqSiloConfigurator.cs @@ -0,0 +1,36 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.Configuration; + +using Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions; +using global::Orleans.TestingHost; +using Hosting; + +/// +/// Rabbit MQ Silo Configurator. +/// +public class RabbitMqSiloConfigurator : ISiloConfigurator +{ + /// + public void Configure(ISiloBuilder siloBuilder) + { + siloBuilder + .AddMemoryGrainStorageAsDefault() + .AddMemoryGrainStorage("PubSubStore") + .AddStreaming() + .AddRabbitMq("silo") + .UseAmqpProtocol(options => + { + options.UserName = "guest"; + options.Password = "guest"; + options.VirtualHost = "/"; + options.Endpoints.Add(new Endpoint + { + HostName = "localhost", + Port = 5672, + }); + }) + .Build(); + } +} diff --git a/test/Orleans/AmqpProtocol/Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.csproj b/test/Orleans/AmqpProtocol/Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.csproj new file mode 100644 index 0000000..c2a5bc0 --- /dev/null +++ b/test/Orleans/AmqpProtocol/Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.csproj @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Fixtures/ClusterFixture.cs b/test/Orleans/AmqpProtocol/Fixtures/ClusterFixture.cs similarity index 89% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Fixtures/ClusterFixture.cs rename to test/Orleans/AmqpProtocol/Fixtures/ClusterFixture.cs index eef7da7..e5a342b 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Fixtures/ClusterFixture.cs +++ b/test/Orleans/AmqpProtocol/Fixtures/ClusterFixture.cs @@ -1,17 +1,15 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Fixtures; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.Fixtures; using Configuration; using global::Orleans.TestingHost; -using System.Diagnostics.CodeAnalysis; +using global::RabbitMQ.Configuration; /// /// Cluster Fixture. /// -[DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicConstructors)] public sealed class ClusterFixture : IDisposable { /// diff --git a/test/Orleans/AmqpProtocol/Fixtures/HostBuilderFixture.cs b/test/Orleans/AmqpProtocol/Fixtures/HostBuilderFixture.cs new file mode 100644 index 0000000..69be55f --- /dev/null +++ b/test/Orleans/AmqpProtocol/Fixtures/HostBuilderFixture.cs @@ -0,0 +1,89 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests.Fixtures; + +using Microsoft.Extensions.Hosting; + +/// +/// Host Builder Fixture. +/// +public sealed class HostBuilderFixture +{ + private readonly string _pubSubStore; + + /// + /// Initializes a new instance of the class. + /// + public HostBuilderFixture() + { + _pubSubStore = "PubSubStore"; + } + + /// + /// Create Host Builder. + /// + /// The silo builder. + /// The new host builder. + public IHostBuilder CreateSiloHostBuilder(Action siloBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleans(SetupSiloDefaults) + .UseOrleans(siloBuilder); + } + + /// + /// Create Host Builder. + /// + /// The silo builder. + /// The host builder. + public IHostBuilder CreateSiloHostBuilder(Action siloBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleans(SetupSiloDefaults) + .UseOrleans(siloBuilder); + } + + /// + /// Create Cluster Client Host Builder. + /// + /// The client builder. + /// The host builder. + public IHostBuilder CreateClusterClientHostBuilder(Action clientBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleansClient(SetupClientDefaults) + .UseOrleansClient(clientBuilder); + } + + /// + /// Create Cluster Client Host Builder. + /// + /// The client builder. + /// The host builder. + public IHostBuilder CreateClusterClientHostBuilder(Action clientBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleansClient(SetupClientDefaults) + .UseOrleansClient(clientBuilder); + } + + private void SetupClientDefaults(IClientBuilder clientBuilder) + { + clientBuilder + .AddMemoryStreams(_pubSubStore) + .UseLocalhostClustering(); + } + + private void SetupSiloDefaults(HostBuilderContext context, ISiloBuilder siloBuilder) + { + siloBuilder + .UseLocalhostClustering() + .AddMemoryGrainStorage(_pubSubStore) + .AddMemoryGrainStorageAsDefault(); + } +} diff --git a/test/Orleans/AmqpProtocol/MockTests.cs b/test/Orleans/AmqpProtocol/MockTests.cs new file mode 100644 index 0000000..316e8d3 --- /dev/null +++ b/test/Orleans/AmqpProtocol/MockTests.cs @@ -0,0 +1,149 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests; + +using AmqpProtocol.Configuration; +using Core; +using global::Orleans.Configuration; +using global::Orleans.Runtime; +using global::Orleans.Serialization; +using global::Orleans.Serialization.Codecs; +using global::Orleans.Serialization.Configuration; +using global::Orleans.Serialization.Serializers; +using global::Orleans.Serialization.Session; +using global::Orleans.Serialization.TypeSystem; +using global::Orleans.Streams; +using global::RabbitMQ.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using NSubstitute; +using Provider; +using RabbitMQ.Tests.Grains; + +/// +/// Mock Tests. +/// +public sealed class MockTests : IDisposable +{ + private readonly ILoggerFactory _loggerFactory; + private readonly IServiceProvider _serviceProvider; + + /// + /// Initializes a new instance of the class. + /// + public MockTests() + { + _loggerFactory = new NullLoggerFactory(); + var serviceCollection = new ServiceCollection(); + _serviceProvider = serviceCollection.BuildServiceProvider(); + } + + /// + /// Finalizes an instance of the class. + /// + ~MockTests() => Dispose(false); + + /// + /// Create Amqp Protocol Adapter. + /// + [Fact] + [UnitTest] + public void CreateAmqpProtocolAdapter() + { + var testAdapter = BuildProtocolAdapter(); + Assert.NotNull(testAdapter); + } + + /// + /// Get Amqp Protocol Adapter Name. + /// + [Fact] + [UnitTest] + public void GetAmqpProtocolAdapterName() + { + var testAdapter = BuildProtocolAdapter(); + Assert.NotNull(testAdapter.Name); + } + + /// + /// Get Amqp Protocol Adapter IsRewindable. + /// + [Fact] + [UnitTest] + public void GetAmqpProtocolAdapterIsRewindable() + { + var testAdapter = BuildProtocolAdapter(); + Assert.False(testAdapter.IsRewindable); + } + + /// + /// Get Amqp Protocol Adapter Direction. + /// + [Fact] + [UnitTest] + public void GetAmqpProtocolAdapterDirection() + { + var testAdapter = BuildProtocolAdapter(); + Assert.Equal(StreamProviderDirection.ReadWrite, testAdapter.Direction); + } + + /// + /// Create Receiver. + /// + [Fact] + [UnitTest] + public void CreateReceiver() + { + var testAdapter = BuildProtocolAdapter(); + Assert.NotNull(testAdapter.CreateReceiver(QueueId.GetQueueId("test", 0, 0))); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + _loggerFactory.Dispose(); + } + + private AmqpProtocolAdapter BuildProtocolAdapter() + { + var options = Options.Create(new TypeManifestOptions()); + var connection = Substitute.For(); + var typeConverter = new TypeConverter( + new List(), + new List(), + new List(), + options, + new CachedTypeResolver()); + var typeCodec = new TypeCodec(typeConverter); + var wellKnownTypes = new WellKnownTypeCollection(options); + var codecProvider = new CodecProvider(_serviceProvider, options); + var serializerSessionPool = new SerializerSessionPool(typeCodec, wellKnownTypes, codecProvider); + var fieldCodec = Substitute.For>(); + var serializer = new Serializer(fieldCodec, serializerSessionPool); + + var testAdapter = new AmqpProtocolAdapter( + "test", + connection, + _loggerFactory, + new QueueOptions(), + new ClusterOptions(), + serializer, + new HashRingBasedStreamQueueMapper(new HashRingStreamQueueMapperOptions(), "test")); + + return testAdapter; + } +} diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/OrleansTests.cs b/test/Orleans/AmqpProtocol/OrleansTests.cs similarity index 82% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/OrleansTests.cs rename to test/Orleans/AmqpProtocol/OrleansTests.cs index 72dbffb..bafa6ea 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/OrleansTests.cs +++ b/test/Orleans/AmqpProtocol/OrleansTests.cs @@ -1,14 +1,13 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests; +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests; using Collections; using Fixtures; using global::Orleans.Streams; using global::Orleans.TestingHost; -using Grains; -using Xunit.Categories; +using RabbitMQ.Tests.Grains; /// /// Orleans Tests. @@ -21,13 +20,23 @@ public class OrleansTests /// /// Initializes a new instance of the class. /// - /// The fixture. + /// The cluster fixture. public OrleansTests(ClusterFixture fixture) { ArgumentNullException.ThrowIfNull(fixture); _cluster = fixture.Cluster; } + /// + /// Start. + /// + [Fact] + [IntegrationTest] + public void ClusterIsUp() + { + Assert.NotNull(_cluster); + } + /// /// Test Client. /// @@ -56,7 +65,7 @@ public async Task SiloActionAsync() await producerService.CallAsync(1); var value = await producerService.GetAsync(); - Assert.True(value == 1); + Assert.Equal(1, value); } /// @@ -71,7 +80,7 @@ public async Task ClientStreamTestAsync() var producerService = _cluster.Client.GetGrain(Guid.Empty); await producerService.CallAsync(1); - await Task.Delay(500); + await Task.Delay(1500); var otherSideValue = await consumerService.GetAsync(); Assert.Equal(1, otherSideValue); @@ -105,21 +114,20 @@ public async Task ClientDirectStreamAsync() { var consumerService = _cluster.Client.GetGrain(Guid.Empty); var producerService = _cluster.Client.GetGrain(Guid.Empty); + var streamProvider = _cluster.Client.GetStreamProvider("client"); + var stream = streamProvider.GetStream(Guid.Empty); + await stream.SubscribeAsync(Observer); await producerService.CallAsync(1); - - var streamProvider = _cluster.Client.GetStreamProvider("Queue"); - var stream = streamProvider.GetStream("ProducerEvent", Guid.Empty); - - await stream.SubscribeAsync((@event, _) => - { - Assert.Equal(1, @event.NewValue); - return Task.CompletedTask; - }); - - await Task.Delay(500); + await Task.Delay(2500); var newValue = await consumerService.GetAsync(); Assert.Equal(1, newValue); } + + private static Task Observer(ProducerEvent @event, StreamSequenceToken seq) + { + Assert.Equal(1, @event.NewValue); + return Task.CompletedTask; + } } diff --git a/test/Orleans/AmqpProtocol/Properties/AssemblyInfo.cs b/test/Orleans/AmqpProtocol/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..14aeb7e --- /dev/null +++ b/test/Orleans/AmqpProtocol/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +[assembly: CLSCompliant(false)] diff --git a/test/Orleans/AmqpProtocol/SiloBuilderTests.cs b/test/Orleans/AmqpProtocol/SiloBuilderTests.cs new file mode 100644 index 0000000..45c9aad --- /dev/null +++ b/test/Orleans/AmqpProtocol/SiloBuilderTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.AmqpProtocol.Tests; + +using Collections; +using Fixtures; +using global::Orleans.Streams; +using global::RabbitMQ.Tests.Extensions; +using global::RabbitMQ.Tests.Generators; +using Hosting; +using Xunit.Categories; + +/// +/// Silo Builder Tests. +/// +[Collection(HostCollectionFixture.Name)] +public class SiloBuilderTests +{ + private readonly HostBuilderFixture _fixture; + + /// + /// Initializes a new instance of the class. + /// + /// The fixture. + public SiloBuilderTests(HostBuilderFixture fixture) + { + ArgumentNullException.ThrowIfNull(fixture); + _fixture = fixture; + } + + /// + /// Add RabbitMQ. + /// + [Fact] + [UnitTest] + public void AddRabbitMq() + { + var host = _fixture + .CreateSiloHostBuilder(siloBuilder => + { + siloBuilder + .AddRabbitMq("test") + .Build(); + }) + .Build(); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ UseStreamProtocol. + /// + [Fact] + [UnitTest] + public void AddRabbitMqUseStreamProtocol() + { + var host = _fixture + .CreateSiloHostBuilder(siloBuilder => + { + siloBuilder + .AddRabbitMq("test") + .UseAmqpProtocol(ConnectionExtensions.Setup) + .Build(); + }) + .Build(); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ UseStreamProtocol PubSub. + /// + /// The type. + [Theory] + [ClassData(typeof(StreamPubSubTypeGenerator))] + [UnitTest] + public void AddRabbitMqUseStreamProtocolWithPubSubType(StreamPubSubType type) + { + var host = _fixture + .CreateSiloHostBuilder(siloBuilder => + { + siloBuilder + .AddRabbitMq("test") + .UseAmqpProtocol(ConnectionExtensions.Setup) + .ConfigureStreamPubSub(type) + .Build(); + }) + .Build(); + Assert.NotNull(host); + } +} diff --git a/test/Orleans/AmqpProtocol/Usings.cs b/test/Orleans/AmqpProtocol/Usings.cs new file mode 100644 index 0000000..bddebe2 --- /dev/null +++ b/test/Orleans/AmqpProtocol/Usings.cs @@ -0,0 +1,9 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +#pragma warning disable SA1200 +#pragma warning disable IDE0065 // Misplaced using directive +global using Xunit; +global using Xunit.Categories; +#pragma warning restore IDE0065 // Misplaced using directive +#pragma warning restore SA1200 diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/TestSiloConfigurator.cs b/test/Orleans/RabbitMQ/Configuration/TestSiloConfigurator.cs similarity index 63% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/TestSiloConfigurator.cs rename to test/Orleans/RabbitMQ/Configuration/TestSiloConfigurator.cs index d8db68b..8867864 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Configuration/TestSiloConfigurator.cs +++ b/test/Orleans/RabbitMQ/Configuration/TestSiloConfigurator.cs @@ -1,12 +1,10 @@ // Copyright (c) Escendit Ltd. All Rights Reserved. // Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. -namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Configuration; +namespace RabbitMQ.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using global::Orleans.TestingHost; -using Options; +using Orleans.TestingHost; /// /// Test Silo Configurator. @@ -30,17 +28,5 @@ public void Configure(ISiloBuilder siloBuilder) siloBuilder .AddMemoryGrainStorageAsDefault() .AddLogStorageBasedLogConsistencyProviderAsDefault(); - - siloBuilder - .Services - .AddRabbitMq("custom", options => - { - options.Endpoints.Add(new RabbitEndpoint { HostName = "localhost", Port = 5672 }); - options.UserName = "test"; - options.Password = "test"; - options.VirtualHost = "testing"; - options.ClientProvidedName = "Custom-Connection"; - }) - .WithConnection(); } } diff --git a/test/Orleans/RabbitMQ/DependencyInjectionUsageTests.cs b/test/Orleans/RabbitMQ/DependencyInjectionUsageTests.cs new file mode 100644 index 0000000..fee75e4 --- /dev/null +++ b/test/Orleans/RabbitMQ/DependencyInjectionUsageTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace RabbitMQ.Tests; + +using Escendit.Orleans.Streaming.RabbitMQ.Hosting; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Categories; + +/// +/// Dependency Injection Usage Tests. +/// +public class DependencyInjectionUsageTests +{ + /// + /// Start. + /// + [Fact] + [UnitTest] + public void Start() + { + var host = Host.CreateDefaultBuilder() + .UseOrleans(siloBuilder => siloBuilder.UseLocalhostClustering()) + .UseOrleans(siloBuilder => siloBuilder + .AddRabbitMq("Name") + .Build()) + .Build(); + Assert.NotNull(host); + } +} diff --git a/test/Orleans/RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj b/test/Orleans/RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj new file mode 100644 index 0000000..1455a5a --- /dev/null +++ b/test/Orleans/RabbitMQ/Escendit.Orleans.Streaming.RabbitMQ.Tests.csproj @@ -0,0 +1,12 @@ + + + Escendit.Orleans.Streaming.RabbitMQ.Tests + + + + + + + + + diff --git a/test/Orleans/RabbitMQ/Extensions/ConnectionExtensions.cs b/test/Orleans/RabbitMQ/Extensions/ConnectionExtensions.cs new file mode 100644 index 0000000..9d32f57 --- /dev/null +++ b/test/Orleans/RabbitMQ/Extensions/ConnectionExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace RabbitMQ.Tests.Extensions; + +using Escendit.Extensions.DependencyInjection.RabbitMQ.Abstractions; + +/// +/// Connection Extensions. +/// +public static class ConnectionExtensions +{ + /// + /// Setup. + /// + /// The connection options. + public static void Setup(ConnectionOptions options) + { + ArgumentNullException.ThrowIfNull(options); + options.UserName = "guest"; + options.Password = "guest"; + options.VirtualHost = "/"; + options.Endpoints.Add(new Endpoint + { + HostName = "localhost", + }); + } +} diff --git a/test/Orleans/RabbitMQ/Generators/StreamPubSubTypeGenerator.cs b/test/Orleans/RabbitMQ/Generators/StreamPubSubTypeGenerator.cs new file mode 100644 index 0000000..a9ace0a --- /dev/null +++ b/test/Orleans/RabbitMQ/Generators/StreamPubSubTypeGenerator.cs @@ -0,0 +1,26 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace RabbitMQ.Tests.Generators; + +using System.Collections; +using Orleans.Streams; + +/// +/// Stream PubSub Type Generator. +/// +public class StreamPubSubTypeGenerator : IEnumerable +{ + private readonly List _data = new List() + { + new object[] { StreamPubSubType.ExplicitGrainBasedAndImplicit }, + new object[] { StreamPubSubType.ImplicitOnly }, + new object[] { StreamPubSubType.ExplicitGrainBasedOnly }, + }; + + /// + public IEnumerator GetEnumerator() => _data.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerEvent.cs b/test/Orleans/RabbitMQ/Grains/ConsumerEvent.cs similarity index 100% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerEvent.cs rename to test/Orleans/RabbitMQ/Grains/ConsumerEvent.cs diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerService.cs b/test/Orleans/RabbitMQ/Grains/ConsumerService.cs similarity index 95% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerService.cs rename to test/Orleans/RabbitMQ/Grains/ConsumerService.cs index 71cb0b9..dd866ef 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerService.cs +++ b/test/Orleans/RabbitMQ/Grains/ConsumerService.cs @@ -18,7 +18,7 @@ public class ConsumerService : JournaledGrain, ICo /// public override async Task OnActivateAsync(CancellationToken cancellationToken) { - var streamProvider = this.GetStreamProvider("Queue"); + var streamProvider = this.GetStreamProvider("silo"); var stream = streamProvider.GetStream("ProducerEvent", Guid.Empty); if (_streamSubscriptionHandle is null) @@ -53,6 +53,7 @@ public Task GetAsync(GrainCancellationToken? cancellationToken = default) /// public async Task OnNextAsync(ProducerEvent item, StreamSequenceToken? token = null) { + ArgumentNullException.ThrowIfNull(item); var listenedEvent = new ConsumerEvent { Value = item.NewValue, diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerState.cs b/test/Orleans/RabbitMQ/Grains/ConsumerState.cs similarity index 100% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ConsumerState.cs rename to test/Orleans/RabbitMQ/Grains/ConsumerState.cs diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/IConsumerService.cs b/test/Orleans/RabbitMQ/Grains/IConsumerService.cs similarity index 94% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/IConsumerService.cs rename to test/Orleans/RabbitMQ/Grains/IConsumerService.cs index e52b25a..e25644a 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/IConsumerService.cs +++ b/test/Orleans/RabbitMQ/Grains/IConsumerService.cs @@ -6,6 +6,7 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Grains; /// /// Consumer Service. /// +[Alias("consumer")] public interface IConsumerService : IGrainWithGuidKey { /// @@ -13,5 +14,6 @@ public interface IConsumerService : IGrainWithGuidKey /// /// The cancellation token. /// A representing the result of the asynchronous operation. + [Alias("get")] Task GetAsync(GrainCancellationToken? cancellationToken = default); } diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/IProducerService.cs b/test/Orleans/RabbitMQ/Grains/IProducerService.cs similarity index 93% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/IProducerService.cs rename to test/Orleans/RabbitMQ/Grains/IProducerService.cs index 320f46d..e3cecea 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/IProducerService.cs +++ b/test/Orleans/RabbitMQ/Grains/IProducerService.cs @@ -6,6 +6,7 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Grains; /// /// Test Producer Service. /// +[Alias("producer")] public interface IProducerService : IGrainWithGuidKey { /// @@ -14,6 +15,7 @@ public interface IProducerService : IGrainWithGuidKey /// The new value. /// The cancellation token. /// The void. + [Alias("call")] Task CallAsync(int newValue, GrainCancellationToken? cancellationToken = default); /// @@ -21,5 +23,6 @@ public interface IProducerService : IGrainWithGuidKey /// /// The cancellation token. /// The value. + [Alias("get")] Task GetAsync(GrainCancellationToken? cancellationToken = default); } diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerEvent.cs b/test/Orleans/RabbitMQ/Grains/ProducerEvent.cs similarity index 94% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerEvent.cs rename to test/Orleans/RabbitMQ/Grains/ProducerEvent.cs index a90f4f3..bd2eae6 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerEvent.cs +++ b/test/Orleans/RabbitMQ/Grains/ProducerEvent.cs @@ -7,6 +7,7 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Grains; /// Producer Event. /// [GenerateSerializer] +[Alias("producerEvent")] public class ProducerEvent { /// diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerService.cs b/test/Orleans/RabbitMQ/Grains/ProducerService.cs similarity index 76% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerService.cs rename to test/Orleans/RabbitMQ/Grains/ProducerService.cs index 5b25fbd..4673a58 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerService.cs +++ b/test/Orleans/RabbitMQ/Grains/ProducerService.cs @@ -10,7 +10,7 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Grains; /// /// Producer Service. /// -public class ProducerService : JournaledGrain, IProducerService +public partial class ProducerService : JournaledGrain, IProducerService { private readonly ILogger _logger; @@ -26,8 +26,8 @@ public ProducerService(ILogger logger) /// public async Task CallAsync(int newValue, GrainCancellationToken? cancellationToken = default) { - _logger.LogDebug("Call"); - var streamProvider = this.GetStreamProvider("Queue"); + Log("Call"); + var streamProvider = this.GetStreamProvider("silo"); var stream = streamProvider.GetStream("ProducerEvent", Guid.Empty); var @event = new ProducerEvent @@ -43,7 +43,14 @@ public async Task CallAsync(int newValue, GrainCancellationToken? cancellationTo /// public Task GetAsync(GrainCancellationToken? cancellationToken = default) { - _logger.LogDebug("Get"); + Log("Get"); return Task.FromResult(State.Value); } + + [LoggerMessage( + EventId = 0, + EventName = "Message", + Level = LogLevel.Debug, + Message = "{message}")] + private partial void Log(string message); } diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerState.cs b/test/Orleans/RabbitMQ/Grains/ProducerState.cs similarity index 89% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerState.cs rename to test/Orleans/RabbitMQ/Grains/ProducerState.cs index d73f4f1..cb4be31 100644 --- a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Grains/ProducerState.cs +++ b/test/Orleans/RabbitMQ/Grains/ProducerState.cs @@ -7,6 +7,7 @@ namespace Escendit.Orleans.Streaming.RabbitMQ.Tests.Grains; /// Producer State. /// [GenerateSerializer] +[Alias("producerState")] public class ProducerState { /// @@ -22,6 +23,7 @@ public class ProducerState /// The event. public void Apply(ProducerEvent @event) { + ArgumentNullException.ThrowIfNull(@event); Value = @event.NewValue; } } diff --git a/test/Orleans/RabbitMQ/MockTests.cs b/test/Orleans/RabbitMQ/MockTests.cs new file mode 100644 index 0000000..57db100 --- /dev/null +++ b/test/Orleans/RabbitMQ/MockTests.cs @@ -0,0 +1,193 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace RabbitMQ.Tests; + +using Escendit.Orleans.Streaming.RabbitMQ.Core; +using Escendit.Orleans.Streaming.RabbitMQ.Provider; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Orleans.Runtime; +using Xunit.Categories; + +/// +/// Mock Tests. +/// +public sealed class MockTests : IDisposable +{ + private readonly ILoggerFactory _loggerFactory; + + /// + /// Initializes a new instance of the class. + /// + public MockTests() + { + _loggerFactory = GetLoggerFactory(); + } + + /// + /// Finalizes an instance of the class. + /// + ~MockTests() => Dispose(); + + /// + /// Initial Adapter Factory Base. + /// + [Fact] + [UnitTest] + public void InitiateAdapterFactoryBase() + { + var adapterFactory = Substitute.For(GetLogger("test")); + Assert.NotNull(adapterFactory); + } + + /// + /// Initial Adapter Receiver Base. + /// + [Fact] + [UnitTest] + public void InitiateAdapterReceiverBase() + { + var adapterReceiverBase = Substitute.For(GetLogger("test")); + Assert.NotNull(adapterReceiverBase); + } + + /// + /// Case when service name is null. + /// + [Fact] + [UnitTest] + public void ThrowWhenCreateDefaultStreamQueueMapperNameIsNull() + { + var serviceCollection = new ServiceCollection(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Throws(() => serviceProvider.CreateDefaultStreamQueueMapper(null)); + } + + /// + /// Case when service name is null. + /// + [Fact] + [UnitTest] + public void ThrowWhenCreateDefaultStreamQueueMapperNameIsNotString() + { + var serviceCollection = new ServiceCollection(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Throws(() => serviceProvider.CreateDefaultStreamQueueMapper(1)); + } + + /// + /// Case when service name is null. + /// + [Fact] + [UnitTest] + public void ThrowWhenCreateDefaultQueueAdapterCacheNameIsNotString() + { + var serviceCollection = new ServiceCollection(); + var serviceProvider = serviceCollection.BuildServiceProvider(); + Assert.Throws(() => serviceProvider.CreateDefaultQueueAdapterCache(1)); + } + + /// + /// Operator Matching. + /// + [Fact] + [UnitTest] + public void OperatorMatching() + { + var left = new RabbitMqBatchContainer( + StreamId.Create("test", 1), + new List(), + new Dictionary(), + null, + 1); + + var right = new RabbitMqBatchContainer( + StreamId.Create("test", 2), + new List(), + new Dictionary(), + null, + 2); + + Assert.True(left != right); + Assert.False(left == right); + Assert.True(left < right); + Assert.True(left <= right); + Assert.False(left > right); + Assert.False(left >= right); + } + + /// + /// Match With Left Null. + /// + [Fact] + [UnitTest] + public void CompareMatchLeftNull() + { +#pragma warning disable CA1508 + var left = default(RabbitMqBatchContainer); + var right = new RabbitMqBatchContainer( + StreamId.Create("test", 2), + new List(), + new Dictionary(), + null, + 2); + + Assert.False(left == right); +#pragma warning restore CA1508 + } + + /// + /// Match With Right Null. + /// + [Fact] + [UnitTest] + public void CompareMatchRightNull() + { +#pragma warning disable CA1508 + var left = new RabbitMqBatchContainer( + StreamId.Create("test", 2), + new List(), + new Dictionary(), + null, + 2); + var right = default(RabbitMqBatchContainer); + + Assert.False(left == right); +#pragma warning restore CA1508 + } + + /// + /// Get HashCode to be different than 0. + /// + [Fact] + [UnitTest] + public void CompareHashCode() + { + var container = new RabbitMqBatchContainer( + StreamId.Create("test", 2), + new List(), + new Dictionary(), + null, + 2); + Assert.NotEqual(0, container.GetHashCode()); + } + + /// + public void Dispose() + { + _loggerFactory.Dispose(); + GC.SuppressFinalize(this); + } + + private static ILoggerFactory GetLoggerFactory() + { + return LoggerFactory.Create(_ => { }); + } + + private ILogger GetLogger(string loggerName) + { + return _loggerFactory.CreateLogger(loggerName); + } +} diff --git a/test/Orleans/RabbitMQ/Properties/AssemblyInfo.cs b/test/Orleans/RabbitMQ/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..14aeb7e --- /dev/null +++ b/test/Orleans/RabbitMQ/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +[assembly: CLSCompliant(false)] diff --git a/test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Usings.cs b/test/Orleans/RabbitMQ/Usings.cs similarity index 100% rename from test/Escendit.Orleans.Streaming.RabbitMQ.Tests/Usings.cs rename to test/Orleans/RabbitMQ/Usings.cs diff --git a/test/Orleans/StreamProtocol/ClientBuilderTests.cs b/test/Orleans/StreamProtocol/ClientBuilderTests.cs new file mode 100644 index 0000000..6d65c8f --- /dev/null +++ b/test/Orleans/StreamProtocol/ClientBuilderTests.cs @@ -0,0 +1,89 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests; + +using Collections; +using Fixtures; +using global::Orleans.Streams; +using global::RabbitMQ.Tests.Extensions; +using global::RabbitMQ.Tests.Generators; +using Hosting; +using Microsoft.Extensions.Hosting; +using Xunit.Categories; + +/// +/// Client Builder Tests. +/// +[Collection(HostCollectionFixture.Name)] +public class ClientBuilderTests +{ + private readonly HostBuilderFixture _fixture; + + /// + /// Initializes a new instance of the class. + /// + /// The fixture. + public ClientBuilderTests(HostBuilderFixture fixture) + { + ArgumentNullException.ThrowIfNull(fixture); + _fixture = fixture; + } + + /// + /// Add Rabbit MQ. + /// + [Fact] + [UnitTest] + public void AddRabbitMq() + { + var host = _fixture + .CreateClusterClientHostBuilder(clientBuilder => + { + clientBuilder + .AddRabbitMq("test"); + }) + .Build(); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ Use Stream Protocol. + /// + [Fact] + [UnitTest] + public void AddRabbitMqUseStreamProtocol() + { + var host = _fixture + .CreateClusterClientHostBuilder(clientBuilder => + { + clientBuilder + .AddRabbitMq("test") + .UseStreamProtocol(ConnectionExtensions.Setup) + .Build(); + }); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ UseStreamProtocol UsePubSubType. + /// + /// The type. + [Theory] + [ClassData(typeof(StreamPubSubTypeGenerator))] + [UnitTest] + public void AddRabbitMqUseStreamProtocolUsePubSubType(StreamPubSubType type) + { + var host = _fixture + .CreateClusterClientHostBuilder(clientBuilder => + { + clientBuilder + .AddRabbitMq("test") + .UseStreamProtocol(ConnectionExtensions.Setup) + .ConfigureStreamPubSub(type) + .Build(); + }) + .Build(); + Assert.NotNull(host); + } +} diff --git a/test/Orleans/StreamProtocol/Collections/ClusterCollectionFixture.cs b/test/Orleans/StreamProtocol/Collections/ClusterCollectionFixture.cs new file mode 100644 index 0000000..5f1bdd0 --- /dev/null +++ b/test/Orleans/StreamProtocol/Collections/ClusterCollectionFixture.cs @@ -0,0 +1,18 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.Collections; + +using Fixtures; + +/// +/// Cluster Collection. +/// +[CollectionDefinition(Name)] +public class ClusterCollectionFixture : ICollectionFixture +{ + /// + /// Cluster Collection Name. + /// + public const string Name = "ClusterCollection"; +} diff --git a/test/Orleans/StreamProtocol/Collections/HostCollectionFixture.cs b/test/Orleans/StreamProtocol/Collections/HostCollectionFixture.cs new file mode 100644 index 0000000..938cb15 --- /dev/null +++ b/test/Orleans/StreamProtocol/Collections/HostCollectionFixture.cs @@ -0,0 +1,18 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.Collections; + +using Fixtures; + +/// +/// Host Collection Fixture. +/// +[CollectionDefinition(Name)] +public class HostCollectionFixture : ICollectionFixture +{ + /// + /// Name. + /// + public const string Name = "HostBuilderCollection"; +} diff --git a/test/Orleans/StreamProtocol/Configuration/RabbitMqClientConfigurator.cs b/test/Orleans/StreamProtocol/Configuration/RabbitMqClientConfigurator.cs new file mode 100644 index 0000000..16f1874 --- /dev/null +++ b/test/Orleans/StreamProtocol/Configuration/RabbitMqClientConfigurator.cs @@ -0,0 +1,34 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.Configuration; + +using Extensions.DependencyInjection.RabbitMQ.Abstractions; +using global::Orleans.TestingHost; +using Hosting; +using Microsoft.Extensions.Configuration; + +/// +/// Test Client Configurator. +/// +public class RabbitMqClientConfigurator : IClientBuilderConfigurator +{ + /// + public void Configure(IConfiguration configuration, IClientBuilder clientBuilder) + { + clientBuilder + .AddRabbitMq("client") + .UseStreamProtocol(options => + { + options.UserName = "guest"; + options.Password = "guest"; + options.VirtualHost = "/"; + options.Endpoints.Add(new Endpoint + { + HostName = "localhost", + Port = 5552, + }); + }) + .Build(); + } +} diff --git a/test/Orleans/StreamProtocol/Configuration/RabbitMqSiloConfigurator.cs b/test/Orleans/StreamProtocol/Configuration/RabbitMqSiloConfigurator.cs new file mode 100644 index 0000000..82ebb94 --- /dev/null +++ b/test/Orleans/StreamProtocol/Configuration/RabbitMqSiloConfigurator.cs @@ -0,0 +1,36 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.Configuration; + +using Extensions.DependencyInjection.RabbitMQ.Abstractions; +using global::Orleans.TestingHost; +using Hosting; + +/// +/// Rabbit MQ Silo Configurator. +/// +public class RabbitMqSiloConfigurator : ISiloConfigurator +{ + /// + public void Configure(ISiloBuilder siloBuilder) + { + siloBuilder + .AddMemoryGrainStorageAsDefault() + .AddMemoryGrainStorage("PubSubStore") + .AddStreaming() + .AddRabbitMq("silo") + .UseStreamProtocol(options => + { + options.UserName = "guest"; + options.Password = "guest"; + options.VirtualHost = "/"; + options.Endpoints.Add(new Endpoint + { + HostName = "localhost", + Port = 5552, + }); + }) + .Build(); + } +} diff --git a/test/Orleans/StreamProtocol/Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.csproj b/test/Orleans/StreamProtocol/Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.csproj new file mode 100644 index 0000000..b7e3fe0 --- /dev/null +++ b/test/Orleans/StreamProtocol/Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.csproj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/Orleans/StreamProtocol/Fixtures/ClusterFixture.cs b/test/Orleans/StreamProtocol/Fixtures/ClusterFixture.cs new file mode 100644 index 0000000..dcc7905 --- /dev/null +++ b/test/Orleans/StreamProtocol/Fixtures/ClusterFixture.cs @@ -0,0 +1,68 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.Fixtures; + +using Configuration; +using global::Orleans.TestingHost; +using global::RabbitMQ.Configuration; + +/// +/// Cluster Fixture. +/// +public sealed class ClusterFixture : IDisposable +{ + /// + /// Initializes a new instance of the class. + /// + public ClusterFixture() + { + var builder = new TestClusterBuilder() + { + Options = + { + ClusterId = "testCluster", + ServiceId = "testService", + }, + }; + builder + .AddSiloBuilderConfigurator() + .AddClientBuilderConfigurator() + .AddSiloBuilderConfigurator(); + Cluster = builder.Build(); + Cluster.Deploy(); + } + + /// + /// Finalizes an instance of the class. + /// + ~ClusterFixture() + { + Dispose(false); + } + + /// + /// Gets the cluster. + /// + /// The cluster. + public TestCluster Cluster { get; } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// The dispose pattern. + /// + /// if cleanup. + private void Dispose(bool disposing) + { + if (disposing) + { + Cluster.StopAllSilos(); + } + } +} diff --git a/test/Orleans/StreamProtocol/Fixtures/HostBuilderFixture.cs b/test/Orleans/StreamProtocol/Fixtures/HostBuilderFixture.cs new file mode 100644 index 0000000..9a8e7d4 --- /dev/null +++ b/test/Orleans/StreamProtocol/Fixtures/HostBuilderFixture.cs @@ -0,0 +1,89 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests.Fixtures; + +using Microsoft.Extensions.Hosting; + +/// +/// Host Builder Fixture. +/// +public sealed class HostBuilderFixture +{ + private readonly string _pubSubStore; + + /// + /// Initializes a new instance of the class. + /// + public HostBuilderFixture() + { + _pubSubStore = "PubSubStore"; + } + + /// + /// Create Host Builder. + /// + /// The silo builder. + /// The new host builder. + public IHostBuilder CreateSiloHostBuilder(Action siloBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleans(SetupSiloDefaults) + .UseOrleans(siloBuilder); + } + + /// + /// Create Host Builder. + /// + /// The silo builder. + /// The host builder. + public IHostBuilder CreateSiloHostBuilder(Action siloBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleans(SetupSiloDefaults) + .UseOrleans(siloBuilder); + } + + /// + /// Create Cluster Client Host Builder. + /// + /// The client builder. + /// The host builder. + public IHostBuilder CreateClusterClientHostBuilder(Action clientBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleansClient(SetupClientDefaults) + .UseOrleansClient(clientBuilder); + } + + /// + /// Create Cluster Client Host Builder. + /// + /// The client builder. + /// The host builder. + public IHostBuilder CreateClusterClientHostBuilder(Action clientBuilder) + { + return Host + .CreateDefaultBuilder() + .UseOrleansClient(SetupClientDefaults) + .UseOrleansClient(clientBuilder); + } + + private void SetupSiloDefaults(HostBuilderContext context, ISiloBuilder siloBuilder) + { + siloBuilder + .UseLocalhostClustering() + .AddMemoryGrainStorage(_pubSubStore) + .AddMemoryGrainStorageAsDefault(); + } + + private void SetupClientDefaults(IClientBuilder clientBuilder) + { + clientBuilder + .AddMemoryStreams(_pubSubStore) + .UseLocalhostClustering(); + } +} diff --git a/test/Orleans/StreamProtocol/OrleansTests.cs b/test/Orleans/StreamProtocol/OrleansTests.cs new file mode 100644 index 0000000..b701b1e --- /dev/null +++ b/test/Orleans/StreamProtocol/OrleansTests.cs @@ -0,0 +1,133 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests; + +using Collections; +using Fixtures; +using global::Orleans.Streams; +using global::Orleans.TestingHost; +using RabbitMQ.Tests.Grains; + +/// +/// Orleans Tests. +/// +[Collection(ClusterCollectionFixture.Name)] +public class OrleansTests +{ + private readonly TestCluster _cluster; + + /// + /// Initializes a new instance of the class. + /// + /// The cluster fixture. + public OrleansTests(ClusterFixture fixture) + { + ArgumentNullException.ThrowIfNull(fixture); + _cluster = fixture.Cluster; + } + + /// + /// Start. + /// + [Fact] + [IntegrationTest] + public void ClusterIsUp() + { + Assert.NotNull(_cluster); + } + + /// + /// Test Client. + /// + /// A representing the asynchronous unit test. + [Fact] + [IntegrationTest] + public async Task ClientActionAsync() + { + var producerService = _cluster.Client.GetGrain(Guid.Empty); + await producerService.CallAsync(1); + var value = await producerService.GetAsync(); + Assert.Equal(1, value); + } + + /// + /// Test. + /// + /// A representing the asynchronous unit test. + [Fact] + [IntegrationTest] + public async Task SiloActionAsync() + { + // Method intentionally left empty. + var producerService = _cluster.GrainFactory.GetGrain(Guid.Empty); + + await producerService.CallAsync(1); + + var value = await producerService.GetAsync(); + Assert.Equal(1, value); + } + + /// + /// Tests the streaming with client. + /// + /// A representing the asynchronous unit test. + [Fact] + [IntegrationTest] + public async Task ClientStreamTestAsync() + { + var consumerService = _cluster.Client.GetGrain(Guid.Empty); + var producerService = _cluster.Client.GetGrain(Guid.Empty); + + await producerService.CallAsync(1); + await Task.Delay(1500); + var otherSideValue = await consumerService.GetAsync(); + + Assert.Equal(1, otherSideValue); + } + + /// + /// Tests the streaming. + /// + /// A representing the asynchronous unit test. + [Fact] + [IntegrationTest] + public async Task SiloStreamTestAsync() + { + var consumerService = _cluster.GrainFactory.GetGrain(Guid.Empty); + var producerService = _cluster.GrainFactory.GetGrain(Guid.Empty); + + await producerService.CallAsync(1); + await Task.Delay(1000); + var otherSideValue = await consumerService.GetAsync(); + + Assert.Equal(1, otherSideValue); + } + + /// + /// Direct Client Streaming. + /// + /// A representing the asynchronous unit test. + [Fact] + [IntegrationTest] + public async Task ClientDirectStreamAsync() + { + var consumerService = _cluster.Client.GetGrain(Guid.Empty); + var producerService = _cluster.Client.GetGrain(Guid.Empty); + var streamProvider = _cluster.Client.GetStreamProvider("client"); + var stream = streamProvider.GetStream(Guid.Empty); + await stream.SubscribeAsync(Observer); + + await producerService.CallAsync(1); + await Task.Delay(2500); + + var newValue = await consumerService.GetAsync(); + Assert.Equal(1, newValue); + } + + private static Task Observer(ProducerEvent @event, StreamSequenceToken seq) + { + Assert.Equal(1, @event.NewValue); + return Task.CompletedTask; + } +} diff --git a/test/Orleans/StreamProtocol/Properties/AssemblyInfo.cs b/test/Orleans/StreamProtocol/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..14aeb7e --- /dev/null +++ b/test/Orleans/StreamProtocol/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +[assembly: CLSCompliant(false)] diff --git a/test/Orleans/StreamProtocol/SiloBuilderTests.cs b/test/Orleans/StreamProtocol/SiloBuilderTests.cs new file mode 100644 index 0000000..13574ee --- /dev/null +++ b/test/Orleans/StreamProtocol/SiloBuilderTests.cs @@ -0,0 +1,89 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +namespace Escendit.Orleans.Streaming.RabbitMQ.StreamProtocol.Tests; + +using Collections; +using Fixtures; +using global::Orleans.Streams; +using global::RabbitMQ.Tests.Extensions; +using global::RabbitMQ.Tests.Generators; +using Hosting; + +/// +/// Silo Builder Tests. +/// +[Collection(HostCollectionFixture.Name)] +public class SiloBuilderTests +{ + private readonly HostBuilderFixture _fixture; + + /// + /// Initializes a new instance of the class. + /// + /// The fixture. + public SiloBuilderTests(HostBuilderFixture fixture) + { + ArgumentNullException.ThrowIfNull(fixture); + _fixture = fixture; + } + + /// + /// Add RabbitMQ. + /// + [Fact] + [UnitTest] + public void AddRabbitMq() + { + var host = _fixture + .CreateSiloHostBuilder(siloBuilder => + { + siloBuilder + .AddRabbitMq("test") + .Build(); + }) + .Build(); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ UseStreamProtocol. + /// + [Fact] + [UnitTest] + public void AddRabbitMqUseStreamProtocol() + { + var host = _fixture + .CreateSiloHostBuilder(siloBuilder => + { + siloBuilder + .AddRabbitMq("test") + .UseStreamProtocol(ConnectionExtensions.Setup) + .Build(); + }) + .Build(); + Assert.NotNull(host); + } + + /// + /// Add RabbitMQ UseStreamProtocol PubSub. + /// + /// The type. + [Theory] + [ClassData(typeof(StreamPubSubTypeGenerator))] + [UnitTest] + public void AddRabbitMqUseStreamProtocolWithPubSubType(StreamPubSubType type) + { + var host = _fixture + .CreateSiloHostBuilder(siloBuilder => + { + siloBuilder + .AddRabbitMq("test") + .UseStreamProtocol(ConnectionExtensions.Setup) + .ConfigureStreamPubSub(type) + .Build(); + }) + .Build(); + Assert.NotNull(host); + } +} diff --git a/test/Orleans/StreamProtocol/Usings.cs b/test/Orleans/StreamProtocol/Usings.cs new file mode 100644 index 0000000..bddebe2 --- /dev/null +++ b/test/Orleans/StreamProtocol/Usings.cs @@ -0,0 +1,9 @@ +// Copyright (c) Escendit Ltd. All Rights Reserved. +// Licensed under the MIT. See LICENSE.txt file in the solution root for full license information. + +#pragma warning disable SA1200 +#pragma warning disable IDE0065 // Misplaced using directive +global using Xunit; +global using Xunit.Categories; +#pragma warning restore IDE0065 // Misplaced using directive +#pragma warning restore SA1200