简体   繁体   中英

Exception hierarchy for a REST service

I'm designing an exception class hierarchy for my REST service and face the following issue. Please note that the solution must be compatible with Java 7.

Suppose that I'm building a system to manage exams. Let's say that I have the following resources:

  • STUDENT (studentId, firstname, lastname, ...)
  • COURSE (courseId, name, ...)
  • EXAM (examId, grade, ...)

and the following operations (among others):

  • GET /students/{studentId}
  • GET /courses/{courseId}
  • POST /students/{studentId}/exams (with a body that contains the grade and the courseId)

Note that for some operations the courseId is provided as a part of the URL and for other as a part of the request-body.

I'd like to design an exception class hierarchy to report errors to the client side in a consistent way. I want to have the following attributes on each exception class:

  • statusCode - the HTTP status code for the response,
  • errorCode - a unique identifier of the error,
  • errorMessage - a human-readable description of the error.

Let us now focus on "invalid ID"-like errors. I have the following requirements:

  • If the invalid ID was provided as a part of the URL, statusCode should be 404 (not-found). If it was provided as a part of the request-body, then it should be 400 (bad-request).

  • The errorCode/errorMessage should identify the error from the business perspective. The errorCode should be a number that will uniquely identify the cause of error (let's say 1 for invalid-studentId, 2 for invalid-courseId, etc.) and the errorMessage should describe the cause in a human redable format (eg "Student with ID [135] does not exist.")

The question is, how do I combine these two ortogonal hierarchies into a single exception class hierarchy?

Ideally I would have the follwing exception classes:

public abstract class ApiException extends Exception {

    public abstract int getStatusCode();
    public abstract int getErrorCode();
    public abstract String getErrorMessage();
}

public abstract class NotFoundException extends ApiException {

    public int getStatusCode() {
        return 404;
    }
}

public abstract class BadRequestException extends ApiException {

    public int getStatusCode() {
        return 400;
    }
}

public abstract class InvalidCourseIdException extends ApiException {

    private final String courseId;

    public InvalidCourseIdException(final String courseId) {
        this.courseId = courseId;
    }

    public int getErrorCode() {
        return 2;
    }

    public String getErrorMessage() {
        return "Course with ID [" + courseId + "] does not exist.";
    }
}

public class CourseNotFoundException extends NotFoundException, InvalidCourseException {
    public CourseNotFoundException(String courseId) {
        super(courseId);
    }
}

public class BadCourseException extends BadRequestException, InvalidCourseException {

    public BadCourseException(String courseId) {
        super(courseId);
    }
}

...

Of course, multiple inheritance is not available in Java. How do I design a Java 7 compatible class hierarchy, honoring the DRY principle (I want to keep each constant value defined at only one place)?

Don't put too much information into the Exception class. It's perfectly OK to have an ExamRestServiceException with the attributes you mentioned. If you want to disallow certain combinations of attributes, make the constructor private and use a factory method, eg

 ...
 private ExamRestServiceException(int httpStatusCode, int errorCode, int objectId, String message) {
    // initialize your exception here
 }

 public ExamRestServiceException of(int httpStatusCode, int errorCode, int objectId, String message) {
    // check the arguments here
    return new ExamRestServiceException(httpStatusCode, errorCode, objectId, message);
 }
 ....

Edit: If you want to guide your API users better than only through your documentations, you can of course provide specialized factory methods, eg

 //e.g. needs no message, the HTTP status code is enough
 public ExamRestServiceException connectionError(int httpStatusCode) {…}

or

 public ExamRestServiceException(int errorCode, String message) { … }

or even provide one method per error condition. By the way there's a perfectly usable HttpRetryException you may want to reuse instead of rolling your own. If (and only if!) you fear that your exception class becomes too unwieldy, you should consider splitting it into multiple classes.

An alternative approach to llogiq is to have an exception builder or factory.

This (a) provides consistent exceptions, (b) keeps the code in one place if a developer needs to examine it and (c) saves the developer from knowing how individual exceptions are constructed if they don't need to know.

The key aspect to have checked exception is that you can catch them and recover from the state.

The exceptions should not transmit the reason by name. In case of 400 or 404 it is not possible to recover so the application should log the state and interrupt.

The class ApiException is more the enough to start.

If you are being afraid to produce to much code clones. You can create a util class to gather all cases.

final class ApiThrowables {

       static final int BAD_REQUEST = 400; 
       static final int NOT_FOUND   = 404;

       public static ApiException newCourseIdNotFound(String courseId) {
          return new ApiException(2, NOT_FOUND,"Course with ID [" + courseId + "] does not exist.");
       }

       public static ApiException newBadCourseId(String courseId) {
          return new ApiException(2, BAD_REQUEST,"Course ID [" + courseId + "] is not valid.");
       }

    }

Later when you will develop your application you will might change the design of exceptions this composition will allow you to do that. When you use inheritance instead of composition your code became relay thing couples and this inhibit the potential of change and reuse.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM