Обработка доменных ошибок в MassTransit - PullRequest
0 голосов
/ 06 марта 2019

Мне интересно, как правильно обрабатывать исключения домена?

Должен ли весь код моего потребителя быть заключен в блок try, catch, или я должен просто выдать исключение, которое будет обработано соответствующим FaultConsumer?

Рассмотрим два примера:

Пример-1 - вся операция заключена в блок try ... catch.

public async Task Consume(ConsumeContext<CreateOrder> context)
{         

try
{
  //Consumer that creates order 
  var order = new Order();
  var product = store.GetProduct(command.ProductId); // check if requested product exists

  if (product is null)
  {
    throw new DomainException(OperationCodes.ProductNotExist);
  }

  order.AddProduct(product);
  store.SaveOrder(order);

  context.Publish<OrderCreated>(new OrderCreated
  {
    OrderId = order.Id;
  });
}
catch (Exception exception)
{
  if (exception is DomainException domainException)
  {
    context.Publish<CreateOrderRejected>(new CreateOrderRejected
    {
      ErrorCode = domainException.Code;
    });
  }                
 }
}

Пример 2 - MassTransit обрабатывает исключение DomainException, помещая сообщение в очередь CreateOrder_error. Другая служба подписывается на это событие, и после публикации события в этой конкретной очереди она обрабатывает его;

public async Task Consume(ConsumeContext<CreateOrder> context)
{         

  //Consumer that creates order 
  var order = new Order();
  var product = store.GetProduct(command.ProductId); // check if requested product exists

  if (product is null)
  {
    throw new DomainException(OperationCodes.ProductNotExist);
  }

  order.AddProduct(product);
  store.SaveOrder(order);

  context.Publish<OrderCreated>(new OrderCreated
  {
    OrderId = order.Id;
  });
}

Какой подход должен быть лучше?

Я знаю, что могу использовать запрос / ответ и сразу получить информацию об ошибке, но в моем случае это должно быть сделано через посредник сообщений.

Ответы [ 2 ]

2 голосов
/ 07 марта 2019

Мое предположение заключается в том, что вы, кажется, заходите в беспорядок команд "забей и забудь".Конечно, это очень специфично для контекста, поскольку существуют сценарии, особенно интеграция, когда у вас нет пользователя, сидящего на другой стороне и задающегося вопросом, была ли его команда в конечном итоге выполнена и каков результат.

Итак, для сценариев интеграции, я согласен с ответом Криса, публикация события исключения домена имеет смысл.

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

Сервисная сторона:

public class CheckOrderStatusConsumer : 
    IConsumer<CheckOrderStatus>
{
    public async Task Consume(ConsumeContext<CheckOrderStatus> context)
    {
        var order = await _orderRepository.Get(context.Message.OrderId);
        if (order == null)
            await context.RespondAsync<OrderNotFound>(context.Message);
        else        
            await context.RespondAsync<OrderStatusResult>(new 
            {
                OrderId = order.Id,
                order.Timestamp,
                order.StatusCode,
                order.StatusText
            });
    }
}

Клиентская сторона:

var (statusResponse,notFoundResponse) = await client.GetResponse<OrderStatusResult, OrderNotFound>(new { OrderId = id});
// both tuple values are Task<Response<T>>, need to find out which one completed
if(statusResponse.IsCompletedSuccessfully)
{
    var orderStatus = await statusResponse;
    // do something
}
else
{
    var notFound = await notFoundResponse;
    // do something else
}
2 голосов
/ 06 марта 2019

В первом примере вы обрабатываете условие домена (в вашем примере это продукт, которого нет в каталоге), создавая событие, когда заказ был отклонен для неизвестного продукта.Это имеет полный смысл.

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

Но это бизнес-исключение, которое вы бы хотели отловить и обработать, опубликовав событие.

public async Task Consume (ConsumeContext<CreateOrder> context) {

  try {
    var order = new Order ();
    var product = store.GetProduct (command.ProductId); // check if requested product exists
    if (product is null) {
      throw new DomainException (OperationCodes.ProductNotExist);
    }

    order.AddProduct (product);
    store.SaveOrder (order);

    context.Publish<OrderCreated> (new OrderCreated {
      OrderId = order.Id;
    });
  } catch (DomainException exception) {
    await context.Publish<CreateOrderRejected> (new CreateOrderRejected {
      ErrorCode = domainException.Code;
    });
  }
}
...