I am working on a quite a big Spring Boot web service and I wanted a standardized and simplified way to handle responses and exceptions by following fluent API design.
So this is what I did and it works as I expected.
For handling responses, first i created an interface
like this,
public interface BaseResponse {
public short code(); //holds API specific code
public HttpStatus httpStatus(); // holds corresponding HTTP status code
}
And then i have created an enum
which implements above BaseResponse
interface,
All the response codes are defined here.
public enum ResponseType implements BaseResponse {
SUCCESS((short) 10000, HttpStatus.OK),
INVALID_FILE_TYPE((short) 10001, HttpStatus.BAD_REQUEST),
//rest...
}
Here's the response object (ApiResponse
) definition,
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse {
//holds http status code
@JsonProperty("httpStatus")
private short httpStatus;
//holds API response code
@JsonProperty("code")
private short code;
//holds data return from API
@JsonProperty("data")
private Map<String, Object> data;
//holds errors return from API, if error response
@JsonProperty("error")
private Map<String, Object> error;
public static ErrorResponseBuilder error(ApiError err) {
return new ErrorResponseBuilder(err);
}
public static SuccessResponseBuilder success() {
return new SuccessResponseBuilder();
}
/**
*
*/
public static class ErrorResponseBuilder {
private ApiError err;
public ErrorResponseBuilder(ApiError err) {
this.err = err;
}
//returns error API response
public ApiResponse build() {
Map<String, Object> d = new HashMap<>();
d.put("error", err.getMessage());
short httpStatus = (short) err.getType().httpStatus().value();
return new ApiResponse(
httpStatus,
err.getType().code(),
null,
d
);
}
}
/**
*
*/
public static class SuccessResponseBuilder {
private Map<String, Object> pd;
public SuccessResponseBuilder() {
pd = new HashMap<>();
}
public SuccessResponseBuilder attr(String key, Object value) {
pd.put(key, value);
return this;
}
//returns success API response
public ApiResponse build(BaseResponse type) {
short httpStatus = (short) type.httpStatus().value();
return new ApiResponse(
httpStatus,
type.code(),
pd,
null
);
}
}
//this is for logging
@Override
public String toString() {
return new StringBuilder()
.append("httpStatus:").append(getHttpStatus()).append(", ")
.append("code:").append(getCode()).append(", ")
.append("data:").append(getData()).append(", ")
.append("error:").append(getError())
.toString();
}
}
Then i use this object to create responses like below,
Success response
//call `@Service` method, and get result of it
String fileId = this.fileHandlerService.storeFile(file);
//create success response
ApiResponse apiRes = ApiResponse.success()
.attr("fileId", fileId)
.build(ResponseType.SUCCESS);
//return HTTP response with 'apiRes' in body
return ResponseEntity.status(apiRes.getHttpStatus()).body(apiRes);
This results in below json
response,
{
"httpStatus": 200,
"code": 10000,
"data": {
"fileId": "a251cb619dc145f684b9682bcb503a5c"
}
}
Error response
To handle exception responses, i created a Runtime Exception
called ApiException
. This is being used to handle every exceptional cases. For example: user-not-found, inactive-user, invalid-file-type, etc...
ApiError
class was created for hold API error details,
public class ApiError {
private BaseResponse type;
private String message;
private String debugMessage;
private Exception exception;
}
ApiException
public class ApiException extends RuntimeException {
private ApiError apiError;
//boilerplate code omitted
}
Use this exception like this,
throw new ApiException(ApiError.builder()
.type(ResponseType.INVALID_FILE_TYPE)
.message("Invalid file, please upload ms-excel file")
.debugMessage("expected file extension:xlsx, but found:" + fileExt)
.build()
);
This results in below json
response,
{
"httpStatus": 400,
"code": 10001,
"error": {
"error": "Invalid file, please upload ms-excel file"
}
}
This is the mechanism used every where in the web service.
- Is this a good way to handle response ?
- How can i improve it?
data
field of success response, i can add objects, arrays, strings etc... \$\endgroup\$jackson-databind
package has to be added in the starting spring boot configuration, have you it in your configuration ? \$\endgroup\$