REST服务的异常层次结构

时间:2014-09-05 15:00:41

标签: java oop

我正在为我的REST服务设计一个异常类层次结构,并面临以下问题。 请注意,该解决方案必须与Java 7兼容。

假设我正在建立一个管理考试的系统。让我们说我有以下资源:

  • 学生(studentId,名字,姓氏,......)
  • 课程(courseId,name,...)
  • 考试(考试,成绩,......)

以及以下操作(以及其他操作):

  • GET / students / {studentId}
  • GET / courses / {courseId}
  • POST / students / {studentId} /考试(包含成绩和课程的正文)

请注意,对于某些操作,courseId作为URL的一部分提供,而其他作为请求体的一部分提供。

我想设计一个异常类层次结构,以一致的方式向客户端报告错误。我想在每个异常类上具有以下属性:

  • statusCode - 响应的HTTP状态代码,
  • errorCode - 错误的唯一标识符
  • errorMessage - 一个人类可读的错误描述。

现在让我们关注"无效ID"类似错误。我有以下要求:

  • 如果无效ID作为网址的一部分提供,则 statusCode 应为404(未找到)。如果它是作为请求体的一部分提供的,那么它应该是400(不良请求)。

  • errorCode / errorMessage应该从业务角度识别错误。 errorCode应该是一个唯一标识错误原因的数字(假设对于invalid-studentId为1,对于invalid-courseId为2等),errorMessage应该以人类可更新的格式描述原因(例如" ID [135]的学生不存在。")

问题是,如何将这两个ortogonal层次结构组合成一个异常类层次结构?

理想情况下,我会有以下异常类:

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

...

当然,Java中没有多重继承。如何设计兼容Java 7的类层次结构,遵循DRY原则(我想保持每个常量值只在一个地方定义)?

3 个答案:

答案 0 :(得分:1)

不要在Exception类中放入太多信息。拥有你提到的属性ExamRestServiceException是完全可以的。如果要禁止某些属性组合,请将构造函数设为私有并使用工厂方法,例如

 ...
 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);
 }
 ....

编辑: 如果您想要比仅通过文档更好地指导API用户,您当然可以提供专门的工厂方法,例如

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

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

甚至为每个错误条件提供一种方法。顺便说一下,有一个完全可用的HttpRetryException,你可能想要重用而不是自己滚动。如果(并且只有!)你担心你的异常类变得太笨重,你应该考虑将它分成多个类。

答案 1 :(得分:1)

llogiq的另一种方法是拥有异常构建器或工厂。

这(a)提供了一致的异常,(b)如果开发人员需要检查代码,则将代码保存在一个地方(c)使开发人员免于了解如何构建个别异常不需要知道。

答案 2 :(得分:1)

检查异常的关键方面是您可以捕获它们并从状态中恢复。

例外情况不应该通过名称传递原因。如果是400或404,则无法恢复,因此应用程序应记录状态并中断。

ApiException类更适合启动。

如果你害怕产生很多代码克隆。您可以创建一个util类来收集所有案例。

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.");
       }

    }

稍后当您开发应用程序时,您可能会更改此组合允许您执行此操作的异常设计。当您使用继承而不是组合时,您的代码变成了中继事物耦合,这会抑制变更和重用的可能性。