Eventuous / eventuous

Event Sourcing library for .NET
https://eventuous.dev
Apache License 2.0
447 stars 71 forks source link

Cannot use more than one gateway transformation #250

Closed alex91352 closed 3 months ago

alex91352 commented 1 year ago

The method GatewayRegistrations.AddGateway<TSubscription, TSubscriptionOptions, TProducer, TProduceOptions, TTransform> registers a GatewayHandler in the SubscriptionBuilder.

Since AddGateway passes the GatewayHandler to AddEventHandler as an IEventHandler, and AddEventHandler tries to register the handler in the service collection as a singleton, this means that only the first GatewayHandler is registered in the service collection. As a result, the first GatewayHandler (and its transformation) is used for all gateway subscriptions.

https://github.com/Eventuous/eventuous/blob/ed2c62edfc6f4b3d1c02e176be2f8d7e6a42cd6c/src/Gateway/src/Eventuous.Gateway/Registrations/GatewayRegistrations.cs#L127-L157

https://github.com/Eventuous/eventuous/blob/ed2c62edfc6f4b3d1c02e176be2f8d7e6a42cd6c/src/Core/src/Eventuous.Subscriptions/Registrations/SubscriptionBuilder.cs#L47-L53

For example, if I register two gateway subscriptions:

services.AddGateway<StreamSubscription, StreamSubscriptionOptions, EventStoreProducer, EventStoreProduceOptions, SellOrderTransformation>("SellOrders", ...);
services.AddGateway<StreamSubscription, StreamSubscriptionOptions, EventStoreProducer, EventStoreProduceOptions, BuyOrderTransformation>("BuyOrders", ...);

then both subscriptions will use SellOrderTransformation.

alexeyzimarev commented 1 year ago

Yeah, that can actually be fixed the same way as event handlers and checkpoint stores are attached to subscriptions. The problem is that gateways DI integration is way less comprehensive than DI stuff for subs as it took me weeks to figure things out there. It definitely worth spending time and figure better DI support for gateways as well.

gius commented 4 months ago

Hi, any update on this? How do you use multiple gateways? Or do you use just a single gateway for all cases?

A quick workaround we use is to create a custom sub class of TProduceOptions per subscription.

alexeyzimarev commented 3 months ago

Can you post the complete code for those?

services.AddGateway<StreamSubscription, StreamSubscriptionOptions, EventStoreProducer, EventStoreProduceOptions, SellOrderTransformation>("SellOrders", ...);
services.AddGateway<StreamSubscription, StreamSubscriptionOptions, EventStoreProducer, EventStoreProduceOptions, BuyOrderTransformation>("BuyOrders", ...);
alexeyzimarev commented 3 months ago

I managed to create a quick fix. It won't work with functions but it will work with transformer classes. Here's the registration function (it existed before):

    public static IServiceCollection AddGateway<TSubscription, TSubscriptionOptions, TProducer, TProduceOptions, TTransform>(
            this IServiceCollection                                           services,
            string                                                            subscriptionId,
            Action<TSubscriptionOptions>?                                     configureSubscription = null,
            Action<SubscriptionBuilder<TSubscription, TSubscriptionOptions>>? configureBuilder      = null,
            bool                                                              awaitProduce          = true
        )
        where TSubscription : EventSubscription<TSubscriptionOptions>
        where TProducer : class, IEventProducer<TProduceOptions>
        where TProduceOptions : class
        where TTransform : class, IGatewayTransform<TProduceOptions>
        where TSubscriptionOptions : SubscriptionOptions

I will create a PR to support multiple transformers there, but it still has limitations. For example, the same transformer can't be used in more than gateway as the registration function I mentioned above adds a singleton GatewayHandler<TTransform, TProduceOptions> so they must be unique with generic constraints to avoid the same issue.

I need to add a test to the PR before I merge it.

I also think that the registration code for subscriptions can be simplified further as most of the complexity there is to overcome missing keyed dependencies, but this is now solved.

alexeyzimarev commented 3 months ago

Ok, I managed to change the registration code to use keyed dependencies. It's all a bit simpler now and handlers are completely independent. So, everything should work: functions, classes, whatever.