#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.
Есть два способа решить проблему запуска вложенной саги как основной сагой, так и другим инициатором.
- Используйте две разные команды.
Пусть две разные команды запускают вложенную сагу, например MySubProcessFromMainSagaCommand
, и MySubProcessFromSomewhereElseCommand
. Хорошо иметь несколько IAmStartedByMessages<>
для саги.
- Расширить
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 /. …