Wednesday, March 15, 2006

A notification strategy for business errors

In one of my past posts we talked about implementing specifications with Apache Commons Collections.
Recall: we used specifications for validating a requestGraduationThesis method of a Student object.
One common need, as emerged here, is to communicate the result of this validation in case something goes wrong.

That is, we need to transfer errors resulted from business methods to the layers above, the service or presentation layers.

Here, I propose an approach using a Notification object and a "business oriented" exception as a means of transporting.

A Notification object is a container of Error objects, carrying information about errors.
Here is a simple implementation:
public class Notification {

private List errors = new LinkedList();

public List getErrors() {
return this.errors;
}

public boolean hasErrors() {
return errors.isEmpty();
}


public void addError(Error error) {
this.errors.add(error);
}
}

public class Error {

private String message;

public Error() {}

public Error(String message) {
this.message = message;
}

public String getMessage() {
return this.message;
}

public void setMessage(String message) {
this.message = message;
}
}

Then, we need an exception for transporting our notification in the layers above, up till the layer interested in managing it:
public class StudentException extends Exception {

private Notification notification;

public StudentException(Notification notification) {
this.notification = notification;

}

public Notification getNotification() {
return this.notification;

}
}

Here, I used a checked exception because I think business errors should always be explicit, but if you think checked exception are too cluttering or invasive, you can safely use an unchecked one: I just suggest you to always declare your exceptions in javadocs.

Finally, we need to link together Errors with Specifications, and Notifications with Students:

public class EnoughCreditsSpecification extends BaseSpecification {


private static final int min = 10;
private static final String errorMessage = "credits.error";


private Error error;

public boolean isSatisfiedBy(Student s) {

if (s.getTotalCredits() >= EnoughCreditsSpecification.min) {

return true;
}
else {
error = new Error(EnoughCreditsSpecification.errorMessage);

return false;
}
}

public Error getError() {

return this.error;
}
}

public class EnoughCoursesSpecification extends BaseSpecification {


private static final int min = 3;
private static final String errorMessage = "courses.error";


private Error error;

public boolean isSatisfiedBy(Student s) {

if (s.getTotalCourses() >= EnoughCoursesSpecification.min) {

return true;
}
else {
error = new Error(EnoughCoursesSpecification.errorMessage);

return false;
}
}

public Error getError() {

return this.error;
}
}

public class Student {


private int totalCourses;
private int totalCredits;
private boolean thesisRequested;

private List> graduationSpecs;

/**
* Register an exam related to a given course.
* This increases the student credits.
*/

public void registerExam(Course course) {

this.totalCredits += course.getCredits();
this.totalCourses++;

}

/**
* Return true if this student can request a graduation thesis,
* depending on a given list of specifications.
*
* @throws StudentException If something with this student
* business method goes wrong ...
*/

public void requestGraduationThesis()
throws StudentException {

if (this.applyAllSpecs() == false) {

Notification n = new Notification();
for (BaseSpecification spec : this.graduationSpecs) {

if (spec.getError() != null) {

n.addError(spec.getError());
}
}

throw new StudentException(n);
}
else {

this.thesisRequested = true;
}
}

public int getTotalCredits() {

return this.totalCredits;
}

public int getTotalCourses() {

return this.totalCourses;
}

/**
* Get the list of specifications defining in which case a student can
* request a graduation thesis.
*/

public List> getGraduationSpecs() {

return graduationSpecs;
}

/**
* Set the list of specifications defining in which case a student can
* request a graduation thesis.
*/

public void setGraduationSpecs(List> graduationSpecs) {

this.graduationSpecs = graduationSpecs;
}

private boolean applyAllSpecs() {

boolean result = true;
for (BaseSpecification spec : this.graduationSpecs) {

if (!spec.isSatisfiedBy(this)) {
result = false;

}
}
return result;
}
}

Here is a simple main method for showing all this stuff:
public class TestStarter {

public static void main(String[] args) {

List<basespecification><student>> specs1 = new ArrayList();
specs1.add(new EnoughCoursesSpecification());

specs1.add(new EnoughCreditsSpecification());

Course c1 = new Course();

Course c2 = new Course();
c1.setCredits(5);

c2.setCredits(4);

Student s1 = new Student();

s1.setGraduationSpecs(specs1);
s1.registerExam(c1);
s1.registerExam(c2);


try {
s1.requestGraduationThesis();
}
catch(StudentException ex) {

Notification n = ex.getNotification();
List<error> errors = n.getErrors();

for (Error e : errors) {
System.out.println(e.getMessage());

}
}
}
}

Doing so, we still use wonderful specifications, avoiding the hard-to-manage validate() method, even if we need multiple error notifications to superior layers, and we do not write additional code for transporting them because we use existent language features.

An alternative implementation, as suggested by Martin Fowler, is to use event notification patterns.
We could explore this in another post.

In the meanwhile, what do you think about this?

Wednesday, March 01, 2006

Blog improvements

During these days I've spent some time in improving my blog.
Despite of how much difficult is to customize Blogger blogs, I've got very good results!
Here are the improvements:

  • A new three columns layout (found here), with two sidebars.

  • A "Categories" section in the left sidebar, for browsing my posts per categories, supported by the great del.icio.us service: it took me some hours to make this work, but now I'm very proud and glad!
    I think this is very useful, and I've started to tag every old post.

  • Again in the left sidebar, a section with comments related to most recent posts, specifically those posts in the main page: this makes following discussions a lot easier.

  • In the bottom side of the right sidebar, a link for adding my blog to your Technorati Favorites, for making easy searches in your favorites blogs.


Hope this will make our blog experience a lot better ... and funnier!