Nservice bus saga ordering - nservicebus

I have two saga's namely a client saga and a client billing saga.
public class ClientSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public ClientMessage ClientChangeMessage { get; set; }
public ClientContactChangeMessage ClientContactChange { get; set; }
}
public class ClientBillingSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public Guid FunderRef { get; set; }
public Guid ClientBillingRef { get; set; }
public ClientBillingMessage ClientBillingMessage { get; set; }
}
public class ClientSaga:Saga<ClientSagaState>,
IAmStartedByMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientContactChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<ClientContactChangeMessage>(s => s.ClientRef, m => m.PrimaryEntityRef);
}
public void Handle(ServiceUserChangeMessage message)
{
if (BusRefTranslator.GetLocalRef(EntityTranslationNames.ClientChange, message.EntityRef.Value) != null)
{
GetHandler<ClientChangeMessage>().Handle(message);
CompleteTheSaga();
return;
}
HandleServiceUserChangeAndDependencies(message);
//MarkAsComplete();
CompleteTheSaga();
}
}
public class ClientBillingSaga:Saga<ClientBillingSagaState>
,IHandleMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientBillingMessage>,
IHandleMessages<FunderChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<FunderChangeMessage>(s => s.FunderRef, m => m.EntityRef);
ConfigureMapping<ClientBillingMessage>(s => s.ClientBillingRef, m => m.PrimaryEntityRef);
}
public void Handle(ClientChangeMessage message)
{
var state = this.Data;
if (state.ClientBillingMessage != null)
{
Handle(state.ClientBillingMessage);
}
}
public void Handle(CareSysInvoiceLineInsertMessage message)
{
//First check for the funder
//If funder is not there store the message in saga
//If funder is there then check for client
//If client is not there then put the message in saga
// if funder and client are there then execute the message delete the saga
}
}
Here is the scenario:
1)If i receive a ClientBillingMessage whose client and funder are not there , i store this message in saga.
2)If now the funder message comes then it gets executed , but the saga still persists as it is waiting for client message
3)Now if the client message comes it runs the ClientBillingSaga's client message handler first hence the already existing ClientBillingSaga still persists and after that it goes and executes the handler inside the ClientSaga for Client message.
My question: Is there any way i can order the execution of these two saga. I found ISpecifyMessageHandlerOrdering interface which deals with handling of messages but i don't think i can use it for saga.

Sagas are meant to help with synchronising events over time.
You can collect the data you need form the messages you handled in your saga state and once you you have handled all then proceed to send a command/publish an event so the business logic could be processed by a handler.
Note: The actual work (business logic and domain data manipulation) should be done in a separate handler (to comply with SRP and unit of work).
hope this helps

I think you should not be ordering the execution of two Sagas. Saga should be autonomous acting purely on the messages it receives, has an internal state and either sends out messages or complete. If you really want to interact with another Saga, you should be using messages(commands/events).

Related

Entity Framework Core - one-to-many but parent also has navigation property to a single child?

I currently have a working one-to-many relationship between the entities 'Conversation' and 'Message', where a conversation can have multiple messages.
This works fine:
public class Conversation
{
public long ID { get; set; }
}
public class Message : IEntity
{
public virtual Conversation Conversation { get; set; }
public long ConversationID { get; set; }
public long ID { get; set; }
}
However, I am trying to add a navigation property to the 'Conversation' class called 'LastMessage' which will keep track of the last message record that was created:
public class Conversation
{
public long ID { get; set; }
public virtual Message LastMessage { get; set; }
public long LastMessageID { get; set; }
}
When I try to apply the above, I get the error
System.InvalidOperationException: The child/dependent side could not
be determined for the one-to-one relationship between
'Conversation.LastMessage' and 'Message.Conversation'.
How do I maintain a one-to-many relationship between 'Conversation' and 'Message', but ALSO add a navigation property in the 'Conversation' class that navigates to a single 'Message' record?
If conversation can have several messages it is called one-to-many relations.
You have to fix the tables:
public class Conversation
{
[Key]
public long ID { get; set; }
[InverseProperty(nameof(Message.Conversation))]
public virtual ICollection<Message> Messages { get; set; }
}
public class Message
{
[Key]
public long ID { get; set; }
public long ConversationID { get; set; }
[ForeignKey(nameof(ConversionId))]
[InverseProperty("Messages")]
public virtual Conversation Conversation { get; set; }
}
After trying all sorts of Data Annotations and Fluent API nonsense, the cleanest solution I could come up with turned out to be very simple which requires neither. It only requires adding a 'private' constructor to the Conversation class (or a 'protected' one if you're using Lazy Loading) into which your 'DbContext' object is injected. Just set up your 'Conversation' and 'Message' classes as a normal one-to-many relationship, and with your database context now available from within the 'Conversation' entity, you can make 'LastMessage' simply return a query from the database using the Find() method. The Find() method also makes use of caching, so if you call the getter more than once, it will only make one trip to the database.
Here is the documentation on this ability: https://learn.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services
Note: the 'LastMessage' property is read-only. To modify it, set the 'LastMessageID' property.
class Conversation
{
public Conversation() { }
private MyDbContext Context { get; set; }
// make the following constructor 'protected' if you're using Lazy Loading
// if not, make it 'private'
protected Conversation(MyDbContext Context) { this.Context = Context; }
public int ID { get; set; }
public int LastMessageID { get; set; }
public Message LastMessage { get { return Context.Messages.Find(LastMessageID); } }
}
class Message
{
public int ID { get; set; }
public int ConversationID { get; set; }
public virtual Conversation Conversation { get; set; }
}

NServiceBus saga Unique attribute

I have a saga data class with one property marked by Unique attribute. However, this didn't prevent NServiceBus from creating several sagas with identical values in this field.
Here is my data class:
public class ModuleAliveSagaData : ContainSagaData
{
[Unique]
public string ModuleId { get; set; }
public string Endpoint { get; set; }
public string Module { get; set; }
public DateTime LastCheck { get; set; }
public bool Warning { get; set; }
public bool Error { get; set; }
}
Here is the mapping:
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ModuleAliveMessage>(m => m.Id).ToSaga(s => s.ModuleId);
}
Here is how data gets its values:
public void Handle(ModuleStartedMessage message)
{
Log.InfoFormat("Module {0} started on {1} at {2}", message.ModuleName, message.Endpoint, message.Timestamp);
Data.ModuleId = message.Id;
Data.Endpoint = message.Endpoint;
Data.Module = message.ModuleName;
Data.LastCheck = DateTime.Now;
Data.Warning = false;
Bus.SendLocal(new SendNotification
{
Subject = string.Format("Module {0} is online at {1}", Data.Module, Data.Endpoint)
});
RequestTimeout<ModuleCheckTimeout>(TimeSpan.FromMinutes(5));
Bus.Publish(new ModuleActivated
{
Endpoint = message.Endpoint,
Module = message.ModuleName
});
}
And here is what I see in the saga persistence table (Azure table storage):
Does it suppose to work like this or may be I am missing something?
Yves wrote this in comments, basically it is the proper answer:
Azure storage cannot check for uniquess besides the partitionkey/rowkey pair, so that attribute is ignored. If you need uniqueness you will have to consider another storage techology. PS: this is a known limitation of the underlying storage: http://github.com/Particular/NServiceBus.Azure/issues/21

NServiceBus, Could not find a saga for the message type

My saga below is not handling the ValidateRegistration and ValidateRegistration commands. I see the "Could not find a saga for the message type Registrations.Messages.ValidateRegistration with id ..." message.
Is my configuration to find saga not correct? Please help!
Thanks
PS: I am using the generic host in the registration process and I am using NServiceBus.Lite profile.
public class EndpointConfig : IConfigureThisEndpoint, AsA_Publisher, IWantCustomInitialization
{
#region Implementation of IWantCustomInitialization
public void Init()
{
var kernel = new StandardKernel();
kernel.Load(new BackendModule());
//Configure.Instance.Configurer.ConfigureProperty<RegistrationSaga>(x => x.Factory, kernel.Get<IAggregateRootFactory>());
Configure.With().NinjectBuilder(kernel);
}
#endregion
}
public class RegistrationSagaData : IContainSagaData
{
#region Implementation of ISagaEntity
public virtual Guid Id { get; set; }
public virtual string Originator { get; set; }
public virtual string OriginalMessageId { get; set; }
public virtual RegistrationID RegistrationID { get; set; }
public virtual bool IsValidated { get; set; }
public virtual string RegistrationType { get; set; }
#endregion
}
public class RegistrationSaga : Saga<RegistrationSagaData>,
IAmStartedByMessages<StartRegistration>,
IHandleMessages<ValidateRegistration>,
IHandleMessages<CancelRegistration>
{
public RegistrationFactory Factory { get; set; }
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<StartRegistration>(data => data.RegistrationID, registration => registration.ID);
ConfigureMapping<ValidateRegistration>(data => data.RegistrationID, registration => registration.ID);
ConfigureMapping<CancelRegistration>(data => data.RegistrationID, registration => registration.ID);
}
#region Implementation of IMessageHandler<StartRegistration>
public void Handle(StartRegistration message)
{
Data.IsValidated = false;
Data.RegistrationType = message.RegistrationType;
Bus.SendLocal(new CreateRegistration
{
RegistrationType = message.RegistrationType,
ID = message.ID
});
Console.WriteLine("======> handled StartRegistration");
}
#endregion
#region Implementation of IMessageHandler<ValidateRegistration>
public void Handle(ValidateRegistration message)
{
MarkAsComplete();
Console.WriteLine("======> handled ValidateRegistration");
}
#endregion
#region Implementation of IMessageHandler<CancelRegistration>
public void Handle(CancelRegistration message)
{
Console.WriteLine("======> handled CancelRegistration");
MarkAsComplete();
}
#endregion
}
Your handler for StartRegistration is not adding RegistrationID to the Saga's Data. So your override of ConfigureHowToFindSaga is mapped on a property that's has no value when the other commands are handled.
Just today I heard that Ninject doesn't handle setter injection out of the box - try switching it over to constructor injection and see if that works.

RavenDB Saga Persister not persisting saga entity

We're using RavenDB saga storage, but the saga data isn't being persisted after the starting message handler. I only see a handful of subscription documents in the database. I'm not sure what to check next. Ideas?
I have a saga:
public class BuyerWaitingOnDocumentsDistributor :
Saga<BuyerDocumentDistributorData>,
IAmStartedByMessages<DocumentVersionRequiresBuyerSignature>
{
public void Handle(DocumentVersionRequiresEmployeeSignature message)
{
Data.DocumentVersionId = message.DocumentVersionId.Value;
// Business logic goes here
}
// Other handlers & methods here
}
and saga entity:
public class BuyerDocumentDistributorData : IContainSagaData
{
public virtual Guid Id { get; set; }
public virtual string Originator { get; set; }
public virtual string OriginalMessageId { get; set; }
public virtual Guid DocumentVersionId { get; set; }
public virtual EmployeeId[] AuthorizedToSign { get; set; }
}
and saga finder:
public class BuyerWaitingOnDocumentsDistributorSagaFinder :
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionRequiresBuyerSignature>,
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionSignedByBuyer>,
IFindSagas<BuyerDocumentDistributorData>.Using<DocumentVersionNoLongerRequiresSignature>
{
private static readonly ILog Log = LogManager.GetLogger(typeof (BuyerWaitingOnDocumentsDistributorSagaFinder));
public ISagaPersister Persister { get; set; }
public BuyerDocumentDistributorData FindBy(DocumentVersionRequiresBuyerSignature message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
public BuyerDocumentDistributorData FindBy(DocumentVersionSignedByBuyer message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
public BuyerDocumentDistributorData FindBy(DocumentVersionNoLongerRequiresSignature message)
{
return Persister.Get<BuyerDocumentDistributorData>("DocumentVersionId", message.DocumentVersionId.Value);
}
}
I've also asked this question on the NServiceBus Yahoo group: http://tech.groups.yahoo.com/group/nservicebus/message/13265
You did not give your endpointconfig, so I'm unsure which container you are using.
We experience the same (saga not getting persisted) when using the NinjectBuilder. Using the DefaultBuilder works ok.

Polymorphic subscriptions in NServiceBus works only for interfaces?

The simplest way to follow this explanation is a NServiceBus Pub/Sub sample which contains example of "polymorphic subscriptions" (Subscriber2).
Messages: (without changes)
public class EventMessage : IEvent
{
public Guid EventId { get; set; }
public DateTime? Time { get; set; }
public TimeSpan Duration { get; set; }
}
public interface IEvent : IMessage
{
Guid EventId { get; set; }
DateTime? Time { get; set; }
TimeSpan Duration { get; set; }
}
Handler: (without changes)
public class EventMessageHandler : IHandleMessages<IEvent>
{
public void Handle(IEvent message)
{
// ...
}
}
This handler will receive both IEvent and EventMessage messages. But if I will make IEvent a class...
public class IEvent : IMessage
{
Guid EventId { get; set; }
DateTime? Time { get; set; }
TimeSpan Duration { get; set; }
}
...then i will not be able to receive EventMessage but will receive IEvent as before
So i found such simple rule: if you use interface in IHandleMessages<> - it will work, if class - it will not work. Currently i have all messages as classes and i would like to subscribe to parent class message in order to receive all child class messages.
Is it intended behavior?
This is all by design in order to enable multiple inheritance. The reasons to do so are detailed here. Publicly available events between Business Components are recommended to be modeled as interfaces, commands within a business component are recommended to be modeled as classes. Sounds like you want this behaviour, I would switch over to interfaces.