Как правильно сопоставить сагу контроллера, которая запускает несколько экземпляров другой саги контроллера?

#c# #nservicebus #correlation #nservicebus-sagas

#c# #nservicebus #корреляция #nservicebus-саги

Вопрос:

У меня есть сага контроллера, в которой раньше был шаг, запускающий процесс, содержащий 3 действия в одной транзакции. Сейчас я нахожусь в процессе рефакторинга этого подпроцесса в отдельную сагу. Результатом этого будет то, что исходная сага запустит несколько экземпляров новой «вложенной саги» (эта вложенная сага также будет запущена другими процессами, не относящимися к саге, с помощью той же команды). Моя проблема в том, как наилучшим образом соотнести эту иерархию саг?

В следующем примере основная сага попытается запустить три экземпляра вложенной саги с одинаковым идентификатором корреляции. Даже если бы это сработало, 3 экземпляра будут мешать друг другу, обрабатывая «завершенные события», исходящие из всех экземпляров.

 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();
    }   
}
  

Единственное решение, которое я вижу, — это изменить вложенную сагу для создания отдельного CorrelationId, а также сохранить идентификатор отправителя. Например:

     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();
    } 
  

Существует ли «лучшее практическое» решение этой проблемы? Я думал об использовании Bus.Ответ, уведомляющий MainSaga о завершении вложенной саги. Проблема с этим заключается в том, что другой потребитель также отправляет MySubProcessCommand, не дожидаясь завершенного события / ответа.

Ответ №1:

Лучше всего использовать ReplyToOriginator() в подзаголовке для обратной связи с основной сагой. Этот метод доступен в базовом классе Saga.

Есть два способа решить проблему запуска вложенной саги как основной сагой, так и другим инициатором.

  1. Используйте две разные команды.

Пусть две разные команды запускают вложенную сагу, например MySubProcessFromMainSagaCommand , и MySubProcessFromSomewhereElseCommand . Хорошо иметь несколько IAmStartedByMessages<> для саги.

  1. Расширить MySubProcessCommand

Включите некоторые данные, MySubProcessCommand чтобы указать, поступили ли они из основной саги или другого инициатора.

В любом случае вы получите достаточно информации, чтобы сохранить, например, как была запущена вложенная сага Data.WasInitatedByMainSaga . Проверьте это в логике завершения вложенной саги. Если это true, выполните a ReplyToOriginator() для обратной связи с исходной основной сагой. Если нет, пропустите ответ.

Комментарии:

1. Спасибо за ответ. Тогда подзаголовок должен будет создать свой собственный идентификатор корреляции, который, я полагаю, является okei. Обычно мои саги используют идентификатор корреляции из сообщения startedBy, что теперь кажется неправильным, учитывая, что этот тип случая существует.

2. В нашем случае ответ не будет работать, поскольку последним шагом саги является обработчик сообщений, обрабатывающий сообщение от другой службы. Таким образом, ответное сообщение будет направлено на эту конечную точку, а не на конечную точку основной саги. Подход с использованием шины. Рекомендуется использовать SendLocal()? Иначе я не вижу другого решения, кроме публикации «события завершения процесса» или подобного.

3. Вы правы @sp1nakr. 🙂 Правильный метод для вызова ReplyToOriginator() , не Bus.Reply() . Я обновил свой ответ. Случай, который вы описываете, такой же, как docs.particular.net/nservicebus/sagas /… и docs.particular.net/nservicebus/sagas /.