简体   繁体   English

Java SDK for REST API服务的错误处理

[英]Error Handling on Java SDK for REST API service

We are building a Java SDK to simplify the access to one of our services that provide a REST API. 我们正在构建Java SDK以简化对提供REST API的服务的访问。 This SDK is to be used by 3rd-party developers. 此SDK将由第三方开发人员使用。 I am struggling to find the best pattern to implement the error handling in the SDK that better fits the Java language. 我很难找到最适合在Java语言中实现错误处理的模式。

Let's say we have the rest endpoint: GET /photos/{photoId} . 假设我们有其余的端点: GET /photos/{photoId} This may return the following HTTP status codes: 这可能会返回以下HTTP状态代码:

  • 401 : The user is not authenticated 401:用户未经过身份验证
  • 403 : The user does not have permission to access this photo 403:用户无权访问此照片
  • 404 : There's no photo with that id 404:没有带有该ID的照片

The service looks something like this: 该服务看起来像这样:

interface RestService {   
    public Photo getPhoto(String photoID);
} 

In the code above I am not addressing the error handling yet. 在上面的代码中,我还没有解决错误处理问题。 I obviously want to provide a way for the client of the sdk to know which error happened, to potentially recover from it. 我显然希望为sdk的客户端提供一种方法来知道发生了哪个错误,以便从中恢复。 Error handling in Java is done using Exceptions, so let's go with that. Java中的错误处理是使用Exceptions完成的,所以让我们继续。 However, what is the best way to do this using exceptions? 但是,使用异常执行此操作的最佳方法是什么?

1. Have a single exception with information about the error. 1.有一个关于错误信息的例外。

public Photo getPhoto(String photoID) throws RestServiceException;

public class RestServiceException extends Exception {
    int statusCode;

    ...
}

The client of the sdk could then do something like this: 然后sdk的客户端可以执行以下操作:

try {
    Photo photo = getPhoto("photo1");
}
catch(RestServiceException e) {
    swtich(e.getStatusCode()) {
        case 401 : handleUnauthenticated(); break;
        case 403 : handleUnauthorized(); break;
        case 404 : handleNotFound(); break;
    }
}

However I don't really like this solution mainly for 2 reasons: 但是我真的不喜欢这个解决方案主要有两个原因:

  • By looking at the method's signature the developer has no idea what kind of error situations he may need to handle. 通过查看方法的签名,开发人员不知道他可能需要处理哪种错误情况。
  • The developer needs to deal directly with the HTTP status codes and know what they mean in the context of this method (obviously if they are correctly used, a lot of the times the meaning is known, however that may not always be the case). 开发人员需要直接处理HTTP状态代码并知道它们在此方法的上下文中的含义(显然,如果它们被正确使用,很多时候已知含义,但情况可能并非总是如此)。

2. Have a class hierarchy of errors 2.有一个错误的类层次结构

The method signature remains: 方法签名仍然是:

public Photo getPhoto(String photoID) throws RestServiceException;

But now we create exceptions for each error type: 但现在我们为每种错误类型创建例外:

public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;

Now the client of the SDK could then do something like this: 现在,SDK的客户端可以执行以下操作:

try {
    Photo photo = getPhoto("photo1");
}
catch(UnauthenticatedException e) {
    handleUnauthorized();
}
catch(UnauthorizedException e) {
    handleUnauthenticated();
}
catch(NotFoundException e) {
    handleNotFound();
}

With this approach the developer does not need to know about the HTTP status codes that generated the errors, he only has to handle Java Exceptions. 使用这种方法,开发人员不需要知道生成错误的HTTP状态代码,他只需要处理Java异常。 Another advantage is that the developer may only catch the exceptions he wants to handle (unlike the previous situation where it would have to catch the single Exception ( RestServiceException ) and only then decide if he wants to deal with it or not). 另一个优点是开发人员可能只捕获他想要处理的异常(不像以前的情况,它必须捕获单个异常( RestServiceException ),然后才决定是否要处理它)。

However, there's still one problem. 但是,还有一个问题。 By looking at the method's signature the developer still has no idea about the kind of errors he may need to handle because we only have the super class in the method's signature. 通过查看方法的签名,开发人员仍然不知道他可能需要处理的错误类型,因为我们在方法的签名中只有超类。

3. Have a class hierarchy of errors + list them in the method's signature 3.有一个错误的类层次结构+在方法的签名中列出它们

Ok, so what comes to mind now is to change the method's signature to: 好的,现在想到的是将方法的签名更改为:

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;

However, it is possible that in the future new error situations could be added to this rest endpoint. 但是,将来可能会将新的错误情况添加到此休止端点。 That would mean adding a new Exception to the method's signature and that would be a breaking change to the java api. 这意味着在方法的签名中添加一个新的Exception,这将是对java api的重大改变。 We would like to have a more robust solution that would not result in breaking changes to the api in the situation described. 我们希望有一个更强大的解决方案,在所描述的情况下不会导致对api的更改。

4. Have a class hierarchy of errors (using Unchecked exceptions) + list them in the method's signature 4.具有错误的类层次结构(使用未经检查的例外)+在方法的签名中列出它们

So, what about Unchecked exceptions? 那么,未经检查的异常呢? If we change the RestServiceException to extend the RuntimeException: 如果我们更改RestServiceException以扩展RuntimeException:

public class RestServiceException extends RuntimeException

And we keep the method's signature: 我们保留方法的签名:

public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;

This way I can add new exceptions to the method's signature without breaking existing code. 这样我就可以在不破坏现有代码的情况下为方法的签名添加新的异常。 However, with this solution the developer is not forced to catch any exception and won't notice that there are error situations he needs to handle until he carefully reads the documentation (yeah, right!) or noticed the Exceptions that are in the method's signature. 但是,使用此解决方案,开发人员不会被强制捕获任何异常,并且不会注意到他需要处理的错误情况,直到他仔细阅读文档(是的,正确!)或注意到方法签名中的异常。

What's the best practice for error handling in these kind of situations? 在这种情况下,错误处理的最佳实践是什么?

Are there other (better) alternatives to the ones I mentioned? 我提到的那些还有其他(更好的)替代方案吗?

Exception handling alternatives: Callbacks 异常处理备选方案:回调

I don't know if it's a better alternative, but you could use callbacks. 我不知道它是否是更好的选择,但你可以使用回调。 You can make some methods optional by providing a default implementation. 您可以通过提供默认实现来使某些方法成为可选方法。 Take a look to this: 看看这个:

    /**
     * Example 1.
     * Some callbacks will be always executed even if they fail or 
     * not, all the request will finish.
     * */
    RestRequest request = RestRequest.get("http://myserver.com/photos/31", 
        Photo.class, new RestCallback(){

            //I know that this error could be triggered, so I override the method.
            @Override
            public void onUnauthorized() {
                //Handle this error, maybe pop up a login windows (?)
            }

            //I always must override this method.
            @Override
            public void onFinish () {
                //Do some UI updates...
            }

        }).send();

This is how the callback class looks like: 这就是回调类的样子:

public abstract class RestCallback {

    public void onUnauthorized() {
        //Override this method is optional.
    }

    public abstract void onFinish(); //Override this method is obligatory.


    public void onError() {
        //Override this method is optional.
    }

    public void onBadParamsError() {
        //Override this method is optional.
    }

}

Doing something like this you could define an request life-cycle, and manage every state of the request. 做这样的事情你可以定义一个请求生命周期,并管理请求的每个状态。 You can make some methods optional to implement or not. 您可以使某些方法可选,以实现与否。 You can get some general errors and give the chance at the user to implements the handling, like in the onError. 您可以获得一些常规错误,并让用户有机会实现处理,就像在onError中一样。

How can I define clearly what exceptions handle? 如何清楚地定义哪些异常处理?

If you ask me, the best approach is draw the life-cycle of the request, something like this: 如果你问我,最好的方法是绘制请求的生命周期,如下所示:

示例异常生命周期

This is only a poor example, but the important it's keep in mind that all the methods implementation, could be or not, optionals. 这只是一个糟糕的例子,但重要的是要记住所有方法的实现,可能是或不是,可选项。 If onAuthenticationError is obligatory, not neccesarily the onBadUsername will be too, and viceversa. 如果onAuthenticationError是强制性的,那么onBadUsername也不是必须的,反之亦然。 This is the point that makes this callbacks so flexible. 这使得这个回调变得如此灵活。

And how I implement the Http client? 我是如何实现Http客户端的?

Well I don't know much about http clients, I always use the apache HttpClient, but there's not a lot of differences between the http clients, the most have a little more or a little fewer features, but in the end, they are all just the same. 好吧,我对http客户端了解不多,我总是使用apache HttpClient,但http客户端之间没有太多差异,最多的是功能稍微多一点,但最后,它们都是一样的。 Just pick up the http method, put the url, the params, and send. 只需拿起http方法,输入网址,参数,然后发送即可。 For this example I will use the apache HttpClient 对于这个例子,我将使用apache HttpClient

public class RestRequest {
    Gson gson = new Gson();

    public <T> T post(String url, Class<T> clazz,
            List<NameValuePair> parameters, RestCallback callback) {
        // Create a new HttpClient and Post Header
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = new HttpPost(url);
        try {
            // Add your data
            httppost.setEntity(new UrlEncodedFormEntity(parameters));
            // Execute HTTP Post Request
            HttpResponse response = httpclient.execute(httppost);
            StringBuilder json = inputStreamToString(response.getEntity()
                    .getContent());
            T gsonObject = gson.fromJson(json.toString(), clazz);
            callback.onSuccess(); // Everything has gone OK
            return gsonObject;

        } catch (HttpResponseException e) {
            // Here are the http error codes!
            callback.onError();
            switch (e.getStatusCode()) {
            case 401:
                callback.onAuthorizationError();
                break;
            case 403:
                callback.onPermissionRefuse();
                break;
            case 404:
                callback.onNonExistingPhoto();
                break;
            }
            e.printStackTrace();
        } catch (ConnectTimeoutException e) {
            callback.onTimeOutError();
            e.printStackTrace();
        } catch (MalformedJsonException e) {
            callback.onMalformedJson();
        }
        return null;
    }

    // Fast Implementation
    private StringBuilder inputStreamToString(InputStream is)
            throws IOException {
        String line = "";
        StringBuilder total = new StringBuilder();

        // Wrap a BufferedReader around the InputStream
        BufferedReader rd = new BufferedReader(new InputStreamReader(is));

        // Read response until the end
        while ((line = rd.readLine()) != null) {
            total.append(line);
        }

        // Return full string
        return total;
    }

}

This is an example implementation of the RestRequest . 这是RestRequest的示例实现。 This is only one simple example, theres a lot of topics to discuss when you are making your own rest client. 这只是一个简单的例子,当你制作自己的休息客户时,有很多话题需要讨论。 For example, "what kind of json library use to parse?", "are you working for android or for java?" 例如,“用什么样的json库来解析?”,“你是在为android还是为java工作?” (this is important because I don't know if android supports some features of java 7 like multi-catch exceptions, and there's some technologies that isn't availabe for java or android and viceversa). (这很重要,因为我不知道android是否支持java 7的一些功能,如多捕获异常,并且有一些技术不适用于java或android和反之亦然)。

But the best that I can say you is code the sdk api in terms of the user, note that the lines to make the rest request are few. 但我能说的最好的就是根据用户编写sdk api的代码,请注意用于生成其余请求的行很少。

Hope this helps! 希望这可以帮助! Bye :] 再见:]

It seems you are doing things by "hand". 看来你正在“手”做事。 I would recommend you0 give a try to Apache CXF . 我建议你试试Apache CXF

It's a neat implementation the JAX-RS API that enables you to almost forget about REST. 这是一个简洁的JAX-RS API实现 ,使您几乎可以忘记REST。 It plays well with (also recommended) Spring . 它适用于(也推荐) Spring

You simply write classes that implement your interfaces (API). 您只需编写实现接口(API)的类。 What you need to do is to annotate the methods and parameters of your interfaces with JAX-RS annotations. 您需要做的是使用JAX-RS注释注释接口的方法和参数。

Then, CXF does the magic. 然后,CXF发挥了魔力。

You throw normal Exceptions in your java code, and then use exception mapper on server/nd or client to translate between them and HTTP Status code. 您在Java代码中抛出正常的异常,然后在server / nd或client上使用异常映射器在它们和HTTP状态代码之间进行转换。

This way, on server/Java client side, you only deal with regular 100% Java exception, and CXF handles the HTTP for you: You have both the benefits of a clear REST API and a Java Client ready to be used by your users. 这样,在服务器/ Java客户端,您只处理常规的100%Java异常,并且CXF为您处理HTTP:您既可以使用明确的REST API,也可以使用Java Client,供用户使用。

The client can either be generated from your WDSL, or built at runtime from introspection of the interface annotations. 客户端既可以从WDSL生成,也可以在运行时从界面注释的内省构建。

See : 见:

  1. http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Exceptionhandling http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Exceptionhandling
  2. http://cxf.apache.org/docs/how-do-i-develop-a-client.html http://cxf.apache.org/docs/how-do-i-develop-a-client.html

In our application, we have defined and mapped a set of error codes and their counterpart Exceptions : 在我们的应用程序中,我们定义并映射了一组错误代码及其对应的异常:

  • 4XX Expected / Functional excecption (like bad arguments, empty sets, etc) 4XX预期/功能例外(如错误的参数,空集等)
  • 5XX Unexpected / Unrecovable RunTimeException for internal errors that "should not happen" 对于“不应该发生”的内部错误,出现5XX Unexpected / Unrecovable RunTimeException

It follows both REST and Java standards. 它遵循REST和Java标准。

I've seen libraries that combine your suggestions 2 and 3, eg 我见过结合你的建议2和3的库,例如

public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;

This way, when you add a new checked exception that extends RestServiceException , you're not changing the method's contract and any code using it still compiles. 这样,当您添加一个扩展RestServiceException的新的已检查异常时,您不会更改方法的合同,并且使用它的任何代码仍然会编译。

Compared to a callback or unchecked exception solution, an advantage is that this ensures your new error will be handled by the client code, even if it's only as a general error. 与回调或未经检查的异常解决方案相比,优点是这可确保客户端代码处理新错误,即使它仅作为一般错误。 In a callback, nothing would happen, and with an unchecked exception, your client application might crash. 在回调中,什么都不会发生,并且在未经检查的异常情况下,您的客户端应用程序可能会崩溃。

The solution may vary depending on your needs. 解决方案可能因您的需求而异。

  • If it is supposed that there could appear unpredictable new exception types in the future, your second solution with checked exception hierarchy and method that throw their superclass RestServiceException is the best one. 如果假设将来可能出现不可预测的新异常类型,那么带有已检查异常层次结构的第二个解决方案和抛出其超类RestServiceException是最好的。 All known subclasses should be listed in the javadoc like Subclasses: {@link UnauthenticatedException}, ... , to let developers know what kind of of exceptions there could hide. 所有已知的子类都应该像Subclasses: {@link UnauthenticatedException}, ...一样列在javadoc中Subclasses: {@link UnauthenticatedException}, ... ,让开发人员知道可以隐藏哪种异常。 It should be noticed that if some method could throw only few exceptions from this list, they should be described in the javadoc of this method using @throws . 应该注意的是,如果某个方法只能从该列表中抛出少量异常,则应使用@throws在此方法的javadoc中对它们进行描述。

  • This solution is also applicable in the case when all appearances of RestServiceException means that any of it's subclasses could hide behind it. 此解决方案也适用于RestServiceException所有外观意味着它的任何子类可以隐藏在它后面的情况。 But in this case, if RestServiceException subclasses hasn't their specific fields and methods, your first solution is preferrable, but with some modifications: 但在这种情况下,如果RestServiceException子类没有其特定的字段和方法,那么您的第一个解决方案是可取的,但需要进行一些修改:

     public class RestServiceException extends Exception { private final Type type; public Type getType(); ... public static enum Type { UNAUTHENTICATED, UNAUTHORISED, NOT_FOUND; } } 
  • Also there is a good practice to create alternative method that will throw unchecked exception that wraps RestServiceException exeption itself for usage within 'all-or-nothing' business logic. 此外,还有一个很好的做法是创建替代方法,该方法将抛出未经检查的异常,该异常将RestServiceException异常包装在“全有或全无”业务逻辑中。

     public Photo getPhotoUnchecked(String photoID) { try { return getPhoto(photoID); catch (RestServiceException ex) { throw new RestServiceUncheckedException(ex); } } 

It all comes down to how informative your API error responses are. 这一切都取决于您的API错误响应的信息量。 The more informative the error handling of the API is, the more informative the exception handling can be. API的错误处理信息越多,异常处理的信息就越多。 I would believe the exceptions would only be as informative as the errors returned from the API. 我相信例外只会像从API返回的错误一样提供信息。

Example: 例:

{ "status":404,"code":2001,"message":"Photo could not be found."}

Following your first suggestion, if the Exception contained both the status and the API error code, the developer has a better understanding of what he needs to do and more option when it comes to exception handling. 根据您的第一个建议,如果Exception包含状态和API错误代码,那么开发人员可以更好地了解他需要做什么以及在异常处理方面有更多选择。 If the exception also contained the error message that was returned, as well, the developer shouldn't even need to reference the documentation. 如果异常还包含返回的错误消息,那么开发人员甚至不需要引用该文档。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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