charlessolar / Aggregates.NET

.NET event sourced domain driven design model via NServiceBus and GetEventStore

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sagas Doesn't Work

ibraheem-ghabash opened this issue · comments

Hi Charles,

Trying to use sagas with persistent storage but it doesn't work, so I created a simple project where I tried using both scenarios: using Aggregates.NET's Apply and using NServiceBus's native context.Publish, in the native scenario the saga works fine, but with Apply scenario the saga never gets executed (same configurations in both scenarios)

Here you can find the project I created for this test.

Thank you

@charlessolar Hi Charels,
Any idea how to fix this ?
The way you mentioned here:
https://github.com/charlessolar/Aggregates.NET/tree/master/samples/5.%20Command%20Saga
Is not the same as NServiceBus uses, is it?

Looking at this briefly it seems the sample app is using NSBs sagas yes.

Agg.net sagas are meant to be described as a series of commands like the example you linked.
Screenshot_20210704-200457

I'll look deeper into this tomorrow!

Thanks @charlessolar,
Our use case is, we need to have series of commands but the handlers distributed on multiple services, I think the sample in Agg.Net is meant for commands handled in the same service.

Hi @charlessolar

I have just pushed a new change to my original repository with applying the solution you proposed (you can find the exact code here )

But we still have an exception (short message):
System.Data.SqlClient.SqlException (0x80131904): Invalid object name 'dbo.Sales_CommandSagaHandler'.
at System.Data.SqlClient.SqlCommand.<>c.b__126_0(Task`1 result)

I believe that exception is from your library - are you using Sql Transport by chance?

There isn't anything wrong with how you tried to do the saga before and for your design it may be preferable.

Can you setup your example based off of the helloworld sample? https://github.com/charlessolar/Aggregates.NET/tree/master/samples/1.%20HelloWorld

That way I can run it myself and see why your saga is not properly running

@charlessolar I was able to figure out what's missing, I need directly to add a reference for the NServiceBus.Persistence.Sql nuget package in the project that's handling the saga and triggering it.
But now a new issue is showing:

Cannot set the NServiceBus.ConversationId header to '57bd6679-c25a-4113-a604-ad610158515e' as it cannot override the incoming header value ('57bd6679-c25a-4113-a604-ad610158515e').

Here's the detailed message:

2021-07-10 23:53:49.882 ERROR Moving message '1fab3ff2-f55b-4e86-a583-ad6101585158' to the error queue 'error' because processing failed due to an exception:
System.Exception: Cannot set the NServiceBus.ConversationId header to '57bd6679-c25a-4113-a604-ad610158515e' as it cannot override the incoming header value ('57bd6679-c25a-4113-a604-ad610158515e').
at NServiceBus.AttachCausationHeadersBehavior.SetConversationIdHeader(IOutgoingLogicalMessageContext context, IncomingMessage incomingMessage)
at NServiceBus.AttachCausationHeadersBehavior.Invoke(IOutgoingLogicalMessageContext context, Func2 next) at (Closure2 , IOutgoingLogicalMessageContext )
at NServiceBus.Pipeline.Behavior1.<>c__DisplayClass0_0.<Invoke>b__0() at Aggregates.Internal.MutateOutgoing.Invoke(IOutgoingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\MutateOutgoing.cs:line 56
at NServiceBus.Pipeline.Behavior1.Invoke(TContext context, Func2 next)
at (Closure2 , IOutgoingLogicalMessageContext ) at NServiceBus.MutateOutgoingMessageBehavior.Invoke(IOutgoingLogicalMessageContext context, Func2 next)
at (Closure2 , IOutgoingLogicalMessageContext ) at NServiceBus.MulticastPublishConnector.Invoke(IOutgoingPublishContext context, Func2 stage)
at (Closure2 , IOutgoingPublishContext ) at NServiceBus.EnforcePublishBestPracticesBehavior.Invoke(IOutgoingPublishContext context, Func2 next)
at (Closure2 , IOutgoingPublishContext ) at NServiceBus.Pipeline1.Invoke(TContext context)
at NServiceBus.MessageOperations.Publish(IBehaviorContext context, Type messageType, Object message, PublishOptions options)
at NServiceBus.MessageOperations.Publish(IBehaviorContext context, Object message, PublishOptions options)
at NServiceBus.IncomingContext.Publish(Object message, PublishOptions options)
at NServiceBus.IPipelineContextExtensions.Publish(IPipelineContext context, Object message)
at TestProj.MyHandler.Handle(MyClass message, IMessageHandlerContext context) in C:\Projects\TestProj\MyHandler.cs:line 63
at Aggregates.Internal.BulkInvokeHandlerTerminator.Terminate(IInvokeHandlerContext context) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\BulkInvokeHandlerTerminator.cs:line 88
at NServiceBus.SagaPersistenceBehavior.Invoke(IInvokeHandlerContext context, Func2 next) at NServiceBus.LoadHandlersConnector.Invoke(IIncomingLogicalMessageContext context, Func2 stage)
at CurrentSessionBehavior.Invoke(IIncomingLogicalMessageContext context, Func1 next) in /_/src/SqlPersistence/SynchronizedStorage/CurrentSessionBehavior.cs:line 19 at NServiceBus.ScheduledTaskHandlingBehavior.Invoke(IIncomingLogicalMessageContext context, Func2 next)
at NServiceBus.InvokeSagaNotFoundBehavior.Invoke(IIncomingLogicalMessageContext context, Func2 next) at Aggregates.Internal.LogContextProviderBehaviour.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\LogContextProviderBehaviour.cs:line 39
at Aggregates.Internal.LocalMessageUnpack.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\LocalMessageUnpack.cs:line 110 at Aggregates.Internal.UnitOfWorkExecutor.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\UnitOfWorkExecutor.cs:line 90
at Aggregates.Internal.UnitOfWorkExecutor.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\UnitOfWorkExecutor.cs:line 129 at ICS.Shared.Infrastructure.Behaviours.CommandExceptionBehaviour.Invoke(IIncomingLogicalMessageContext context, Func1 next)
at Aggregates.Internal.ExceptionRejector.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\ExceptionRejector.cs:line 49 at Aggregates.Internal.ExceptionRejector.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\ExceptionRejector.cs:line 99
at Aggregates.Internal.SagaBehaviour.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\SagaBehaviour.cs:line 32 at Aggregates.Internal.CommandAcceptor.Invoke(IIncomingLogicalMessageContext context, Func1 next) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\CommandAcceptor.cs:line 41
at NServiceBus.DeserializeMessageConnector.Invoke(IIncomingPhysicalMessageContext context, Func2 stage) at NServiceBus.InvokeAuditPipelineBehavior.Invoke(IIncomingPhysicalMessageContext context, Func2 next)
at NServiceBus.ProcessingStatisticsBehavior.Invoke(IIncomingPhysicalMessageContext context, Func2 next) at NServiceBus.TransportReceiveToPhysicalMessageConnector.Invoke(ITransportReceiveContext context, Func2 next)
at NServiceBus.MainPipelineExecutor.Invoke(MessageContext messageContext)
at Aggregates.Internal.Dispatcher.SendLocal(IFullMessage message, IDictionary`2 headers) in C:\projects\aggregates-net\src\Aggregates.NET.NServiceBus\Internal\Dispatcher.cs:line 117
Exception details:
Message ID: 1fab3ff2-f55b-4e86-a583-ad6101585158

Hi @charlessolar,
I needed to go around the issue of saga I mentioned before, so I did the following:
"Aggregates.Configuration.Build":

var config = new EndpointConfiguration("EndpointName");
endpointConfig.BusConfiguration.Pipeline.Remove("MutateOutgoing");

I'm trying now to do the following:
EndpointA -- CommandA To (received in B) -->
EndpointB -- EventB triggered from the "Apply" method To (received in A) -->
EndpointA (Here the event won't be received in A)

For some reason I'm not receiving the last event in EndpointA.

Hey @AbdoDabbas

Looking over the issue from above its actually a small bug / possible issue with NSB itself.
Its complaining that it can't change a header to a value that is the current value...

Removing the MutateOutgoing step shouldn't stop this issue though.. since its the NServiceBus.AttachCausationHeadersBehavior step that causes it. Most likely some header Agg.Net is setting in Mutate is redundant so I'll open a new ticket for this.

Would be nice if NSB checked if the header was there and equal before throwing but thats not important.

As for your question does EndpointA connect to the eventstore? Check the eventstore UI to see if theres a projection setup for your endpoint which contains the event you're interested in

Regarding the example, yes both endpoints are connected to EventStore, I checked the UI and there's a projection for EventB on EndpointB.

I'm still using Aggregates.NET version 0.15.67.918, upgrading will be hard for me now.

EventB (which is generated by "Apply") is not even registered in EventStore, I can't see it, not even in NserviceBus audit app (ServiceInsight, which reads from RabbitMQ and shows the events and commands linked to each other), it looks like it disappeared.

Any suggestions?

Can you generate a debug log file? Agg.net uses LibLog so any logger supported by LibLog will receive Agg.net logs.

Once an event is applied the only way its wont be committed to the store would be if theres an error processing the command.

Any sample how to do that? I checked the Saga sample, and added Serilog, no errors showed at all, just the regular warning that I should use the "Versioned" attribute.

If you're using serilog for logging then thats the way to go.

Check Hello World

Log.Logger = new LoggerConfiguration()

And add a line for logging to a file .WriteTo.File("C:/Logs/log.txt") something like that

All that debug info should help determine why your event is not getting to the eventstore

I added a logger and used verbose level.
Nothing showed as error, all are just debug and info that the event I'm watching is there and linked to a handler and all good.
No info that the event triggered at all.

Let me re-describe the sample with the new names I just copied and pasted to prepare the example:
ClientUI -> Command PlaceOrder -> Sales.
Sales -> Apply event OrderPlaced -> Billing.
Billing -> Apply event ToOrderEndpointEvent -> Sales.

ToOrderEndpointEvent is the event that's not shown in the logs and not reaching the Sales endpoint.

Here're the logs:
billing-logs.txt
sales-logs.txt

Ok I think I know what the problem is then from your description.

Event handlers cannot write events to the store. Only commands can generate events which are written to the eventstore.

Your event handlers typically do not have access to the eventstore streams only to the projected events.

For whatever purpose you want Billing to communicate back to Sales you need to send a command instead.

Try this:

ClientUI -> Command PlaceOrder -> Sales
Sales -> Apply event OrderPlaced -> Billing, Warehouse
Billing -> Command FinalizeOrder -> Sales
Sales -> Apply event OrderFinalized -> Warehouse
Sales -> Command ReleaseOrder -> Warehouse
Warehouse -> Apply event OrderShipped -> Sales

Something like that to represent a order flow. I added Warehouse in there to show how different endpoints can care about different events and how to process commands vs events.

Have you seen the eShop example that I made? https://github.com/charlessolar/eShopOnContainersDDD

It contains a pretty good starting point for a "shop" type app

I want to do this as a workaround because the Saga as mentioned in NSB documentations didn't work for me (but not using the context to publish events, I thought I can depend on ES events to use Saga).
So the idea is to do things in specific order.

I will check the eShop sample and try what you suggested.

Thanks.

Saga's and the header issue were upgraded and fixed in v0.17!