Gestione di DomainExceptions mediante l'endpoint REST

0

Supponiamo che sto facendo un po 'di DDD. Ora, ho un microservizio che riflette un contesto limitato / una parte di un contesto limitato.

Ora, supponiamo che esista un endpoint REST:

'/somedomainmodel/someaction'

Il mio utente API sta facendo una richiesta POST con dati in qualche modo non validi. Sto usando Java e presumo che la seguente catena sia eseguita:

  • la richiesta viene gestita da un controller Spring o qualcos'altro

  • il controller sta invocando un metodo su un servizio di applicazione

  • il servizio applicativo sta facendo qualche azione su un modello di dominio

Tuttavia, come accennato in precedenza, i dati forniti da un utente sono in qualche modo errati e potrebbero far sì che il mio oggetto dominio entri in stato non valido. Quindi viene generata una sottoclasse di DomainException extends RuntimeException .

Ora: come dovrei gestirlo? Supponiamo che il mio endpoint sia utilizzato da un frontend e voglio visualizzare un messaggio appropriato all'utente.

Devo restituire un codice di stato e un messaggio in una risposta? Dove dovrebbe essere rilevato e come? In un servizio di applicazione? O forse in un controller? E poiché non è un'eccezione controllata, come dovrei gestirla? Try / Catch per un'eccezione di runtime sembra essere terribile.

Forse se stiamo parlando di specificare l'implementazione, dovrei usare qualcosa come spring @ExceptionHandler ?

    
posta slnowak 14.02.2015 - 13:49
fonte

5 risposte

1

Preferisco fare qualcosa del genere:

  1. Il resto del punto finale riceverà una richiesta con i dati, quindi
  2. La classe del servizio business viene chiamata con i dati
  3. convalidare i dati se non è valida lanciare un'eccezione con una descrizione appropriata, ad esempio throw new InvalidArgumentException("invalid objet Id");
  4. Sul controller se viene generata un'eccezione sopra e restituisce 400 Richiesta non valida con descrizione dell'errore, ad esempio {"ok":false, "error":"invalid_argument","msg":"invalid objet Id"}

In questo modo la business class può essere utilizzata ovunque e la risposta può essere gestita di conseguenza.

    
risposta data 30.05.2015 - 22:42
fonte
0

Credo che sia possibile inviare la risposta dal gestore degli errori o testare i dati prima di inviarli, se possibile. Puoi anche utilizzare un passo di precaricamento durante l'inizializzazione dell'app dall'interfaccia REST e memorizzare parametri di valore validi e utilizzare quelli sul tuo client per testare i dati in uscita.

Sarebbe semplice provare a inviare una risposta dal gestore. La raccolta dei rifiuti si ripulisce, ad esempio gli errori sono costosi. Quindi un design migliore è la pulizia del cliente. Spero che sia d'aiuto.

    
risposta data 16.02.2015 - 04:19
fonte
0

Dovresti restituire un errore 4xx con una descrizione dettagliata del motivo per cui la richiesta non è riuscita nel corpo della risposta.

Non può aiutare con l'implementazione Java. In molti framework Python si genera un'eccezione 4xx non appena il codice ha determinato che la richiesta HTTP fallirà e verrà rilevata dal framework e restituirà un messaggio di stato HTTP 4xx.

    
risposta data 09.04.2015 - 00:06
fonte
0

Recentemente ho scritto un post dettagliato su questi e alcuni relativi domande.

Should I return a status code and a message in a response?

Sì, ma il messaggio non dovrebbe contenere messaggi di eccezione a meno che quel messaggio sia stato creato specificamente per essere mostrato all'utente finale. Dato che parli di DomainException , che probabilmente dovrebbe essere abbastanza specifico nello spiegare cosa è successo e perché, potresti mostrare questo messaggio di eccezione a un utente. Tuttavia, ti consiglio di separare le eccezioni a livello di dominio dalle eccezioni a livello di API e di rendere quest'ultimo al 100% "sicuro per l'utente", quindi puoi mostrarle all'utente finale senza preoccuparti che contenga qualcosa come "MySQLIntegrityConstraintViolationException".

Le eccezioni a livello di API dovrebbero apparire come segue:

// Assume that all those exception classes extend some base class 'ApiException'

throw new ApiNotFoundException(404, "Entity with id=" + id + " not found.");

throw new ApiDatabaseUnavailableException(502, "Database server is unavailable. Retry later.")

throw new ApiConstraintViolationException(422, // WebDAV, Unprocessable Entity
    "Entity with id=" + id + " cannot be deleted " +
    "because it is a parent of entity with id=" + childId + ". "
    "Delete all children first.");

Where should it be detected and how?

Ci sono diversi livelli nell'applicazione in cui diversi tipi di eccezioni dovrebbero essere gestiti. L'idea di base è: gestire le eccezioni Java standard (NPE, IllegalArgument, ecc.) Il più presto possibile, avvolgerle in qualcosa di più specifico e lanciare ulteriormente. Successivamente, puoi gestire l'eccezione specifica del dominio in un livello intermedio e infine le eccezioni specifiche dell'API possono essere gestite in un unico gestore / intercettore "catch-them-all".

Vedi Eccezioni di registrazione: dove registrare le eccezioni? e il mio post per i dettagli.

And since it's not a checked exception, how should I handle it? Try/Catch for a runtime exception seems to be awful.

try / catch va bene se rilevi eccezioni al giusto livello. L'errore principale che vedo le persone fare (che inizialmente mi ha fatto scrivere su di esso) sta rilevando eccezioni troppo in alto nello stack di chiamate dal punto in cui sono state gettate - in pratica, troppo "in ritardo". In tali casi le persone cercano di indovinare cosa è successo esaminando un tipo di eccezione e / o un modello di corrispondenza. Questo è stato davvero orribile quando ho visto NPE interpretato come "elemento non trovato nel database" (in realtà era solo un% casuale dinull da qualche parte nel mezzo).

Maybe if we're talking about specifing implementation, should I use something like spring's @ExceptionHandler?

Dovresti usare anche questo tipo di cose, ma solo per le eccezioni API / a livello di dominio.

    
risposta data 30.05.2015 - 23:25
fonte
0

data provided by an user is somehow incorrect and could cause my domain object to get into invalid state. So a subclass of DomainException extends RuntimeException is thrown.

Perfetto.

Now: how should I handle this? Suppose my endpoint is used by a frontend and I want to display a proper message to user.

Rileva l'eccezione di runtime sul limite di errore e utilizza il tipo e il contenuto dell'eccezione per creare una risposta appropriata da restituire al client.

Il limite di errore probabilmente appartiene al luogo in cui viene gestita la richiesta e si ha consapevolezza di cose come il tipo di media previsto.

Non sarebbe del tutto irragionevole avere un limite di errore nel gestore dei comandi, per pacchettizzare DomainException come ApplicationException, a seconda di come si percepisce il dominio che perde nel livello http. Poiché la convalida del comando avviene nel componente dell'applicazione, probabilmente vorrete essere in grado di generare eccezioni anche da lì. Entrambe queste ApplicationException verranno quindi gestite dal limite di errore nel componente che gestisce la richiesta http.

And since it's not a checked exception, how should I handle it? Try/Catch for a runtime exception seems to be awful.

Abbraccia la verità che controlla che le eccezioni siano un esperimento che non è stato davvero visualizzato; prendere e segnalare il problema.

Le DomainExceptions devono essere segnalate con codici di stato 4xx (es .: errori del client). 409-Conflict è quello che preferirei quando il problema è una violazione dell'invarianza di business.

Si sta dicendo al cliente che è probabile che possa risolvere il problema (ad esempio: ottenere una nuova visualizzazione dello stato di aggregazione e quindi modificare il comando in modo che non sia in conflitto). Quindi sarebbe ragionevole includere nella risposta ipermedia un link ad una rappresentazione leggibile dello stato aggregato, per esempio. Il che implicherebbe che la gestione delle eccezioni debba avvenire in un punto del codice che sappia come costruire quel collegamento dalle informazioni nella richiesta.

In pseudo codice, probabilmente l'intera cosa sembra

try {
    // If this bit fails, we want to return a 400-Bad Request to the client.
    Command<A> command = parse(httpRequest);

    // We got this far, so now we pass the command we've parsed off to the
    // command handler....
    {
        // The aggregate lookup could fail; that might be any of
        // 400-Bad Request
        // 404-Not Found
        // 410-Gone
        // ... depending on how you interpret the relationship between the
        // *resource* and the *aggregate*
        Aggregate<A> aggregate = Repository<A>.getById(command.id);

        // A failure here is probably best reported via 409-Conflict;
        // essentially to inform the user that the application state
        // is stale, and should be refreshed
        aggregate.execute(command);

        // A failure here indicates that the command lost a data race;
        // that's certainly a conflict.  So if you are going to report
        // an error at this point, 409-Conflict is the right way to go.
        // A better choice might be to retry the command, though; after
        // all, the stale version of the aggregate thought it was acceptable
        // so the updated version might as well.  In that case, you would
        // catch this exception and reload the aggregate.
        Repository<A>.save(aggregate);
    }

    // OK, we got through everything without a problem, so report success
    reportSuccess(httpResponse);
} catch (...) {
    // Possibly a single handler, or multiple handlers, depending upon
    // how you design your exception hierarchy.  The important thing is that
    // the exception handler here knows how to translate the application
    // exceptions thrown above into something suitable for http, and the
    // media types being used by this endpoint.
    exceptionHandler.forRequest(httpRequest).report(e, httpResponse);
}
    
risposta data 28.01.2016 - 22:20
fonte

Leggi altre domande sui tag