0

Suppose I'm doing some DDD. Now, I have a microservice reflecting a bounded context/a part of a bounded context.

Now, suppose there is a REST endpoint:

'/somedomainmodel/someaction'

My API user is doing a POST request with somehow invalid data. I'm using Java and assume that following chain is executed:

  • request is handled by a Spring controller or something else

  • controller is invoking a method on an application service

  • application service is doing some action on a domain model

But as I mentioned before, data provided by an user is somehow incorrect and could cause my domain object to get into invalid state. So a subclass of DomainException extends RuntimeException is thrown.

Now: how should I handle this? Suppose my endpoint is used by a frontend and I want to display a proper message to user.

Should I return a status code and a message in a response? Where should it be detected and how? In an application service? Or maybe in a controller? And since it's not a checked exception, how should I handle it? Try/Catch for a runtime exception seems to be awful.

Maybe if we're talking about specifing implementation, should I use something like spring's @ExceptionHandler?

1

5 Answers 5

2

I prefer to do something like this:

  1. The rest end point will receive an request with data then,
  2. Business service class is called with the data
  3. Validate the data if invalid throw an exception with proper description, e.g.: throw new InvalidArgumentException("invalid objet Id");
  4. On controller if above exception is thrown then return 400 Bad Request with error description e.g.: {"ok":false, "error":"invalid_argument","msg":"invalid objet Id"}

By doing this way the business class can be used anywhere and response can be handled accordingly.

0

My belief is that you can just send the response from the error handler or test the data before sending if possible. Can also use a preload step during app initialization from your REST interface and store valid value parameters and use those on your client to test data going out.

It would be simple to try to send a response from your handler. Garbage collection will clean up i.e. errors are costly. So a better design is client cleaning. Hope that helps.

1
  • So you think I should basically send a response with error code and maybe a reason in json from my error handler? Validating data before sending a request, on a client side doesn't make any sense - consider a case when you try to participate in a specific event, but you are already assigned for another event at the sime time - I don't want this validation to be done on client side. And maybe there is another, domain restriction which states you cannot participate in specific event - how would you check it on client side?
    – slnowak
    Commented Feb 16, 2015 at 11:15
0

You should return a 4xx error with a detailed description of the reason the request failed in the response body.

Can't help with the Java implementation. In many Python frameworks you would throw a 4xx exception as soon as your code has determined that the HTTP request is going to fail and this is caught by the framework and returns a 4xx HTTP status message.

0

Recently I wrote a detailed post on these and some related questions.

Should I return a status code and a message in a response?

Yes, but message should not contain exception message unless that message was crafted specifically to be shown to end user. Since you talk about DomainException, which probably should be quite specific about explaining what happened and why, you may show this exception message to a user. However, I recommend you to separate domain-level exceptions from API-level exceptions, and make latter be 100% "user-safe", so you can show them to end user without worrying it contains something like "MySQLIntegrityConstraintViolationException".

API-level exceptions should look like this:

// Assume that all those exception classes extend some base class `ApiException`

throw new ApiNotFoundException(404, "Entity with id=" + id + " not found.");

throw new ApiDatabaseUnavailableException(502, "Database server is unavailable. Retry later.")

throw new ApiConstraintViolationException(422, // WebDAV, Unprocessable Entity
    "Entity with id=" + id + " cannot be deleted " +
    "because it is a parent of entity with id=" + childId + ". "
    "Delete all children first.");

Where should it be detected and how?

There are several layers in application where different types of exceptions whould be handled. Basic idea is: handle standard Java exceptions (NPEs, IllegalArgument, etc.) as soon as possible, wrap them into something more specific and throw further. Then later you can handle domain-specific exception in a middle layer, and finally API-specific exceptions can all be handled in a single "catch-them-all" handler/interceptor.

See Logging Exceptions: Where to Log Exceptions? and my post for details.

And since it's not a checked exception, how should I handle it? Try/Catch for a runtime exception seems to be awful.

try/catch is just fine if you catch exceptions at the right level. The main mistake I see people make (which initially made me write about it) is catching exceptions too far up in the call stack from the place they've been thrown - basically, too "late". In such cases people try to guess what happened by examining exception type and/or pattern-matching a message. That was indeed horrible when I saw NPE was interpreted as "item not found in the database" (it actually was just random null somewhere in between).

Maybe if we're talking about specifing implementation, should I use something like spring's @ExceptionHandler?

You should use that kind of things as well, but only for API/domain-level exceptions.

0

data provided by an user is somehow incorrect and could cause my domain object to get into invalid state. So a subclass of DomainException extends RuntimeException is thrown.

Perfect.

Now: how should I handle this? Suppose my endpoint is used by a frontend and I want to display a proper message to user.

Catch the runtime exception at the fault boundary, and use the type and contents of the exception to craft an appropriate response to return to the client.

The fault boundary probably belongs out where the request is being handled, and you have awareness of things like the expected media type.

It wouldn't be completely unreasonable to have a fault boundary in your command handler, to package the DomainException as an ApplicationException, depending on how you feel about the domain leaking into the http layer. Since the validation of the command happens in the application component, you'll presumably want to be able to throw exceptions from there too. Both of these ApplicationExceptions would then be handled by the fault boundary in the component handling the http request.

And since it's not a checked exception, how should I handle it? Try/Catch for a runtime exception seems to be awful.

Embrace the truth that checked exceptions are an experiment that didn't really pan out; catch and report the problem.

DomainExceptions should be reported with 4xx status codes (ie: Client errors). 409-Conflict is the one I would prefer when the problem is a violation of the business invariant.

You are effectively telling the client that it can probably fix the problem (for example: get a fresh view of the aggregate state, and then modify the command so that it doesn't conflict). So it would be reasonable to include in your hypermedia response a link to a readable representation of the aggregate state, for example. Which would imply that the exception handling needs to happen in a place in your code that knows how to construct that link from the information in the request.

In pseudo code, the whole thing probably looks like

try {
    // If this bit fails, we want to return a 400-Bad Request to the client.
    Command<A> command = parse(httpRequest);

    // We got this far, so now we pass the command we've parsed off to the
    // command handler....
    {
        // The aggregate lookup could fail; that might be any of
        // 400-Bad Request
        // 404-Not Found
        // 410-Gone
        // ... depending on how you interpret the relationship between the
        // *resource* and the *aggregate*
        Aggregate<A> aggregate = Repository<A>.getById(command.id);

        // A failure here is probably best reported via 409-Conflict;
        // essentially to inform the user that the application state
        // is stale, and should be refreshed
        aggregate.execute(command);

        // A failure here indicates that the command lost a data race;
        // that's certainly a conflict.  So if you are going to report
        // an error at this point, 409-Conflict is the right way to go.
        // A better choice might be to retry the command, though; after
        // all, the stale version of the aggregate thought it was acceptable
        // so the updated version might as well.  In that case, you would
        // catch this exception and reload the aggregate.
        Repository<A>.save(aggregate);
    }

    // OK, we got through everything without a problem, so report success
    reportSuccess(httpResponse);
} catch (...) {
    // Possibly a single handler, or multiple handlers, depending upon
    // how you design your exception hierarchy.  The important thing is that
    // the exception handler here knows how to translate the application
    // exceptions thrown above into something suitable for http, and the
    // media types being used by this endpoint.
    exceptionHandler.forRequest(httpRequest).report(e, httpResponse);
}

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.