Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add injection point for handling exceptions #742

Merged
merged 5 commits into from
Jul 18, 2018
Merged

Conversation

archolewa
Copy link
Contributor

-- Before we hard-coded how we handled exceptions in the Servlets.
For example, by default we return a 400 - Bad Request if we get an
Exception. However, this is very inflexible. For example, suppose
customers have some customizations (like their own SearchProviders) that
may generate errors the DataServlet doesn't know to look for, but aren't
the fault of the user. Then, Fili currently returns a 400, even though
it should probably be some sort of 5xx.

So we add an injection point to all servlets that allows customers to
provide their own logic for handling DataServlet errors.

@archolewa archolewa force-pushed the customizeException branch 2 times, most recently from ae5b873 to 279a162 Compare July 13, 2018 17:01
@yahoo yahoo deleted a comment Jul 13, 2018
@yahoo yahoo deleted a comment Jul 13, 2018
@yahoo yahoo deleted a comment Jul 13, 2018
Copy link
Contributor

@QubitPi QubitPi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change log needed


/**
* Allows a customer to inject custom code for handling Exceptions in Fili's DataServlet.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a <p> tag to start the next paragraph

* For example, customers may want to return 500's for some custom exceptions, or return
* an empty set when dimension rows are not found.
*/
public interface ExceptionHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this interface used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is vestigial, and should be removed.

* For example, customers may want to return 500's for some custom exceptions, or return
* an empty set when dimension rows are not found.
*/
public interface DataExceptionHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we make DataExceptionHandler and MetadataExceptionHandler separate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of how the two servlets work. The DataServlet uses asynchronous responses, so we need to pass in the asyncResponse for this method to resume.

The metadata servlets meanwhile use synchronous responses (if you look at their get* methods, you'll see that they return a Response), so we need to do the same thing in the Metadata interface.

Kinda hard to put both of those behaviors into one interface.

* For example, customers may want to return 500's for some custom exceptions, or return
* an empty set when dimension rows are not found.
*/
public interface ExceptionHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is orphaned?

*/
Response handleThrowable(
Throwable e,
Optional<ApiRequest> request,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be ? extends ApiRequest allowing the subclasses to be typesafer?

Copy link
Contributor

@QubitPi QubitPi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should've commented instead of requesting changes. Please let @michael-mclawhorn take a look as well

* a result, there is not a clean way to unify them into a single exception handler. So they each
* get their own
*/
public interface MetadataExceptionHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can have a marker interface ExceptionHandler whose child interfaces are MetadataExceptionHandler and DataExceptionHandler

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like unnecessary infrastructure to me. Since both interfaces
have such different signatures, we'll still need to either use the
(Meta)DataExceptionHandler in the appropriate Servlet, or we'll have to cast.
So I don't know what such a marker interface buys us.


/**
* The default implementation of {@link DataExceptionHandler}.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<p>

String msg = ErrorMessageFormat.REQUEST_PROCESSING_EXCEPTION.format(e.getMessage());
LOG.error(msg, e);
responseSender = () -> Response.status(Status.BAD_REQUEST).entity(msg).build();
return response;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I used to have no idea what responseSender is for

@@ -41,10 +41,9 @@ public FiliJobsExceptionHandler(ObjectMappersSuite objectMappers) {
@Override
public Response handleThrowable(
Throwable e,
Optional<ApiRequest> request,
Optional<? extends ApiRequest> request,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that these would become more type specific. e.g. Optional etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you're asking. So far as I know, you can't make a type more specific when implementing an interface method. Are you suggesting providing a separate interface for each metadata endpoint?

@yahoo yahoo deleted a comment Jul 17, 2018
@yahoo yahoo deleted a comment Jul 17, 2018
@yahoo yahoo deleted a comment Jul 17, 2018
);
// Generally, it's expected that implementations of `ExceptionHandler` will resume
// the response in every case. This exists so that if someone writes a buggy handler
// that fails to resume the response, they at least get *something* back.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'just in case' asynch response resume should check if the response is already done and if it /isn't/ send a message indicating that an error was mishandled. (A 500 error, probably, also passing along the error that was badly handled)

Copy link
Contributor

@michael-mclawhorn michael-mclawhorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restated below.

Copy link
Contributor

@michael-mclawhorn michael-mclawhorn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing may want for a quick test, maybe directly on dataservlet.getData(), initialize with a bunch of Mocks, throw an NPE early in processing the request, and then have an error handler for NPE catch and respond to it correctly.

Also, now that the error handlers exist externally, it would be very easy to write regression tests instantiating them and stimulating their various exception handling scenarios. It would also be a hint to people building their own to make tests on them.

@yahoo yahoo deleted a comment Jul 17, 2018
@yahoo yahoo deleted a comment Jul 17, 2018
@yahoo yahoo deleted a comment Jul 17, 2018
@archolewa
Copy link
Contributor Author

@michael-mclawhorn I'm hesitant to do a direct test on getData like that, because that will be a very brittle test. Every time somebody adds a new injection point, that test will break even if it has nothing to do error handling, because we'll need to build a DataServlet. Same if/when we add additional query parameters, and modify getData. Meanwhile, we already have the ErrorDataServletSpec which forces and tests a variety of error situations in a more stable fashion.

The only thing the ErrorServlet isn't going to test directly (though it does exercise) is the failsafe response if someone misses a step, so that's the only thing that could benefit from such a test.

I'm really hesitant to add a brittle test to test something that doesn't happen in the default implementation.

That being said, I have no problem adding unit tests for the exception handlers.

@yahoo yahoo deleted a comment Jul 18, 2018
@yahoo yahoo deleted a comment Jul 18, 2018
@yahoo yahoo deleted a comment Jul 18, 2018
Andrew Cholewa added 3 commits July 18, 2018 14:42
-- Before we hard-coded how we handled exceptions in the Servlets.
For example, by default we return a 400 - Bad Request if we get an
Exception. However, this is very inflexible. For example, suppose
customers have some customizations (like their own SearchProviders) that
may generate errors the DataServlet doesn't know to look for, but aren't
the fault of the user. Then, Fili currently returns a 400, even though
it should probably be some sort of 5xx.

So we add an injection point to all servlets that allows customers to
provide their own logic for handling DataServlet errors.
-- Also, removes the unnecessary `metadataEntityName` parameter from the
`MetadataExceptionHandler`. That was used to pass along the dimension
name for one of the error messages in the `DimensionsServlet`, but the
`ApiRequest` contains the dimension name already.
-- Use `DataApiRequest` rather than `? extends ApiRequest` in
`DataExceptionHandler`.

-- Improves some Javadoc.
Andrew Cholewa added 2 commits July 18, 2018 14:42
-- Tests have been added for unit tests, but not the direct test on
`DataServlet` that Michael recommended because of how brittle such a
test would be.
@archolewa archolewa force-pushed the customizeException branch from f6e0efd to cf978a4 Compare July 18, 2018 19:43
@archolewa
Copy link
Contributor Author

Added some tests, as requested by @michael-mclawhorn. Note that I very explicitly did not try to merge the tests under a shared specification, despite them having some shared tests. The implementations are all completely separate, and I think the shared implementation are a coincidence, and not because of any inherent relationship. Also, these days I like keeping my tests self-contained (navigating Specification trees is annoying) if I can, and the shared tests are simple enough that I don't think we got a whole lot out of trying to share them anyway.

@yahoo yahoo deleted a comment Jul 18, 2018
@yahoo yahoo deleted a comment Jul 18, 2018
@yahoo yahoo deleted a comment Jul 18, 2018
@archolewa archolewa merged commit 2173d9a into master Jul 18, 2018
@archolewa archolewa deleted the customizeException branch July 18, 2018 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants