2016-10-05 1 views
1

J'ai une saga de contrôleur qui avait l'habitude d'avoir une étape commençant un processus contenant 3 actions dans une transaction. Je suis en train de refactoriser ce sous-processus dans une saga séparée. Le résultat de ceci sera que la saga originale commencera plusieurs instances du nouveau "sous-saga" (cette sous-saga sera également commencée par d'autres processus non-saga, par la même commande). Mon problème est de savoir comment corréler de la meilleure façon possible cette hiérarchie des sagas?Comment corréler correctement une saga de contrôleur qui démarre plusieurs instances d'une autre saga de contrôleur?

Dans l'exemple suivant, la saga principale tentera de démarrer trois instances de la sous-saga avec le même ID de corrélation. Même si cela devait fonctionner, les 3 instances interfèreraient l'une avec l'autre en manipulant des "événements terminés" provenant de toutes les instances.

public class MyMainSaga : Saga<MyMainSagaData>, 
    IAmStartedByMessages<MyMainCommand>, 
    IHandleMessage<MySubProcessCommandCompletedEvent> 
{ 
    protected override void ConfigureHowToFindSaga(SagaPropertyMapper<MyMainSagaData> mapper) 
    { 
     mapper.ConfigureMapping<MyMainCommandCompletedEvent>(message => message.CorrelationId).ToSaga(data => data.CorrelationId); 
    } 

    public void Handle(MyMainCommand message) 
    { 
     Data.CorrelationId = message.CorrelationId; 
     foreach (var item in message.ListOfObjectsToProcess) 
     { 
      Bus.Send(new MySubProcessCommand{ 
       CorrelationId = Data.CorrelationId, 
       ObjectId = item.Id 
      }); 
     } 
    } 

    public void Handle(MySubProcessCommandCompletedEvent message) 
    { 
     SetHandledStatus(message.ObjectId); 
     if(AllObjectsWhereProcessed()) 
      MarkAsComplete();  
    }  
} 


public class MySubSaga : Saga<MySubSagaData>, 
    IAmStartedByMessages<MySubProcessCommand>, 
    IHandleMessage<Step1CommandCompletedEvent>, 
    IHandleMessage<Step2CommandCompletedEvent>, 
    IHandleMessage<Step3CommandCompletedEvent> 
{ 
    protected override voidConfigureHowToFindSaga(SagaPropertyMapper<MySubSagaData> mapper) 
    { 
     mapper.ConfigureMapping<Step1CommandCompletedEvent>(message => message.CorrelationId).ToSaga(data => data.CorrelationId); 
     mapper.ConfigureMapping<Step2CommandCompletedEvent>(message => message.CorrelationId).ToSaga(data => data.CorrelationId); 
     mapper.ConfigureMapping<Step3CommandCompletedEvent>(message => message.CorrelationId).ToSaga(data => data.CorrelationId); 
    } 

    public void Handle(MySubProcessCommand message) 
    { 
     Data.CorrelationId = message.CorrelationId; 
     Data.ObjectId = message.ObjectId; 
     Bus.Send(new Step1Command{ 
      CorrelationId = Data.CorrelationId; 
     }); 
    } 

    public void Handle(Step1CommandCompletedEvent message) 
    { 
     Bus.Send(new Step2Command{ 
      CorrelationId = Data.CorrelationId; 
     }); 
    } 

    public void Handle(Step2CommandCompletedEvent message) 
    { 
     Bus.Send(new Step3Command{ 
      CorrelationId = Data.CorrelationId; 
     }); 
    } 

    public void Handle(Step3CommandCompletedEvent message) 
    { 
     Bus.Publish<MySubProcessCommandCompletedEvent>(e => { 
      e.CorrelationId = Data.CorrelationId; 
      e.ObjectId = Data.ObjectId; 
     }); 
     MarkAsComplete(); 
    } 
} 

La seule sollution que je vois est de changer la sous-saga pour générer une correlationId séparée ainsi que de garder l'identifiant du donneur d'ordre. E.g:

public void Handle(MySubProcessCommand message) 
    { 
     Data.CorrelationId = Guid.NewGuid(); 
     Data.OriginatorCorrelationId = message.CorrelationId; 
     Data.ObjectId = message.ObjectId; 
     Bus.Send(new Step1Command{ 
      CorrelationId = Data.CorrelationId; 
     }); 
    } 
    public void Handle(Step1CommandCompletedEvent message) 
    { 
     Bus.Send(new Step2Command{ 
      CorrelationId = Data.CorrelationId; 
     }); 
    } 

    public void Handle(Step2CommandCompletedEvent message) 
    { 
     Bus.Send(new Step3Command{ 
      CorrelationId = Data.CorrelationId; 
     }); 
    } 

    public void Handle(Step3CommandCompletedEvent message) 
    { 
     Bus.Publish<MySubProcessCommandCompletedEvent>(e => { 
      e.CorrelationId = Data.OriginatorCorrelationId; 
      e.ObjectId = Data.ObjectId; 
     }); 
     MarkAsComplete(); 
    } 

Existe-t-il une solution «meilleure pratique» à ce problème? J'ai pensé à utiliser Bus.Reply, notifiant le MainSaga quand la sous-saga est terminée. Le problème avec ceci est qu'un autre consommateur envoie également MySubProcessCommand sans attendre un événement/réponse terminé.

Répondre

3

La meilleure pratique consiste à utiliser ReplyToOriginator() dans la sous-saga pour communiquer avec la saga principale. Cette méthode est exposée sur la classe de base Saga.

Il existe deux façons de résoudre le problème du démarrage de la sous-saga à la fois par la saga principale et par un autre initiateur.

  1. Utilisez deux commandes différentes.

Soit deux commandes différentes commencent la sous-saga, comme MySubProcessFromMainSagaCommand et MySubProcessFromSomewhereElseCommand. Il est bon d'avoir plusieurs IAmStartedByMessages<> pour une Saga.

  1. Extend MySubProcessCommand

Inclure des données dans MySubProcessCommand pour indiquer si elle venait de la saga principale ou l'autre initiateur. L'une ou l'autre façon vous donnera assez d'informations pour stocker comment la sous-saga a été démarrée, par exemple Data.WasInitatedByMainSaga. Vérifiez ceci dans la logique d'achèvement de sous-saga. Si c'est le cas, faites ReplyToOriginator() pour revenir à la saga principale d'origine. Sinon, ignorez la réponse.

+0

Merci pour la réponse. Ensuite, la sous-saga devra créer son propre identifiant de corrélation, que je suppose être okei. Normalement, mes sagas utilisent l'identifiant de corrélation du message startedBy, qui semble maintenant faux, étant donné que ce type de cas existe. – sp1nakr

+0

Dans notre cas, la réponse ne fonctionnera pas, car la dernière étape de la saga est un gestionnaire de messages gérant un message provenant d'un autre service. Le message de réponse sera donc dirigé vers ce point de terminaison et non vers l'extrémité de la saga principale. Une approche utilisant Bus.SendLocal() serait-elle conseillée? Sinon, je ne vois pas d'autre solution que la publication d'un "processus fini", ou similaire. – sp1nakr

+0

Vous avez raison @ sp1nakr. :) La méthode correcte pour appeler est 'ReplyToOriginator()', pas 'Bus.Reply()'. J'ai mis à jour ma réponse. Le cas que vous décrivez est le même que https://docs.particular.net/nservicebus/sagas/reply-replaytooriginator-differences et https://docs.particular.net/nservicebus/sagas/#notifying-callers-of-status . – janovesk