Was ist der beste Weg, um ein REST-API-Fehlerantwortmodell und ein Fehlercodesystem zu erstellen?

15

Meine REST-Implementierung gibt Fehler in JSON mit der folgenden Struktur zurück:

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

Ich schlage vor, ein spezielles Antwortmodell zu erstellen, in dem ich die erforderlichen Werte für Eigenschaften (dev_message, message_for_user, some_internal_error_code) übergeben und zurückgeben kann. Im Code wäre es ähnlich wie folgt:

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

Wie soll dieses Modell aussehen? Soll ich Methoden implementieren, z. B. successResponse (), bei denen ich nur Textinformationen übergebe und Code dort standardmäßig 200 ist? Ich bleibe dabei. Und dies ist der erste Teil meiner Frage: Muss ich dieses Modell implementieren, ist dies eine gute Praxis? Weil ich momentan nur Arrays direkt aus dem Code zurücksende.

Der zweite Teil befasst sich mit dem Fehlercodesystem. Fehlercodes werden in der Dokumentation beschrieben. Das Problem, auf das ich stoße, liegt im Code. Wie können Fehlercodes am besten verwaltet werden? Soll ich sie in model schreiben? Oder wäre es besser, einen separaten Service für die Abwicklung zu erstellen?

UPDATE 1

Ich habe eine Modellklasse für die Antwort implementiert. Es ist ähnlich wie Gregs Antwort, die gleiche Logik, aber zusätzlich habe ich Fehler im Modell fest codiert und hier ist es, wie es aussieht:

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

     ...some code...
    }

Warum habe ich das getan? Und wofür?

  1. Im Code sieht es cool aus: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. Einfach zu ändernde Fehlermeldung. Alle Nachrichten befinden sich an einem Ort anstelle von Controller / Service / etc oder was auch immer Sie es platzieren.

Wenn Sie Verbesserungsvorschläge haben, kommentieren Sie diese bitte.

Grokking
quelle

Antworten:

13

In dieser Situation denke ich immer zuerst an die Schnittstelle und schreibe dann PHP-Code, um sie zu unterstützen.

  1. Es ist eine REST-API, daher sind aussagekräftige HTTP-Statuscodes ein Muss.
  2. Sie möchten konsistente, flexible Datenstrukturen zum und vom Client senden.

Denken wir an all die Dinge, die schief gehen könnten, und an ihre HTTP-Statuscodes:

  • Der Server gibt einen Fehler aus (500)
  • Authentifizierungsfehler (401)
  • Die angeforderte Ressource wurde nicht gefunden (404)
  • Die Daten, die Sie ändern, wurden geändert, seit Sie sie geladen haben (409)
  • Validierungsfehler beim Speichern von Daten (422)
  • Der Kunde hat seine Anfragerate überschritten (429)
  • Nicht unterstützter Dateityp (415)

Beachten Sie, dass es andere gibt, die Sie später recherchieren können.

Für die meisten Fehlerzustände gibt es nur eine Fehlermeldung, die zurückgegeben werden muss. Die 422 Unprocessable EntityAntwort, die ich für "Validierungsfehler" verwendet habe, kann mehr als einen Fehler zurückgeben - einen oder mehrere Fehler pro Formularfeld.

Wir brauchen eine flexible Datenstruktur für Fehlerreaktionen.

Nehmen Sie als Beispiel 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!"
        ]
    }
}

Vergleichen Sie dies mit einfachen Überprüfungsfehlern, wenn Sie versuchen, etwas auf dem Server zu veröffentlichen:

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"
        ]
    }
}

Sie geben hier den Inhaltstyp an text/json. Dies teilt Clientanwendungen mit, dass sie den Antworttext mit einem JSON-Decoder decodieren können. Wenn beispielsweise ein interner Serverfehler nicht abgefangen wird und stattdessen Ihre generische Webseite "Something went wrong" ausgeliefert wird, sollte der Inhaltstyp so sein, text/html; charset=utf-8dass Clientanwendungen nicht versuchen, den Antworttext als JSON zu dekodieren.

Das sieht alles find and dandy aus, bis Sie JSONP- Antworten unterstützen müssen. Sie müssen eine 200 OKAntwort zurückgeben, auch bei Fehlern. In diesem Fall müssen Sie feststellen, dass der Client eine JSONP-Antwort anfordert (normalerweise durch Erkennen eines aufgerufenen URL-Anforderungsparameters callback) und die Datenstruktur ein wenig ändern:

(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!"
            ]
        }
    }
});

Dann sollte der Response-Handler auf dem Client (in einem Webbrowser) eine globale JavaScript-Funktion haben, displayBlogPostdie ein einzelnes Argument akzeptiert. Diese Funktion müsste feststellen, ob die Antwort erfolgreich war:

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

Also haben wir uns um den Kunden gekümmert. Nun kümmern wir uns um den Server.

<?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;
    }
}

Und um dies im Falle eines Serverfehlers zu verwenden:

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

Oder beim Validieren von Benutzereingaben:

// 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;

Danach brauchen Sie nur noch etwas, das das zurückgegebene Antwortobjekt in JSON konvertiert und die Antwort auf fröhliche Weise sendet.

Greg Burghardt
quelle
Vielen Dank für eine Antwort! Ich habe eine ähnliche Lösung implementiert. Der einzige Unterschied, dass ich selbst keine Nachrichten weitergebe, ist, dass sie bereits gesetzt sind (siehe meine aktualisierte Frage).
Grokking
-2

Ich stand vor etwas ähnlichem, ich habe 3 Dinge getan,

  1. Erstellt einen ExceptionHandler für mich mit dem Namen ABCException.

Da ich Java & Spring benutze,

Ich definierte es als

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

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

        }
    }

Dann nannte es, wo immer erforderlich, wie folgt,

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

Und ja, Sie müssen einen ExceptionHandler in Ihrem Controller erstellen, wenn Sie einen REST-basierten Webservice verwenden.

Beschriften Sie es mit, @ExceptionHandlerwenn Sie Spring verwenden

Dave Ranjan
quelle
Bei Programmierern geht es um konzeptionelle Fragen, und von den Antworten wird erwartet, dass sie die Dinge erklären . Das Werfen von Code-Dumps anstelle von Erklärungen ist wie das Kopieren von Code von IDE auf Whiteboard: Es mag vertraut und manchmal sogar verständlich erscheinen, aber es fühlt sich seltsam an ... einfach seltsam. Whiteboard hat keinen Compiler
Mücke