8

I am using Spring for a web application. To validate a user's input in a form such as for creating a Person entity I use JSR 303 validation to check for not null/empty or valid patterns etc.. Some things however I check in the service layer such as making sure a field is unique in the database and if not throwing an exception. I catch this exception in the controller and add an error to the field.

This is basically what Ben J is doing in Option 1 here https://stackoverflow.com/questions/3224749/passing-errors-back-to-the-view-from-the-service-layer

Service Layer

public Person createPerson(String username, String fullName, String specialCode)
        throws DuplicateUsernameException {
    // Check if username exists and throw exception otherwise create person.
    if (checkUsernameExists(username)) {
        throw new DuplicateUsernameException();
    }
    Person person = new Person(username, fullName, specialCode);
    return personDao.create(person);
}

Controller

void createPerson(Form form, Errors errors) {

    try {
        service.createPerson(form.getUsername(), form.getFullName(),
                form.getSpecialCode());
    } catch (DuplicateUsernameException e) {
        errors.add("username", "This username exists");
    }

    // render view
    ...
}

Up until now this has served me well but I now have a requirement to check two fields are individually unique and want to inform the user. In the above example if I wanted to check the specialCode was also unique I could add another check below the existing one in the service layer and throw a separate DuplicateSpecialCodeException.

Service Layer

public Person createPerson(String username, String fullName, String specialCode)
        throws DuplicateUsernameException, DuplicateSpecialCodeException {
    if (checkUsernameExists(username)) {
        throw new DuplicateUsernameException();
    }
    if (checkSpecialCodeExists(specialCode)) {
        throw new DuplicateSpecialCodeException();
    }
    Person person = new Person(username, fullName, specialCode);
    return personDao.create(person);
}

Controller

void createPerson(Form form, Errors errors) {

    try {
        service.createPerson(form.getUsername(), form.getFullName(),
                form.getSpecialCode());
    } catch (DuplicateUsernameException e) {
        errors.add("username", "This username exists");
    } catch (DuplicateSpecialCodeException e) {
        errors.add("specialCode", "This special code exists");
    }

    // render view
    ...
}

The problem is if the user submits a duplicate Username and a duplicate SpecialCode they will only be informed of the duplicate Username. I want to shown both fields.

What is a neat way of doing this?

My ideas so far are:

  • Throw an exception that contains all the errors.
  • Pass the Errors object to the service and add errors to this.

Both of these methods would rely on the service layer knowing about the name of the fields and a suitable error code which seems to go against keeping the layers separate.

1 Answer 1

2

In most languages, exceptions are classes that work just like any other class. You can add properties or methods to them. So why not allow a data structure or even another kind of error DTO or a list of error DTOs to be passed in the constructor of an UnableToCreatePersonException() ? You could even define an interface like IMultipleErrorsException that some of your exceptions implement that has a method like getAllErrors() that returns the list of error DTOs. Each error DTO might have a key for a translation that contains an appropriate error message, and the Controller's responsibility then becomes ensuring that these messages get passed back to the user in the appropriate language. Or the DTO could be a more sophisticated type that can handle the mapping from service layer exceptions to fields and error messages.

1
  • I have added the errors to a single exception and it is working a treat. Thanks.
    – Goose
    Commented May 16, 2014 at 13:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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