Question

I have two types of events :

  • PersonChanged
  • PersonAddressChanged.

Both of them are being published also when a new Person (and a new address) is created (kind of create or update).

When a new person is created two events are published: PersonChanged and PersonAddressChanged (in that order). However, because NServiceBus is asynchronous they can be handled in any order. And when an address is changed (for an existing person) there is no PersonChanged event, only PersonAddressChanged event.

I want to write a handler for PersonAddressChanged event which will :

  1. Check if the person is in database
  2. If yes then just make an update
  3. If no then start saga and wait for PersonChanged event (assuming it is a new person)

And in PersonChanged event I need to insert the person to database, find the saga and run the handler for PersonAddressChanged once again.

Can I achieve this with NServiceBus Sagas? I cannot assume that the message processing should be in order PersonChanged → PersonAddressChanged because sometimes there will be no PersonChanged event for any particular address change.

Was it helpful?

Solution

You could do this with sagas, yes, although you probably shouldn't. First of, having the same event for both creation and updating the person loses semantic information in the event (which is what you are trying to recreate by checking for the user in the database). You also expose yourself to a lot of potential race conditions, and need to think about how to handle them all. My suggestion would be to rework your message flow, that will probably make your current problem go away.

But, if you want to try it anyway, you'd do something like this: (not tested or compiled, just conceptual code)

public class MySaga : Saga<MySagaDataType>, IAmStartedByMessage<PersonChanged>, IAmStartedByMessage<PersonAddressChanged> {
    public override void ConfigureHowToFindSaga() {
        // How to correlate PersonChanged and PersonAddressChanged messages
    }
    public void Handle<PersonChanged>(PersonChanged msg) {
        Insert(msg);
        if(Data.PendingAddressChange != null) 
            UpdateDatabase(msg);
        MarkAsComplete();
    }
    public void Handle<PersonAddressChanged>(PersonAddressChanged msg) {
        if(IsInDatabase(msg.Person)) {
            UpdateDatabase(msg);
            MarkAsComplete();
        }
        else {
            Data.PendingAddressChange = msg;
        }
    }
}
public class MySagaDataType : IContainSagaData {
    public PersonAddressChanged PendingAddressChange { get; set; }
}

OTHER TIPS

I'd agree with @carlpett's statement about rethinking some of the semantics around your event.

I'd also suggest you consider a different solution than a saga here.

What about if your PersonAddressChanged message handler, when it sees that there is no current person in the database, just goes and creates one with the address its got?

If PersonAddressChanged events are only sent for an existing person, I would not bother with sagas at all.

Instead, I'd rely on retry logic to process messages that arrive out of order.

Any time the PersonAddressChanged is processed, it could assume that a person record exists. If it doesn't (ie messages are being processed out of order) the handler throws an exception and is retried later.

By the time it is reprocessed again, the PersonChanged event has been processed and all is good.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top