-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Host.Serialization] HybridMessageSerializer builder does not resolve specialised #238
Comments
Yes, I agree. It's something I was also thinking about. Thanks for looking into this. One minor ask, please raise the discussion first in the issue before starting to implement the PR feature. Nevertheless, good idea! |
…Provider Signed-off-by: Richard Pringle <[email protected]>
That's a perfectly reasonable request. It started off as a very minor extension method change that I thought I would just start the conversation with but snowballed fairly rapidly :/
It definitely has a smell about it. One solution could be to allow for registration to happen in the HybridSerializerOptionsBuilder itself instead of beforehand. Something like services
.AddSlimMessageBus(mbb =>
{
mbb
.AddHybridSerializer(
o => {
o..AddJsonSerializer(options)
.AsDefault();
o..AddAvroSerializer()
.For(typeof(Message1), typeof(Message2));
o..AddGoogleProtobufSerializer()
.For(typeof(Message3));
})
...
} In order to facilitate this, an interface could be added to SlimMessageBus.Host.Serialization which would need an implementation in each of the serializers i.e // In SlimMessageBus.Host.Serialization
public interface IHybridBuilder
{
IHybridBuilderOptions RegisterSerializer<TSerializer>(Action<IServiceCollection> services = null) where TSerializer : IMessageSerializer;
}
public interface IHybridBuilderOptions
{
IHybridBuilder For(params Type[] types);
IHybridBuilder AsDefault();
}
// In each of the serializer assemblies
public static class MessageBusBuilderExtensions
{
// Original builder
public static MessageBusBuilder AddJsonSerializer(this MessageBusBuilder mbb, JsonSerializerOptions options = null)
{
...
}
// An overload for hybrid
public static IHybridBuilderOptions AddJsonSerializer(this IHybridBuilder builder, JsonSerializerOptions options = null)
{
return builder.RegisterSerializer<JsonMessageSerializer>(services =>
{
services.TryAddSingleton(svp => new JsonMessageSerializer(options ?? svp.GetService<JsonSerializerOptions>()));
});
}
} By making the services parameter in IHybridBuilder.RegisterSerializer optional, it could be used for both DI registration or to add an already registered serializer to the hybrid builder (third party based on previous implementations). The hybrid builder would still need to replace any previous IMessageSerializer registrations and, while the implementation would require a migration from previous implementations, it could be a more succinct approach. It would also require a bump to the SlimMessageBus.Host.Serialization nuget package which would be a consideration. |
…Provider Signed-off-by: Richard Pringle <[email protected]>
…Provider Signed-off-by: Richard Pringle <[email protected]>
@EtherZa building on your idea, I am proposing to extract an This could allow achieving your provided sample - still, be backwards compatible and have a nice developer API to configure this: services
.AddSlimMessageBus(mbb =>
{
mbb
.AddHybridSerializer(
o => {
o.AddJsonSerializer(options)
.AsDefault();
o.AddAvroSerializer()
.For(typeof(Message1), typeof(Message2));
o.AddGoogleProtobufSerializer()
.For(typeof(Message3));
})
...
} Could you accommodate your changes on top of the proposed PR? |
@zarusz It's a nice suggestion but would leave two issues that would need to be overcome:
services
.AddSlimMessageBus(mbb =>
{
mbb
.AddHybridSerializer(
o => {
o.AsDefault();
o.For(typeof(Message1), typeof(Message2));
})
...
} Unless, of course, I am missing something? :) |
@EtherZa so I was thinking that the Then Now, I think experimenting with the code might highlight issues in my thinking but I believe this could work ;) |
…Provider Signed-off-by: Richard Pringle <[email protected]>
@zarusz We are thinking about the same implementation (o is I have updated the PR with this implementation, but it is a little crude. The implementation passes an empty When faced with a scenario like below, the logic will completely fall apart as the type cannot be determined without invoking the delegate. public static TBuilder AddAnotherSerializer<TBuilder>(this TBuilder builder, AnotherSerialzierOptions options)
where TBuilder : ISerializationBuilder
{
builder.PostConfigurationActions.Add(services =>
{
services.TryAddSingleton<IMessageSerializer>(svp => ActivatorUtilities.CreateInstance<AnotherSerializer>(svp, options));
});
return builder;
} Further to this, it also plays havoc on intellisense which will complicate the development experience. The builder will be in the incorrect state for some of the options ( The solution does work but is somewhat fragile, relying on the current two-step pattern being followed. I will submit an alternative PR with the original proposal as well, and perhaps when seen side by side, there will be a clearer path ahead. |
…Provider Signed-off-by: Richard Pringle <[email protected]>
…Provider Signed-off-by: Richard Pringle <[email protected]>
@zarusz I have added an alternative PR using the specialised builder proposal for review. If you still prefer the common implementation, please let me know. There are a few more unit tests that will need to be added if you want to follow this path. |
…Provider Signed-off-by: Richard Pringle <[email protected]>
@EtherZa thanks for your patience on this, and for going back and forth with me. I merged the PR #241 of mine to rule out some moving parts from the conversation as I think it won't hurt and might be used in the overall end-state solution. Apologies if it causes conflicts now in the PR. Regarding the alternative PR #242 am not a fan of adding hybrid specialized config elements into Going back to this idea. I think we could still improve the intellisense in this last approach presented in your original #239, by perhaps starting with AsDefault/For first: .AddHybridSerializer((HybridSerializerBuilder o) =>
{
o.AsDefault().AddJsonSerializer(); // We start with first AsDefault returns its own instance of ChildHybridSerializerBuilder
o.For(typeof(CustomerEvent)).AddAvroSerizaliser();
});
class HybridSerializerBuilder {
ChildHybridSerializerBuilder AsDefault() {}
ChildHybridSerializerBuilder For(params Type[] types) {}
}
class ChildHybridSerializerBuilder : ISerializationBuilder {
// this does the post actions visitation, only takes the builders and action delegates into account that actually configured the ServiceCollection
} So essentially, the difference is that the AsDefault/For needs to be used first which then create their own builders Let me know. I am happy to move this one forward. |
@zarusz Absolutely no issue here. It's worth getting it right. Moving the Would you be happy to take a bite from PR #242 and add a method to the The implementation in public interface ISerializationBuilder
{
void RegisterSerializer<TMessageSerializer>(Action<IServiceProvider> services) where TMessageSerializer : IMessageSerializer;
}
public class MessageBusBuilder : ISerializationBuilder
{
public void RegisterSerializer<TMessageSerializer>(Action<IServiceProvider> services) where TMessageSerializer : IMessageSerializer
{
this.PostConfigurationActions.Add(services);
services.TryAddSingleton<IMessageSerializer>(svp => svp.GetRequiredService<JsonMessageSerializer>());
}
}
public class ChildHybridSerializerBuilder : ISerializationBuilder
{
public void RegisterSerializer<TMessageSerializer>(Action<IServiceProvider> services) where TMessageSerializer : IMessageSerializer
{
this.PostConfigurationActions.Add(services);
// do something with typeof(TMessageSerializer)
}
}
// MessageBusBuilderExtensions
public static MessageBusBuilder AddJsonSerializer(this MessageBusBuilder mbb, JsonSerializerOptions options = null)
{
mbb.PostConfigurationActions.Add(services =>
{
services.TryAddSingleton(svp => new JsonMessageSerializer(options ?? svp.GetService<JsonSerializerOptions>()));
// Removed (added by ISerializationBuilder where necessary)
// services.TryAddSingleton<IMessageSerializer>(svp => svp.GetRequiredService<JsonMessageSerializer>());
});
return mbb;
} The implementation would solve most of the issues but would leave the fluent building in var hsb = new HybridSerializerBuilder();
hsb
.AsDefault()
.AddSampleSerializer()
.RegisterSerializer<SampleSerializer>(sp => { });
hsb
.For(typeof(string))
.AddSampleSerializer()
.AddSampleSerializer();
// same issue (cannot be avoided at compile time)
hsb
.AsDefault()
.AddSampleSerializer();
hsb
.AsDefault()
.RegisterSerializer<SampleSerializer>(sp => { });
hsb
.For(typeof(string))
.AddSampleSerializer();
hsb
.For(typeof(string))
.AddSampleSerializer(); |
…Provider Signed-off-by: Richard Pringle <[email protected]>
Signed-off-by: Richard Pringle <[email protected]>
Signed-off-by: Richard Pringle <[email protected]>
Signed-off-by: Richard Pringle <[email protected]>
When adding the hybrid message serializer, instances of the specialised serializers need to be supplied which complicates the dependency management of those serializers.
It would be better if instances of the specialised message serializers were resolved from the DI container instead.
The text was updated successfully, but these errors were encountered: