Как обрабатывать ошибки сервера с многоуровневой архитектурой в контроллере веб-API - PullRequest
0 голосов
/ 16 мая 2018

РЕДАКТИРОВАТЬ:

Я только что понял, что SaveChangesAsync, возвращающий 0, не означает, что он потерпел неудачу, структура сущности всегда будет генерировать исключение, когда что-то не так, поэтому проверка, если SaveChanges == 0лишний!Изменения сохранения всегда должны возвращать 1 в примере ниже, если что-то не получается, тогда генерируется исключение.

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


Серверы могут выйти из строя, при помещении всего моего кода доступа к данным в контроллеры, я могу справиться с этим следующим образом:

[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
{
    await _dbContext.AddAsync(item);
    if (await _dbContext.SaveChangesAsync() == 0)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
    return CreatedAtAction(nameof(GetAsync), new { id = item.Id }, item);
}

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

public class ItemsService
{
    public async Task<Item> CreateAsync(Item item)
    {
        await _dbContext.AddAsync(item);
        if (await _dbContext.SaveChangesAsync() == 0)
        {
            return null;
        }
        return item;
    }
}

Тогда это будет использоваться следующим образом:

[HttpPost]
public async Task<ActionResult<Item>> CreateAsync([FromBody] Item item)
{
   // model state validation skipped for the sake of simplicity,
   // that would return BadRequest or some more valuable information
    var item = await _itemsService.CreateAsync(item);
    if (item == null)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
    return CreatedAtAction(nameof(GetAsync), new { id = item.Id }, item);
}

Может быть, это работает для точного создания, потому что есть только 2 кода состояния, но давайте рассмотрим обновление, где может быть больше 2Возможные ошибки, такие как:

  • Не найдено (404)
  • Внутренняя ошибка сервера (500)
  • Ok (200)

Кодбез услуг:

[HttpPut("{id}")]
public async Task<ActionResult<Item>> UpdateAsync(int id, [FromBody] Item itemToUpdate)
{
    var item = await _dbContext.Items.FindAsync(id);
    if (item == null)
    {
        return NotFound();
    }

    // update item with itemToUpdate
    //...
    await _dbContext.Update(item);
    if (await _dbContext.SaveChangesAsync() == 0)
    {
        return StatusCode(StatusCodes.Status500InternalServerError);
    }
    return item;
}

Теперь со службами это не может быть должным образом обработано:

public class ItemsService
{
    public async Task<Item> UpdateAsync(Item updateItem)
    {
        var item = await _dbContext.Items.FindAsync(id);
        if (item == null)
        {
            return null;
        }
        //change some properties and update
        //...
        _dbContext.Items.Update(item);
        if (await _dbContext.SaveChangesAsync() == 0)
        {
            // what now?
        }
        return item;
    }
}

, потому что оно всегда возвращает ноль, и нет никакого способа узнать, был ли элемент не найден илисохранение не удалось.

Как я могу правильно с ним справиться?

Примечание. Я не добавил DTO или что-то подобное, чтобы этот пример был простым.

Ответы [ 3 ]

0 голосов
/ 16 мая 2018

На самом деле это довольно большая тема, и есть десятки вариантов сделать это.

Я предпочитаю бросать исключения.Вы можете создать свои собственные классы для этого или использовать .net.Вы можете иметь NotFoundException, InternalServerError и т. Д. Каждый класс может иметь поле StatusCode и Message или что угодно.Затем вам нужно реализовать фильтр исключений для asp.net, который будет обрабатывать все исключения и возвращать ответ с кодом состояния, который можно получить из класса исключений.

Другой вариант может быть проще.Вы можете создать класс, который будет содержать StatusCode и объект в результате, так что вы будете возвращать его из ваших методов.Это способ вернуть больше, чем просто NULL или объект из ваших методов.

Несколько хороших статей: Исключения или коды ошибок

https://www.hanselman.com/blog/GoodExceptionManagementRulesOfThumb.aspx

http://codebetter.com/karlseguin/2006/04/05/understanding-and-using-exceptions/

0 голосов
/ 16 мая 2018

Код с сервисом намного лучше. Контроллер должен делегировать бизнес-логику сервисному уровню.

Чтобы улучшить реализацию уровня обслуживания, необходимо:

  • выдает пользовательские исключения, когда что-то не так происходит в вашем сервисе. Пример: бросить ItemNotFoundExcption вместо возврата null. ItemUpdateException при сбое обновления.
  • в вашем контроллере вам нужно перехватить все возможные пользовательские исключения, которые могут произойти, и сделать то, что нужно сделать, например, вернуть NotFound() для ItemNotFoundException и BadRequest() для ItemUpdateException.
  • последнее можно сделать с помощью фильтра пользовательских действий, который поможет вам сделать действия вашего контроллера более рациональными.
0 голосов
/ 16 мая 2018

Ваш сервис отвечает за отлов всех исключений, которые он знает, как их обрабатывать. О всех других исключениях следует сообщать через какое-то IServiceResult<T> с указанием bool IsSuccessful и AggregatedException Exceptions { get; }. Поэтому вместо того, чтобы бросать, вы позволяете высшему уровню решать, как реагировать. На самом деле это лучший подход со многих точек зрения, в том числе: чистота ваших функций (что является ключевой концепцией для простого распараллеливания, не важно ли это для сервера?), Чистый и обслуживаемый код (потребители сервиса не должны знать / это очень важно: они просто проверяют, является ли результат успешным, и потребляют результат, в противном случае копаются в агрегированном исключении; может иметь смысл реализовать собственный тип исключений, предоставляющий методы, подходящие для потребностей вашего проекта), возможность продолжить работу на более высоком уровне. функции, которые отвечают за фактическую обработку ошибок; возьмем rX в качестве популярного примера: .OnError(Action<Exception> handler). Их гораздо больше, но я не хочу, чтобы ответ был длиннее, чем нужно.

Как примечание. Обязательно прочитайте вводные рычажные статьи о Maybe и Either монадах из Haskell языка программирования; в случае, если вы предпочитаете F# больше, вы можете попытаться найти Some и Either соответственно. Там вы можете найти полезные методы, такие как .Map(...) и увидеть хорошие способы их эффективного объединения.

Удачи.

...