OperationResult: Ответ сервера всегда понятен пользователю

Просто о NET | создано: 06.03.2018 | опубликовано: 06.03.2018 | обновлено: 13.01.2024 | просмотров: 3105

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

История

Когда-то, очень давно, мне довелось поработать над одним очень сложным проектом, который представлял собой сложносоставную систему, не то чтобы из разных проектов, а даже разных платформ. Я и по сей день я считаю его сложным по сравнению со всеми теми, что встретились на моем пути разработчика. Речь идет не о WebAPI платформе и Xamarin. Для это "пары" на текущий момент уже придумано огромное множество различных прослоек, существенно упрощающих разработку. В том сложном проекте были такие "кирпичики" как Rational DOORS, InterSystems Caché, всякого рода стандарты типа ISO9001. Другими словами, проект был не из простых. Тогда пришлось решать очень много разных задач и зачастую не всегда простых задач. Вдаваться в подробности я не буду, скажу только что одна из простых задач звучала примерно так "корректная обработка статусов ответов от Web-сервера".

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

Решение

Чтобы не мучить сервер и не писать горы кода, мы приняли решение, которое в дальнейшем стало правилом:

Сервер должен всегда возвращать один код ответа как результат обработки запроса. Это код 200. А всю дополнительную информацию требуется передавать в теле ответа.

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

Как это работает

Предположим у нас есть WebAPI сервис, который работает с заказами нашего интернет-магазина. Пусть пользователь отправляет запрос на добавление в корзину 143 пачек презервативов (8 марта всё-таки!). Код на JavaScript, который делает отправку выглядит примерно так:

var data = {
    code: "PR2018-3-06",
    name: "Презерватив",
    count: 143
}
_cardService.addToBasket(data)
    .done(function (response){
        if(response && response.ok){
            showMessage(response.metadata.success);
        } else {
            if (response.metadata.warning) showMessage(response.metadata.warning);
            if (response.metadata.error) showMessage(response.metadata.error);
        }
    })
    .fail(function (response){
        showMessage("Что-то пошло не так. Сервер вернул не корректный ответ.")
    });

Какие существуют варианты ответа от сервера в данном случае? Их несколько:

  1. Заказ добавлен в корзину.
  2. Презервативы закончились на складе.
  3. В корзине уже есть такой товар.
  4. Нет ответа. Ошибка сервиса или сервис не доступен.

При использовании OperationResult мы можем легко обработать все варианты. При результате номер 1 мы можем показать сообщение, например, "Товар успешно добавлен в корзину". Более того, зная что за товар, мы можешь в специальном объекте, который возвращается в OperationResult вернуть как предложение сопутствующий товар, например, "Букет цветов" и показать этот товар на вкладке "Вместе с этим покупают...". и т.д. При результате номер 2, когда количество на складе 53, мы можем покупателю показать информацию о том, что не хватает на складе презервативов, и "спросить" что делать в данном случае, добавлять 53 или отменить добавление. При результате номер 3 мы также имеем возможность спросить у пользователя какие действия следует произвести далее, или добавить к существующему в корзине количеству добавляемое количество или отменить добавление. А в результате номер 4 мы точно знаем, что сервис сломался и мы не сможем завершить составление заказа.

OperationResult

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

T Result {get; set;} - Обобщенный объект для результата, то есть это может быть как примитивный тип, так класс, или даже коллекция классов.

Exception Error {get; set;} - Ошибка выполнении операции. Мы может на клиента передавать ошибку целиком, если это потребуется для пост обработки уже на клиенте или еще какая-нибудь информация, например, из стека.

Dictionary<OperationInfo, object> Metadata {get; set;} - Это объекты с предопределенным типом ключа, где тип ключа OperationInfo является перечислением.

public enum OperationInfo {
    Info,
    Success,
    Warning,
    Error,
    DataObject
}

В общем-то, больше ничего в OperationResult и нет. Но перечисленный набор предоставляет огромные возможности по управлению процессами приложения. Поверьте, этот подход можно и нужно использовать не только WebAPI, но на любых других типах взаимодействия между модулями, сервисами, объектами и прочими составляющими (компонентами) любого приложения на любой платформе. Особенно полезно при взаимодействии слабосвязанных компонентов, например, когда получая результат NULL при выполнении метода Send в EmailService вы не будете знать что случилось и дополнительная информация в этому случае вам бы точно не помешала.

Видео

Ссылки