Monday, January 21, 2008

Managing session state in persitent objects







On my current web project, like most web projects, needed a way of managing user session state. We wanted a clean separation from the web tier and a way of managing warnings or annotations to the supplied user data incase the data was not valid.


We decided to store information supplied in a GET or POST request in document objects. These document objects would then be used by the service tiers to perform the requested operation. Each document object was kept simple by only allowing fields/attributes. The documents would be validated by a validator for each document type, the validators using a utiltity class of validations to keep the duplicate code count low. The documents can be presisted for long running user transactions.


Perhaps the best way to illustrate how this works is with an example. Lets assume that we have a request handler for a registration service that takes a map of name/value pairs for the values the user has supplied on the registration form. For simpliity the response and error handling paths have been ignored.







import java.util.Map;

public class RegisterRequestHandler {
private RegistrationService registrationService;

public RegisterRequestHandler(RegistrationService registrationService) {

this.registrationService = registrationService;
}

public void handlePost(Map<String, String> params) {
RegisterDocument document = readDocument(params);

RegisterDocumentValidator validator = new RegisterDocumentValidator();

if (validator.validate(document)) {
registrationService.register(document);
redirectToSuccessfulConfirmation();
} else {
persistDocument(document);
redirectToRegisterPath();
}

}

private void redirectToSuccessfulConfirmation() {
// ...
}

private void persistDocument(RegisterDocument document) {
// ...
}

private void redirectToRegisterPath() {
// ...
}

private RegisterDocument readDocument(Map<String, String> params) {
RegisterDocument registerDocument = new RegisterDocument();
registerDocument.name = new Field(params.get("name"));
registerDocument.password = new Field(params.get("password"));
registerDocument.userName = new Field(params.get("username"));

return registerDocument;
}
}



The handlePost entry point turns the name/value pair of parameters into a registraion document representing the users application for an account. The document is validated and if valid a request for a new account is passed on the appropriate service. If the document is not valid the user is redirected to the registraion application form with any warnings identified int he persisted registrtion document.



The registration document contains the fields we expect from the UI and very simple helper methods.






public class RegisterDocument {
Field userName;
Field password;
Field name;

public boolean hasWarning() {
return userName.hasWarning() || password.hasWarning() || name.hasWarning();
}
}



The field class manages the value and any warnings.







public class Field {
private String value;
private Annotation annotation;

public Field(String value, Annotation annotation) {
this.value = value;
this.annotation = annotation;
}

public Field(String value) {
this(value, Annotation.none);
}

public String getValue() {
return value;
}

public void addWarning(String warning) {
annotation = new Annotation(warning, Annotation.AnnotationType.warning);
}

public boolean hasWarning() {
return annotation.getType() == Annotation.AnnotationType.warning;
}
}



Perhpas choosing the most overloaded name (Annotation) this class models the validators response. Allowing warnings or infomation to be associated with the value. Used to report problems back to the user.






public class Annotation {
enum AnnotationType {
warning, info, none
}

private AnnotationType type;
private String value;

public static Annotation none = new Annotation("", AnnotationType.none);

public Annotation(String value, AnnotationType type) {
this.value = value;
this.type = type;
}


public AnnotationType getType() {
return type;
}
}







public class RegisterDocumentValidator {
public boolean validate(RegisterDocument document) {
if (isEmpty(document.name.getValue())) {
document.name.addWarning("You must supply a name");
}

// ...

return document.hasWarning();
}

private boolean isEmpty(String name) {
return (name == null || name.length() == 0);
}
}



Any services use the documents as parameters. They are decoupled from the UI and can be more easily unit tested.





public interface RegistrationService {
void register(RegisterDocument document);
}









No comments: