Jaki jest najlepszy sposób na stworzenie modelu odpowiedzi na błąd REST API i systemu kodów błędów?

15

Moja implementacja REST zwróci błędy w JSON z następną strukturą:

{
 "http_response":400,
 "dev_message":"There is a problem",
 "message_for_user":"Bad request",
 "some_internal_error_code":12345
}

Sugeruję utworzenie specjalnego modelu odpowiedzi, w którym mogę przekazać potrzebne wartości właściwości (dev_message, message_for_user, some_internal_error_code) i zwrócić je. W kodzie byłoby to podobne do tego:

$responseModel = new MyResponseModel(400,"Something is bad", etc...);

Jak powinien wyglądać ten model? Czy powinienem wdrożyć metody, np. SuccessResponse (), w których przekażę tylko informacje tekstowe, a kod będzie tam domyślnie 200? Utknąłem z tym. I to jest pierwsza część mojego pytania: czy muszę wdrożyć ten model, czy to dobra praktyka? Ponieważ na razie zwracam tablice bezpośrednio z kodu.

Druga część dotyczy systemu kodów błędów. Kody błędów zostaną opisane w dokumentacji. Ale napotykam problem związany z kodem. Jaki jest najlepszy sposób zarządzania kodami błędów? Czy powinienem napisać je w modelu? Czy lepiej byłoby stworzyć osobną usługę do obsługi tego?

AKTUALIZACJA 1

Zaimplementowałem klasę modelu dla odpowiedzi. Jest to podobna odpowiedź Grega, ta sama logika, ale dodatkowo na stałe zapisałem błędy w modelu i oto jak to wygląda:

    class ErrorResponse
    {
     const SOME_ENTITY_NOT_FOUND = 100;
     protected $errorMessages = [100 => ["error_message" => "That entity doesn't exist!"]];

     ...some code...
    }

Dlaczego to zrobiłem? A po co?

  1. Wygląda świetnie w kodzie: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. Łatwy do zmiany komunikat o błędzie. Wszystkie wiadomości są w jednym miejscu zamiast kontrolera / usługi / etc lub cokolwiek to umieścisz.

Jeśli masz jakieś sugestie, aby to poprawić, prosimy o komentarz.

Grokking
źródło

Odpowiedzi:

13

W tej sytuacji zawsze najpierw myślę o interfejsie, a następnie piszę kod PHP, aby go obsługiwać.

  1. Jest to interfejs API REST, dlatego konieczne są znaczące kody stanu HTTP.
  2. Chcesz, aby spójne, elastyczne struktury danych były wysyłane do i od klienta.

Pomyślmy o wszystkich rzeczach, które mogą się nie udać, i ich kodach stanu HTTP:

  • Serwer zgłasza błąd (500)
  • Błąd uwierzytelnienia (401)
  • Żądany zasób nie został znaleziony (404)
  • Dane, które modyfikujesz, zostały zmienione od czasu ich załadowania (409)
  • Błędy walidacji podczas zapisywania danych (422)
  • Klient przekroczył wskaźnik żądań (429)
  • Nieobsługiwany typ pliku (415)

Uwaga: istnieją inne, które możesz później zbadać.

W przypadku większości stanów awaryjnych zwracany jest tylko jeden komunikat o błędzie. The422 Unprocessable EntityOdpowiedź, której użyłem do „błędów walidacji” może zwrócić więcej niż jeden błąd --- jeden lub więcej błędów na pola formularza.

Potrzebujemy elastycznej struktury danych do reagowania na błędy.

Weźmy jako przykład 500 Internal Server Error:

HTTP/1.1 500 Internal Server Error
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "general": [
            "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
        ]
    }
}

Porównaj to z prostymi błędami weryfikacji podczas próby wysłania czegoś na serwer:

HTTP/1.1 422 Unprocessable Entity
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "first_name": [
            "is required"
        ],
        "telephone": [
            "should not exceed 12 characters",
            "is not in the correct format"
        ]
    }
}

Kluczem tutaj jest typ zawartości text/json. Informuje to aplikacje klienckie, że mogą dekodować treść odpowiedzi za pomocą dekodera JSON. Jeśli powiedzmy, że wewnętrzny błąd serwera nie został wykryty i zamiast tego zostanie wyświetlona ogólna strona internetowa „Coś poszło nie tak”, typ zawartości powinientext/html; charset=utf-8 taki, aby aplikacje klienckie nie próbowały dekodować treści odpowiedzi jako JSON.

Wygląda to na wyszukane i eleganckie, dopóki nie będziesz musiał obsługiwać odpowiedzi JSONP . Musisz zwrócić 200 OKodpowiedź, nawet w przypadku awarii. W takim przypadku musisz wykryć, że klient żąda odpowiedzi JSONP (zwykle wykrywając parametr żądania adresu URL o nazwiecallback ) i nieco zmienić strukturę danych:

(GET / posts / 123? Callback = displayBlogPost)

<script type="text/javascript" src="/posts/123?callback=displayBlogPost"></script>

HTTP/1.1 200 OK
Content-Type: text/javascript
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

displayBlogPost({
    "status": 500,
    "data": {
        "errors": {
            "general": [
                "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
            ]
        }
    }
});

Następnie moduł obsługi odpowiedzi na kliencie (w przeglądarce internetowej) powinien mieć globalną funkcję JavaScript o nazwie, displayBlogPostktóra akceptuje pojedynczy argument. Ta funkcja musiałaby ustalić, czy odpowiedź się powiodła:

function displayBlogPost(response) {
    if (response.status == 500) {
        alert(response.data.errors.general[0]);
    }
}

Więc zadbaliśmy o klienta. Teraz zajmijmy się serwerem.

<?php

class ResponseError
{
    const STATUS_INTERNAL_SERVER_ERROR = 500;
    const STATUS_UNPROCESSABLE_ENTITY = 422;

    private $status;
    private $messages;

    public function ResponseError($status, $message = null)
    {
        $this->status = $status;

        if (isset($message)) {
            $this->messages = array(
                'general' => array($message)
            );
        } else {
            $this->messages = array();
        }
    }

    public function addMessage($key, $message)
    {
        if (!isset($message)) {
            $message = $key;
            $key = 'general';
        }

        if (!isset($this->messages[$key])) {
            $this->messages[$key] = array();
        }

        $this->messages[$key][] = $message;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getStatus()
    {
        return $this->status;
    }
}

I aby użyć tego w przypadku błędu serwera:

try {
    // some code that throws an exception
}
catch (Exception $ex) {
    return new ResponseError(ResponseError::STATUS_INTERNAL_SERVER_ERROR, $ex->message);
}

Lub podczas sprawdzania poprawności danych wprowadzanych przez użytkownika:

// Validate some input from the user, and it is invalid:

$response = new ResponseError(ResponseError::STATUS_UNPROCESSABLE_ENTITY);
$response->addMessage('first_name', 'is required');
$response->addMessage('telephone', 'should not exceed 12 characters');
$response->addMessage('telephone', 'is not in the correct format');

return $response;

Następnie potrzebujesz czegoś, co pobiera zwrócony obiekt odpowiedzi i przekształca go w JSON i wysyła odpowiedź w wesoły sposób.

Greg Burghardt
źródło
Dziękuję za odpowiedź! Wdrożyłem podobne rozwiązanie. Jedyną różnicą jest to, że nie przekazuję żadnych wiadomości samodzielnie, są one już ustawione (patrz moje zaktualizowane pytanie).
Grokking,
-2

Stawiłem czoła czemuś podobnemu, zrobiłem 3 rzeczy,

  1. Stworzyłem dla mnie ExceptionHandler o nazwie ABCException.

Ponieważ używam Java i Spring,

Zdefiniowałem to jako

 public class ABCException extends Exception {
private String errorMessage;
private HttpStatus statusCode;

    public ABCException(String errorMessage,HttpStatus statusCode){
            super(errorMessage);
            this.statusCode = statusCode;

        }
    }

Następnie nazwał to, gdziekolwiek było to wymagane, w ten sposób,

throw new ABCException("Invalid User",HttpStatus.CONFLICT);

I tak, musisz stworzyć ExceptionHandler w swoim kontrolerze, jeśli używasz usługi internetowej opartej na REST.

Dodaj adnotację, @ExceptionHandlerjeśli używasz wiosny

Dave Ranjan
źródło
Programiści jest o koncepcyjnych pytań i odpowiedzi oczekuje się wyjaśnienia sprawy . Rzucanie zrzutów kodu zamiast objaśnień przypomina kopiowanie kodu z IDE na tablicę: może wyglądać znajomo, a czasem nawet być zrozumiałe, ale wydaje się dziwne ... po prostu dziwne. Tablica nie ma kompilatora
gnat