Использование команд и сервисной шины

353
03 августа 2017, 22:16

Предположим, есть веб-сервис, который принимает POST-запрос на регистрацию пользователя. Первоначальная реализация (намеренно очень упрощенная):

[HttpPost]
public ActionResult Post([FromBody]CustomerDTO value)
{
    try
    {
        _customerRepository.Create(value);
        return Ok();
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }
}

Я захотел распределить нагрузку между несколькими серверами, и вместо того, чтобы дожидаться сохранения в репозитории, я хочу послать команду "Зарегистрировать пользователя" в шину. Она попадет в очередь, например, RabbitMQ. Далее, эту команду обработает один из серверов, который подписан на выполнение этой команды. Что-то вроде того:

[HttpPost]
public ActionResult Post([FromBody]CustomerDTO value)
{
    try
    {
        RegisterCustomerCommand command = MapFromDto<RegisterCustomerCommand>(value);
        _bus.Send(command)
        return Ok();
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }
}
...
public class RegisterCustomerCommandHandler: ICommandHandler<RegisterCustomerCommand>
{
    private ICustomerRepository _customerRepository;
    public RegisterCustomerCommandHandler(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }
    public void Handle(RegisterCustomerCommand command)
    {
        _customerRepository.Create(GetDTOFromCommand(command));
    }
}

Однако, допустим, согласно логике приложения мы не можем зарегистрировать двух пользователей с одинаковым E-mail. В случае реализации сервисной шины мы имеем опасность в одно и то же время получить запросы на регистрацию пользоватаеля с одинаковыми E-mail. Клиенту WebApi вернется код 200, что значит что регистрация прошла успешно. Однако одна из команд неминуемо упадёт (т.к. попытается зарегистрировать пользователя с E-mail, который пару мгновений назад уже был зарегистирован), но клиент уже об этом не узнает.

Предварительная валидация (не зарегистрирован ли уже пользователь с таким Email?) тут тоже не поможет - в момент валидации он может быть не зарегистрирован, а в момент выполнения команды - уже зарегистрирован.

Что вы думаете об этой ситуации? Как вы поступаете в таком случае?

Мои мысли по этому вопросу: такая реализация отлично подойдет, например, в случае, когда надо послать пользователю электронное письмо с ссылкой подтверждения Email. Зарегистрировали пользователя --> создали событие "CustomerRegistredEvent", послали в очередь --> это событие из очереди через n секунд заберет обработчик и пошлёт письмо пользователю. Однако в "чувствительных" случаях, таких как тот, что описан выше (регистрация пользователя) необходимо дождаться выполнения всей команды. Возможно, тут не надо применять Команду и Шину.

Answer 1

Возможно в мире распределенных систем данная задача решается иначе, но если отбросить специфику конкретной реализации, то выглядит, как самая обыкновенная асинхронная задача.

Ты регистрируешь команду в очереди и отдаёшь пользователю её идентификатор и примерное время, за которое она будет выполнена. Спустя это время пользователь спрашивает о текущем состоянии задачи. Получает либо результат выполнения, либо новый эстимейт по времени. И так пока сервер не скажет клиенту, что задача завершена с определенным результатом, либо пока клиенту не надоест ждать (и в этом случае стоит хотя бы попытаться отправить запрос на отмену), либо пока задача каким-нибудь нехорошим образом не исчезнет из истории (н.п. если сервер перезагрузили). Эти ситуации клиент обрабатывает самостоятельно и либо повторно пытается выполнить задачу, либо информирует пользователя о том, что что-то пошло не так.

READ ALSO
Не отображает Particle sysme в 2Д игре

Не отображает Particle sysme в 2Д игре

Привет! создал первую 2Д игру, добавил партиклы, но они не отображаются над картинкой

222
Как отключить передвижение заголовков Pivot&#39;а?

Как отключить передвижение заголовков Pivot'а?

Пытаюсь создать Pivot табамиВроде бы как все закончил, но из за того что элементы примыкают к краям приложения они начинают "бегать"

259
Задать Source для WebBrowser в WPF

Задать Source для WebBrowser в WPF

Есть приложение C# WPF в котором присутствует 2 WPF Window на одной несколько кнопок и WebBrowser на другойПри щелчке по одной из кнопок на открывается...

483