2

Most often I need to call a service method from a controller, and based on the outcome, if there's an error show an appropriate error message depending on the error (or some other action, showing message is just an example), otherwise proceed.

A very simple way I am using is returning an Outcome object from such method calls. Something like these:

public interface Outcome {
    boolean success();
    String error;
}

public class Success implements Outcome {
    public boolean success() {
        return true;
    }

    public String error() {
        return "";
    }
}

public class Failure implements Outcome {
    String error; //or call it reason

    public Failure(String error) {
        this.error = error;
    }

    public boolean success() {
        return false;
    }

    public String error() {
        return error;
    }
}

And in controller, checking outcome.success(), and proceeding according to the result.

Is there a more elegant/oop way of doing this? Maybe like passing in success and failure callbacks to service methods as parameters, or some better way.


let me give you a concrete code example to clarify:

A new user, chooses a username for himself by writing it in a text field, then presses OK button.

  • SUCCESS: user is registered
  • FAIL: if there's already a user with the same name, user is prompted to choose a different name
  • FAIL: some other problem, let's say system is not accepting new registrations at the moment
  • FAIL: username is a word marked as offensive

as you see there are different outcomes for a method call, and for each outcome a different action must be taken.

public class UserService {
    public Outcome register(String username)...
}

public class RegistrationController {
    ...
    //somewhere inside controller, there is a handler for OK button's click event

    onclick...
    Outcome outcome = userService.register(username);
    if(outcome.success()) 
        ...
    else {
        if(outcome.error() == "OFFENSIVE_USERNAME"
        //... other outcomes
    }

    ...
}
  • 1
    Why not use the http status codes to know the request outcome? – linuxunil Dec 29 '16 at 16:41
  • @linuxunil He probably wants to know what went wrong. If you only get a 500 error, how do you know what went wrong exactly? – Jelle Dec 30 '16 at 10:20
  • but that is exactly the purpose of the http status codes. A 5XX error family means a server error. The client must read the status code and determine the next step. Every situation is covered in the RFC. You have families and members for each situation, be it and sucess or error. What happens if your client is not a human? You can't assume that in a rest ws. – linuxunil Dec 30 '16 at 11:54
  • 1
    it is not necessarily a http call. It's a generic service call, service as in a layered architecture, or domain driven design. – uylmz Dec 30 '16 at 12:54
  • i see you point @Reek. Even for that purpose - IMHO - Strings are not a good choice. Again, a human can read and decode the message, but this decodification for machines is not as easy. To be generic you need to provide a way for both human or machine to decode the error and determine the next step. On top of my head, why not wrap a throwable? That way you can subclass it the same way as http status codes does... – linuxunil Dec 30 '16 at 17:36
2

I had this question for REST a while ago, and I used this answer. It provides good information about the outcome of the service. It is also written in Java ;-)

With this design you can write your service methods like this: public AbstractResponse getAllUsers() {}. Also, because of the polymorphism, you can add other responses as well. For example, I needed an extra response for when a warning happens, and so I created a WarningResponse class.

Note that you can leave out the Status enum if you're using this in Java only. I converted my objects to JSON so I needed some kind of variable to show the status.

1

There is a common functional pattern to handle errors. It is usually called Either. Idea is to return an object that can be either a result or an error, but not to check any boolean fields in it and instead rely on the object itself to process both result and error:

public final class Either<Left, Right> {
  private final Optional<Left> left;
  private final Optional<Right> right;
  /** @param left - non null value for an erroneous outcome
   **/
  public static <Left, Right> Either<Left, Right> left(Left left) {
    return new Either<>(Optional.of(left), Optional.empty());
  }

  /** @param right - non null value for a normal outcome
   **/
  public static <Left, Right> Either<Left, Right> right(Right right) {
    return new Either<>(Optional.empty(), Optional.of(right));
  }

  /** Accepts handler for both potential outcomes, only one handler is executed
   * This is an imperative terminal operation
   **/
  public void forEach(Consumer<Left> leftConsumer, Consumer<Right> rightConsumer) {
    left.ifPresent(leftConsumer);
    right.ifPresent(rightConsumer);
  }

  /** Accepts processors for each outcome
   *  This implements a functional approach for handling errors
   * @param leftMapper, rightMapper - converters for each outome to target value of type T
   */
  public <T> map(Function<Left, T> leftMapper, Function<Right, T> rightMapper) {
    return left.map(leftMapper).orElseGet(() -> right.map(rightMapper).get());
  }

  private Either(Optional<Left> left, Optional<Right> right) {
    assert left.isPresent() ^ right.isPresent();
    this.left = left;
    this.right = right;
  }
}

class UsageExample {
    public String getUserOrError(int id) {
       return repository.getUser(id).map(
         error -> error.getMessage(), 
         user -> user.getName()
       );
    }
}

See how in usage example no checks are done - both error and normal outcome are covered in uniform, but typesafe way.

Is there an equivalent of Scala's Either in Java 8?

  • +1 Also see Futures for asynchronous calls. – Andres F. Dec 31 '16 at 22:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.