From 2829d04d088d9cbce347f556b93bf183ec9e98da Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Mon, 8 Apr 2024 13:22:05 +0800 Subject: [PATCH] #238 Resolve HybridMessageSerializer dependencies with IServiceProvider Signed-off-by: Richard Pringle --- docs/serialization.md | 36 +++--- .../Program.cs | 42 ++----- .../Builders/ISerializationBuilder.cs | 3 +- .../Builders/MessageBusBuilder.cs | 15 ++- .../SerializationBuilderExtensions.cs | 6 +- .../SerializationBuilderExtensions.cs | 3 +- .../HybridMessageSerializer.cs | 34 +++--- .../SerializationBuilderExtensions.cs | 113 +++++++++++++++++- ...essageBus.Host.Serialization.Hybrid.csproj | 28 +++-- .../SerializationBuilderExtensions.cs | 3 +- .../SerializationBuilderExtensions.cs | 4 +- src/SlimMessageBus.sln | 11 ++ .../SerializationBuilderExtensionsTest.cs | 2 +- .../Helpers/SampleMessages.cs | 5 + .../HybridMessageSerializerTests.cs | 103 ++++++++++++++++ .../SerializationBuilderExtensionsTests.cs | 86 +++++++++++++ ...eBus.Host.Serialization.Hybrid.Test.csproj | 14 +++ .../Usings.cs | 9 ++ .../JsonMessageSerializerTests.cs | 19 ++- 19 files changed, 436 insertions(+), 100 deletions(-) create mode 100644 src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs create mode 100644 src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs create mode 100644 src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SerializationBuilderExtensionsTests.cs create mode 100644 src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj create mode 100644 src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs diff --git a/docs/serialization.md b/docs/serialization.md index 961881a0..9d90a37d 100644 --- a/docs/serialization.md +++ b/docs/serialization.md @@ -175,21 +175,27 @@ The Hybrid plugin allows to have multiple serialization formats on one message b To use it install the nuget package `SlimMessageBus.Host.Serialization.Hybrid` and then configure the bus: ```cs -services.AddSlimMessageBus(mbb => -{ - // serializer 1 - var avroSerializer = new AvroMessageSerializer(); - - // serializer 2 - var jsonSerializer = new JsonMessageSerializer(); - - // Note: Certain messages will be serialized by the Avro serializer, other using the Json serializer - mbb.AddHybridSerializer(new Dictionary - { - [jsonSerializer] = new[] { typeof(SubtractCommand) }, // the first one will be the default serializer, no need to declare types here - [avroSerializer] = new[] { typeof(AddCommand), typeof(MultiplyRequest), typeof(MultiplyResponse) }, - }, defaultMessageSerializer: jsonSerializer); -}); + services + .AddSlimMessageBus(mbb => + { + mbb + .AddHybridSerializer( + builder => { + builder + .AsDefault() + .AddJsonSerializer(); + + builder + .For(typeof(Message1), typeof(Message2)) + .AddAvroSerializer(); + + builder + .For(typeof(Message3)) + .AddGoogleProtobufSerializer(); + }) + ... + } ``` The routing to the proper serializer happens based on message type. When a type cannot be matched the default serializer will be used. +The routing to the proper serializer happens based on message type. When a type cannot be matched the default serializer will be used. diff --git a/src/Samples/Sample.Serialization.ConsoleApp/Program.cs b/src/Samples/Sample.Serialization.ConsoleApp/Program.cs index 15a7face..607f5f52 100644 --- a/src/Samples/Sample.Serialization.ConsoleApp/Program.cs +++ b/src/Samples/Sample.Serialization.ConsoleApp/Program.cs @@ -39,44 +39,26 @@ static async Task Main(string[] args) => await Host.CreateDefaultBuilder(args) Secrets.Load(@"..\..\..\..\..\secrets.txt"); services.AddHostedService(); - - // alternatively a simpler approach, but using the slower ReflectionMessageCreationStategy and ReflectionSchemaLookupStrategy - var avroSerializer = new AvroMessageSerializer(); - - // Avro serialized using the AvroConvert library - no schema generation neeeded upfront. - var jsonSerializer = new JsonMessageSerializer(); - services .AddSlimMessageBus(mbb => { // Note: remember that Memory provider does not support req-resp yet. - var provider = Provider.Redis; - - /* - var sl = new DictionarySchemaLookupStrategy(); - /// register all your types - sl.Add(typeof(AddCommand), AddCommand._SCHEMA); - sl.Add(typeof(MultiplyRequest), MultiplyRequest._SCHEMA); - sl.Add(typeof(MultiplyResponse), MultiplyResponse._SCHEMA); - - var mf = new DictionaryMessageCreationStategy(); - /// register all your types - mf.Add(typeof(AddCommand), () => new AddCommand()); - mf.Add(typeof(MultiplyRequest), () => new MultiplyRequest()); - mf.Add(typeof(MultiplyResponse), () => new MultiplyResponse()); - - // longer approach, but should be faster as it's not using reflection - var avroSerializer = new AvroMessageSerializer(mf, sl); - */ + var provider = Provider.Memory; mbb .AddServicesFromAssemblyContaining() - // Note: Certain messages will be serialized by one Avro serializer, other using the Json serializer - .AddHybridSerializer(new Dictionary + + // Note: Certain messages will be serialized by the Avro serializer, others will fall back to the Json serializer (the default) + .AddHybridSerializer(builder => { - [jsonSerializer] = new[] { typeof(SubtractCommand) }, // the first one will be the default serializer, no need to declare types here - [avroSerializer] = new[] { typeof(AddCommand), typeof(MultiplyRequest), typeof(MultiplyResponse) }, - }, defaultMessageSerializer: jsonSerializer) + builder + .AsDefault() + .AddJsonSerializer(); + + builder + .For(typeof(AddCommand), typeof(MultiplyRequest), typeof(MultiplyResponse)) + .AddAvroSerializer(); + }) .Produce(x => x.DefaultTopic("AddCommand")) .Consume(x => x.Topic("AddCommand").WithConsumer()) diff --git a/src/SlimMessageBus.Host.Configuration/Builders/ISerializationBuilder.cs b/src/SlimMessageBus.Host.Configuration/Builders/ISerializationBuilder.cs index b45c8e9c..30d6ceb6 100644 --- a/src/SlimMessageBus.Host.Configuration/Builders/ISerializationBuilder.cs +++ b/src/SlimMessageBus.Host.Configuration/Builders/ISerializationBuilder.cs @@ -1,5 +1,6 @@ namespace SlimMessageBus.Host; -public interface ISerializationBuilder : IHasPostConfigurationActions +public interface ISerializationBuilder { + void RegisterSerializer(Action services) where TMessageSerializer : class, IMessageSerializer; } diff --git a/src/SlimMessageBus.Host.Configuration/Builders/MessageBusBuilder.cs b/src/SlimMessageBus.Host.Configuration/Builders/MessageBusBuilder.cs index b499a80b..79326f03 100644 --- a/src/SlimMessageBus.Host.Configuration/Builders/MessageBusBuilder.cs +++ b/src/SlimMessageBus.Host.Configuration/Builders/MessageBusBuilder.cs @@ -1,6 +1,8 @@ namespace SlimMessageBus.Host; -public class MessageBusBuilder : ISerializationBuilder +using Microsoft.Extensions.DependencyInjection.Extensions; + +public class MessageBusBuilder : IHasPostConfigurationActions, ISerializationBuilder { /// /// Parent bus builder. @@ -224,6 +226,13 @@ public MessageBusBuilder WithSerializer(Type serializerType) return this; } + public void RegisterSerializer(Action services) + where TMessageSerializer : class, IMessageSerializer + { + PostConfigurationActions.Add(services); + PostConfigurationActions.Add(services => services.TryAddSingleton(sp => sp.GetRequiredService())); + } + public MessageBusBuilder WithDependencyResolver(IServiceProvider serviceProvider) { Settings.ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); @@ -264,7 +273,7 @@ public MessageBusBuilder WithMessageTypeResolver(Type messageTypeResolverType) public MessageBusBuilder WithMessageTypeResolver() => WithMessageTypeResolver(typeof(T)); /// - /// Hook called whenver message is being produced. Can be used to change message headers. + /// Hook called whenever message is being produced. Can be used to change message headers. /// /// Should the previously set modifier be executed as well? public MessageBusBuilder WithHeaderModifier(MessageHeaderModifier headerModifier, bool executePrevious = true) @@ -338,5 +347,5 @@ public IMessageBusProvider Build() throw new ConfigurationMessageBusException($"{busName}The bus provider was not configured. Check the MessageBus configuration and ensure the has the '.WithProviderXxx()' setting for one of the available transports."); } return BusFactory(Settings); - } + } } diff --git a/src/SlimMessageBus.Host.Serialization.Avro/SerializationBuilderExtensions.cs b/src/SlimMessageBus.Host.Serialization.Avro/SerializationBuilderExtensions.cs index a66b39e0..461f3e72 100644 --- a/src/SlimMessageBus.Host.Serialization.Avro/SerializationBuilderExtensions.cs +++ b/src/SlimMessageBus.Host.Serialization.Avro/SerializationBuilderExtensions.cs @@ -18,10 +18,9 @@ public static class SerializationBuilderExtensions public static TBuilder AddAvroSerializer(this TBuilder builder, IMessageCreationStrategy messageCreationStrategy, ISchemaLookupStrategy schemaLookupStrategy) where TBuilder : ISerializationBuilder { - builder.PostConfigurationActions.Add(services => + builder.RegisterSerializer(services => { services.TryAddSingleton(svp => new AvroMessageSerializer(svp.GetRequiredService(), messageCreationStrategy, schemaLookupStrategy)); - services.TryAddSingleton(svp => svp.GetRequiredService()); }); return builder; } @@ -35,10 +34,9 @@ public static TBuilder AddAvroSerializer(this TBuilder builder, IMessa public static TBuilder AddAvroSerializer(this TBuilder builder) where TBuilder : ISerializationBuilder { - builder.PostConfigurationActions.Add(services => + builder.RegisterSerializer(services => { services.TryAddSingleton(svp => new AvroMessageSerializer(svp.GetRequiredService())); - services.TryAddSingleton(svp => svp.GetRequiredService()); }); return builder; } diff --git a/src/SlimMessageBus.Host.Serialization.GoogleProtobuf/SerializationBuilderExtensions.cs b/src/SlimMessageBus.Host.Serialization.GoogleProtobuf/SerializationBuilderExtensions.cs index d5f73c7d..ec8ca741 100644 --- a/src/SlimMessageBus.Host.Serialization.GoogleProtobuf/SerializationBuilderExtensions.cs +++ b/src/SlimMessageBus.Host.Serialization.GoogleProtobuf/SerializationBuilderExtensions.cs @@ -17,10 +17,9 @@ public static class SerializationBuilderExtensions public static TBuilder AddGoogleProtobufSerializer(this TBuilder builder, IMessageParserFactory messageParserFactory = null) where TBuilder : ISerializationBuilder { - builder.PostConfigurationActions.Add(services => + builder.RegisterSerializer(services => { services.TryAddSingleton(svp => new GoogleProtobufMessageSerializer(svp.GetRequiredService(), messageParserFactory)); - services.TryAddSingleton(svp => svp.GetRequiredService()); }); return builder; } diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs b/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs index 5d2106f7..2645a345 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/HybridMessageSerializer.cs @@ -8,10 +8,12 @@ public class HybridMessageSerializer : IMessageSerializer { private readonly ILogger _logger; - private readonly IList _serializers = new List(); - private readonly IDictionary _serializerByType = new Dictionary(); + private readonly Dictionary _serializerByType = []; + public IMessageSerializer DefaultSerializer { get; set; } + internal IReadOnlyDictionary SerializerByType => _serializerByType; + public HybridMessageSerializer(ILogger logger, IDictionary registration, IMessageSerializer defaultMessageSerializer = null) { _logger = logger; @@ -24,12 +26,14 @@ public HybridMessageSerializer(ILogger logger, IDiction public void Add(IMessageSerializer serializer, params Type[] supportedTypes) { - if (_serializers.Count == 0 && DefaultSerializer == null) - { - DefaultSerializer = serializer; - } +#if NETSTANDARD2_0 + if (serializer is null) throw new ArgumentNullException(nameof(serializer)); +#else + ArgumentNullException.ThrowIfNull(serializer); +#endif + + DefaultSerializer ??= serializer; - _serializers.Add(serializer); foreach (var type in supportedTypes) { _serializerByType.Add(type, serializer); @@ -38,19 +42,19 @@ public void Add(IMessageSerializer serializer, params Type[] supportedTypes) protected virtual IMessageSerializer MatchSerializer(Type t) { - if (_serializers.Count == 0) - { - throw new InvalidOperationException("No serializers registered."); - } - if (!_serializerByType.TryGetValue(t, out var serializer)) { - // use first as default - _logger.LogTrace("Serializer for type {0} not registered, will use default serializer", t); + _logger.LogTrace("Serializer for type {MessageType} not registered, will use default serializer", t); + + if (DefaultSerializer == null) + { + throw new InvalidOperationException("No serializers registered."); + } + serializer = DefaultSerializer; } - _logger.LogDebug("Serializer for type {0} will be {1}", t, serializer); + _logger.LogDebug("Serializer for type {MessageType} will be {Serializer}", t, serializer); return serializer; } diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/SerializationBuilderExtensions.cs b/src/SlimMessageBus.Host.Serialization.Hybrid/SerializationBuilderExtensions.cs index a737934d..703884a2 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/SerializationBuilderExtensions.cs +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/SerializationBuilderExtensions.cs @@ -18,11 +18,120 @@ public static class SerializationBuilderExtensions public static TBuilder AddHybridSerializer(this TBuilder builder, IDictionary registration, IMessageSerializer defaultMessageSerializer) where TBuilder : ISerializationBuilder { - builder.PostConfigurationActions.Add(services => + builder.RegisterSerializer(services => { services.TryAddSingleton(svp => new HybridMessageSerializer(svp.GetRequiredService>(), registration, defaultMessageSerializer)); - services.TryAddSingleton(svp => svp.GetRequiredService()); }); return builder; } + + /// + /// Registers the with implementation as using serializers as registered in the . + /// + /// + /// Action to register serializers for dependency injection resolution. + /// + public static MessageBusBuilder AddHybridSerializer(this MessageBusBuilder mbb, Action registration) + { + var builder = new HybridSerializerOptionsBuilder(); + registration(builder); + + foreach (var action in builder.ServiceRegistrations) + { + mbb.PostConfigurationActions.Add(action); + } + + mbb.PostConfigurationActions.Add(services => + { + services.TryAddSingleton(svp => + { + if (services.Count(x => x.ServiceType == typeof(IMessageSerializer)) > 1) + { + throw new NotSupportedException($"Registering instances of {nameof(IMessageSerializer)} outside of {nameof(AddHybridSerializer)} is not supported."); + } + + var defaultMessageSerializer = builder.DefaultSerializer != null ? (IMessageSerializer)svp.GetRequiredService(builder.DefaultSerializer) : null; + var typeRegistrations = builder.TypeRegistrations.ToDictionary(x => (IMessageSerializer)svp.GetRequiredService(x.Key), x => x.Value); + return new HybridMessageSerializer(svp.GetRequiredService>(), typeRegistrations, defaultMessageSerializer); + }); + + services.AddSingleton(svp => svp.GetRequiredService()); + }); + return mbb; + } + + public sealed class HybridSerializerOptionsBuilder + { + private readonly List _configurations = []; + + public Type DefaultSerializer + { + get + { + return _configurations + .OfType() + .LastOrDefault(x => x.IsValid) + .Type; + } + } + + public IReadOnlyList> ServiceRegistrations + { + get + { + return _configurations + .Where(x => x.IsValid) + .Select(x => x.Action) + .ToList(); + } + } + + public IReadOnlyDictionary TypeRegistrations + { + get + { + return _configurations + .OfType() + .Where(x => x.IsValid) + .ToDictionary(x => x.Type, x => x.Types); + } + } + + public ISerializationBuilder AsDefault() + { + var configuration = new DefaultSerializerConfiguration(); + this._configurations.Add(configuration); + return configuration; + } + + public ISerializationBuilder For(params Type[] types) + { + var configuration = new ForSerializerConfiguration(types); + this._configurations.Add(configuration); + return configuration; + } + + public abstract class SerializerConfiguration : ISerializationBuilder + { + public Action Action { get; private set; } + public bool IsValid => Type != null; + public Type Type { get; private set; } = null; + + public void RegisterSerializer(Action services) + where TMessageSerializer : class, IMessageSerializer + { + Type = typeof(TMessageSerializer); + Action = services; + } + } + + public class ForSerializerConfiguration(Type[] types) : SerializerConfiguration, ISerializationBuilder + { + public Type[] Types { get; } = types; + } + + public class DefaultSerializerConfiguration : SerializerConfiguration, ISerializationBuilder + { + } + } } diff --git a/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj b/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj index f6633595..b5260b1c 100644 --- a/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj +++ b/src/SlimMessageBus.Host.Serialization.Hybrid/SlimMessageBus.Host.Serialization.Hybrid.csproj @@ -1,19 +1,23 @@ - + - - Extension to SlimMessageBus that delegates serialization to the respective serialization plugin based on message type. - SlimMessageBus Serialization messaging - + + Extension to SlimMessageBus that delegates serialization to the respective serialization plugin based on message type. + SlimMessageBus Serialization messaging + - - - - + + + + - - - + + + + + + + diff --git a/src/SlimMessageBus.Host.Serialization.Json/SerializationBuilderExtensions.cs b/src/SlimMessageBus.Host.Serialization.Json/SerializationBuilderExtensions.cs index a40e11fd..4f6c0bad 100644 --- a/src/SlimMessageBus.Host.Serialization.Json/SerializationBuilderExtensions.cs +++ b/src/SlimMessageBus.Host.Serialization.Json/SerializationBuilderExtensions.cs @@ -22,10 +22,9 @@ public static class SerializationBuilderExtensions public static TBuilder AddJsonSerializer(this TBuilder builder, Encoding encoding = null, JsonSerializerSettings jsonSerializerSettings = null) where TBuilder : ISerializationBuilder { - builder.PostConfigurationActions.Add(services => + builder.RegisterSerializer(services => { services.TryAddSingleton(svp => new JsonMessageSerializer(jsonSerializerSettings ?? svp.GetService(), encoding, svp.GetRequiredService>())); - services.TryAddSingleton(svp => svp.GetRequiredService()); }); return builder; } diff --git a/src/SlimMessageBus.Host.Serialization.SystemTextJson/SerializationBuilderExtensions.cs b/src/SlimMessageBus.Host.Serialization.SystemTextJson/SerializationBuilderExtensions.cs index 38452a3b..d42fbd03 100644 --- a/src/SlimMessageBus.Host.Serialization.SystemTextJson/SerializationBuilderExtensions.cs +++ b/src/SlimMessageBus.Host.Serialization.SystemTextJson/SerializationBuilderExtensions.cs @@ -17,10 +17,8 @@ public static class SerializationBuilderExtensions public static TBuilder AddJsonSerializer(this TBuilder builder, JsonSerializerOptions options = null) where TBuilder : ISerializationBuilder { - builder.PostConfigurationActions.Add(services => - { + builder.RegisterSerializer(services => { services.TryAddSingleton(svp => new JsonMessageSerializer(options ?? svp.GetService())); - services.TryAddSingleton(svp => svp.GetRequiredService()); }); return builder; } diff --git a/src/SlimMessageBus.sln b/src/SlimMessageBus.sln index 114dfabf..32a06020 100644 --- a/src/SlimMessageBus.sln +++ b/src/SlimMessageBus.sln @@ -236,6 +236,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.Sql.Com EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlimMessageBus.Host.Serialization.Avro.Test", "Tests\SlimMessageBus.Host.Serialization.Avro.Test\SlimMessageBus.Host.Serialization.Avro.Test.csproj", "{8507237C-68C3-46AD-B7DA-800791C6FDDB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SlimMessageBus.Host.Serialization.Hybrid.Test", "Tests\SlimMessageBus.Host.Serialization.Hybrid.Test\SlimMessageBus.Host.Serialization.Hybrid.Test.csproj", "{DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -740,6 +742,14 @@ Global {8507237C-68C3-46AD-B7DA-800791C6FDDB}.Release|Any CPU.Build.0 = Release|Any CPU {8507237C-68C3-46AD-B7DA-800791C6FDDB}.Release|x86.ActiveCfg = Release|Any CPU {8507237C-68C3-46AD-B7DA-800791C6FDDB}.Release|x86.Build.0 = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Debug|x86.Build.0 = Debug|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|Any CPU.Build.0 = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|x86.ActiveCfg = Release|Any CPU + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -813,6 +823,7 @@ Global {F5373E1D-A2B4-46CC-9B07-94F6655C8E29} = {9F005B5C-A856-4351-8C0C-47A8B785C637} {5EED0E89-2475-40E0-81EF-0F05C9326612} = {9291D340-B4FA-44A3-8060-C14743FB1712} {F19B7A21-7749-465A-8810-4C274A9E8956} = {9291D340-B4FA-44A3-8060-C14743FB1712} + {DB624D5F-CB7C-4E16-B1E2-3B368FCB5A46} = {9F005B5C-A856-4351-8C0C-47A8B785C637} {8507237C-68C3-46AD-B7DA-800791C6FDDB} = {9F005B5C-A856-4351-8C0C-47A8B785C637} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Avro.Test/SerializationBuilderExtensionsTest.cs b/src/Tests/SlimMessageBus.Host.Serialization.Avro.Test/SerializationBuilderExtensionsTest.cs index 06789396..622cc69b 100644 --- a/src/Tests/SlimMessageBus.Host.Serialization.Avro.Test/SerializationBuilderExtensionsTest.cs +++ b/src/Tests/SlimMessageBus.Host.Serialization.Avro.Test/SerializationBuilderExtensionsTest.cs @@ -3,7 +3,7 @@ public class SerializationBuilderExtensionsTest { [Fact] - public void When_AddJsonSerializer_Given_Builder_Then_ServicesRegistered() + public void When_AddAvroSerializer_Given_Builder_Then_ServicesRegistered() { // arrange var services = new ServiceCollection(); diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs new file mode 100644 index 00000000..5ae040a1 --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Helpers/SampleMessages.cs @@ -0,0 +1,5 @@ +namespace SlimMessageBus.Host.Serialization.Hybrid.Test.Helpers; + +public record SampleOne; +public record SampleTwo; +public record SampleThree; \ No newline at end of file diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs new file mode 100644 index 00000000..20cd823e --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/HybridMessageSerializerTests.cs @@ -0,0 +1,103 @@ +namespace SlimMessageBus.Host.Serialization.Hybrid.Test +{ + + public class HybridMessageSerializerTests + { + [Fact] + public void When_ConstructorReceivesARepeatedDefinition_Then_ThrowException() + { + // arrange + var mockDefaultSerializer = new Mock(); + var mockSerializer1 = new Mock(); + var mockSerializer2 = new Mock(); + var mockLogger = new Mock>(); + + var serializers = new Dictionary + { + { mockSerializer1.Object, new[] { typeof(SampleOne), typeof(SampleTwo) } }, + { mockSerializer2.Object, new[] { typeof(SampleOne) } }, + }; + + // act + var act = () => new HybridMessageSerializer(mockLogger.Object, serializers, mockDefaultSerializer.Object); + + // assert + act.Should().Throw(); + } + + [Fact] + public void When_NoDefaultSerializerIsSupplied_Then_UseFirstSpecialist() + { + // arrange + var mockDefaultSerializer = new Mock(); + var mockSerializer1 = new Mock(); + var mockLogger = new Mock>(); + + var serializers = new Dictionary + { + { mockDefaultSerializer.Object, new[] { typeof(SampleOne) } }, + { mockSerializer1.Object, new[] { typeof(SampleTwo) } }, + }; + + // act + var target = new HybridMessageSerializer(mockLogger.Object, serializers, default); + var actual = target.DefaultSerializer; + + // assert + actual.Should().BeEquivalentTo(mockDefaultSerializer.Object); + } + + [Fact] + public void When_ASpecialisedMessageIsSerialized_Then_UseSpecializedSerializer() + { + // arrange + var mockDefaultSerializer = new Mock(); + + var mockSerializer1 = new Mock(); + mockSerializer1.Setup(x => x.Serialize(typeof(SampleOne), It.IsAny())).Verifiable(Times.Once()); + + var mockSerializer2 = new Mock(); + + var mockLogger = new Mock>(); + var serializers = new Dictionary + { + { mockSerializer1.Object, new[] { typeof(SampleOne) } }, + { mockSerializer2.Object, new[] { typeof(SampleTwo) } }, + }; + + // act + var target = new HybridMessageSerializer(mockLogger.Object, serializers, mockDefaultSerializer.Object); + var _ = target.Serialize(typeof(SampleOne), new SampleOne()); + + // assert + mockSerializer1.Verify(x => x.Serialize(typeof(SampleOne), It.IsAny())); + mockSerializer2.VerifyNoOtherCalls(); + mockDefaultSerializer.VerifyNoOtherCalls(); + } + + [Fact] + public void When_AGenericMessageIsSerialized_Then_UseDefaultSerializer() + { + // arrange + var mockDefaultSerializer = new Mock(); + mockDefaultSerializer.Setup(x => x.Serialize(typeof(SampleOne), It.IsAny())).Verifiable(Times.Once()); + + var mockSerializer1 = new Mock(); + + var mockLogger = new Mock>(); + var serializers = new Dictionary + { + { mockSerializer1.Object, new[] { typeof(SampleTwo) } } + }; + + // act + var target = new HybridMessageSerializer(mockLogger.Object, serializers, mockDefaultSerializer.Object); + var _ = target.Serialize(typeof(SampleOne), new SampleOne()); + + // assert + mockDefaultSerializer.Verify(x => x.Serialize(typeof(SampleOne), It.IsAny())); + mockSerializer1.VerifyNoOtherCalls(); + } + } +} + diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SerializationBuilderExtensionsTests.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SerializationBuilderExtensionsTests.cs new file mode 100644 index 00000000..5c05d4d5 --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SerializationBuilderExtensionsTests.cs @@ -0,0 +1,86 @@ +namespace SlimMessageBus.Host.Serialization.Hybrid.Test; + +using Microsoft.Extensions.DependencyInjection; + +public class SerializationBuilderExtensionsTests +{ + private readonly IServiceCollection _services; + + public SerializationBuilderExtensionsTests() + { + // arrange + var mockLogger = new Mock>(); + + _services = new ServiceCollection(); + _services.AddSingleton(mockLogger.Object); + + // act + _services.AddSlimMessageBus(cfg => + { + cfg.AddHybridSerializer(builder => + { + builder + .AsDefault() + .RegisterSerializer(services => services.AddSingleton()); + + builder + .For(typeof(SampleTwo)) + .RegisterSerializer(services => services.AddSingleton()); + + builder + .For(typeof(SampleThree)) + .RegisterSerializer(services => services.AddSingleton()); + }); + }); + } + + [Fact] + public void When_IMessageSerializerRegistrationsAlreadyExist_Then_RemovePreviousRegistrations() + { + // assert + _services.Count(x => x.ServiceType == typeof(IMessageSerializer)).Should().Be(1); + } + + [Fact] + public void When_HybridMessageSerializerIsAdded_Then_RegisterAsIMessageSerializer() + { + // act + var serviceProvider = _services.BuildServiceProvider(); + var target = serviceProvider.GetServices().ToList(); + + // assert + target.Count.Should().Be(1); + target.Single().GetType().Should().Be(typeof(HybridMessageSerializer)); + } + + [Fact] + public void When_HybridMessageSerializerIsAdded_Then_SerializersAndTypesShouldConfigured() + { + // act + var serviceProvider = _services.BuildServiceProvider(); + var target = serviceProvider.GetService(); + + // assert + target.DefaultSerializer.GetType().Should().Be(typeof(SerializerOne)); + target.SerializerByType.Count.Should().Be(2); + target.SerializerByType.Should().ContainKey(typeof(SampleTwo)).WhoseValue.Should().BeOfType(); + target.SerializerByType.Should().ContainKey(typeof(SampleThree)).WhoseValue.Should().BeOfType(); + } + + public abstract class AbstractSerializer : IMessageSerializer + { + public object Deserialize(Type t, byte[] payload) + { + throw new NotImplementedException(); + } + + public byte[] Serialize(Type t, object message) + { + throw new NotImplementedException(); + } + } + + public class SerializerOne : AbstractSerializer { } + public class SerializerTwo : AbstractSerializer { } + public class SerializerThree : AbstractSerializer { } +} \ No newline at end of file diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj new file mode 100644 index 00000000..5006b25a --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/SlimMessageBus.Host.Serialization.Hybrid.Test.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs new file mode 100644 index 00000000..2b2513a2 --- /dev/null +++ b/src/Tests/SlimMessageBus.Host.Serialization.Hybrid.Test/Usings.cs @@ -0,0 +1,9 @@ +global using FluentAssertions; + +global using Microsoft.Extensions.Logging; + +global using Moq; + +global using SlimMessageBus.Host.Serialization.Hybrid.Test.Helpers; + +global using Xunit; diff --git a/src/Tests/SlimMessageBus.Host.Serialization.SystemTextJson.Test/JsonMessageSerializerTests.cs b/src/Tests/SlimMessageBus.Host.Serialization.SystemTextJson.Test/JsonMessageSerializerTests.cs index bc56cd3b..56809f67 100644 --- a/src/Tests/SlimMessageBus.Host.Serialization.SystemTextJson.Test/JsonMessageSerializerTests.cs +++ b/src/Tests/SlimMessageBus.Host.Serialization.SystemTextJson.Test/JsonMessageSerializerTests.cs @@ -5,16 +5,15 @@ namespace SlimMessageBus.Host.Serialization.SystemTextJson.Test; public class JsonMessageSerializerTests { public static IEnumerable Data => - new List - { - new object[] { null, null }, - new object[] { 10, 10 }, - new object[] { false, false }, - new object[] { true, true }, - new object[] { "string", "string" }, - new object[] { DateTime.Now.Date, DateTime.Now.Date }, - new object[] { Guid.Empty, "00000000-0000-0000-0000-000000000000" }, - }; + [ + [null, null], + [10, 10], + [false, false], + [true, true], + ["string", "string"], + [DateTime.Now.Date, DateTime.Now.Date], + [Guid.Empty, "00000000-0000-0000-0000-000000000000"], + ]; [Theory] [MemberData(nameof(Data))]