Приведу сферический пример из головы, чтобы было нагляднее:
Допустим у нас есть два микросервиса со своими базами данных. Первый служит для заполнения данных, второй для формирования из этих данных PDF для печати.
В первом микросервисе происходит создание карточки студента, заполнение её персональными данными и прикрепление фотографии. Иными словами есть WEB API и три метода, которые вызываются в сл. порядке:
1. CreateStudentCard()
2. PutPersonalData()
3. PutPhoto()
В первом методе CreateStudentCard() происходит создание карточки студента, генерация её ID и сохранение в базу с этим ID, в качестве первичного ключа.
В остальных двух методах мы, используя полученный ID карточки студента в качестве внешнего ключа, сохраняем в другие соотв. таблицы персональные данные и фото.
В каждом из этих трех методов, после сохранения в базу, происходит отправка события на шину RabbitMQ с использованием библиотеки MassTransit. В первом методе, после сохранения карточки в базу, происходит отправка сообщения StudentCardCreated, которое содержит ID карточки и дату создания. Во втором методе, соответственно, отправка события PersonalDataUpdated с данными студента, в третьем PhotoUpdated с фотографией.
Во втором микросервисе мы подписаны на все эти три события. Иными словами - есть три асинхронных Consumer'а, которые получат асинхронно три эти сообщения для того, что бы уже в своей базе создать карточку студента, с тем же ID, что и в микросервисе источнике, и прикрепить к ней персональные данные и фото.
И вот тут начинается суть вопроса:
Каждый асинхронный consumer-потребитель в своей реализации имеет методы обращения к контексту данных, которые так же асинхронны (в частности LINQ-методы EntityFramework).
И вот первый consumer, который прослушивает событие StudentCardCreated получает сообщение. Внутри него мы, прежде чем сразу взять и сохранить пришедшую карточку, вызываем какой-то асинхронный метод, для проверки чего-либо в базе, используя await (допустим AnyAsync). Как только в этом consumer'e вызывается await, управление вернется вызывающему потоку, который тут же подхватит следующее событие из очереди и вызывает consumer для второго сообщения PersonalDataUpdated.
В таком случае есть риск, что consumer для PersonalDataUpdated дойдет до сохранения в базу данных раньше, чем создастся сама карточка студента в consumer'e для StudentCardCreated, что, разумеется приведет к ошибке, так как нам нужен ID карточки в качестве внешнего ключа, ведь мы не можем прикрепить данные и фото к карточке, которой не существует в базе.
Думаю, что я не до конца понимаю логику работы с брокером либо то, как работает асинхронность в данном случае. Может быть, кто-то сможет подсказать, как правильно такие ситуации разрешаются и может быть такой подход вообще в корне неверный?
Набросал в пейнте схему того, что я имею в виду. Стрелочками и цифрами обозначен тот порядок действий, как я его вижу: