Navigation

RSS 2.0 New Entries Syndication Feed Atom 0.3 New Entries Syndication Feed

Show blog menu v

 

General

Use it

Documentation

Support

Sibling projects

RIFE powered

Valid XHTML 1.0 Transitional

Valid CSS!

Blogs : Archives

< Videocast : Terracotta tuning and statistics recorder   Beware, Apple TV warranty isn't worldwide >
Gotcha: using RIFE's bean-centric validation with Terracotta (or any entity container/cache)

RIFE has a unique approach towards validation which is centralized around bean instances and not bean classes.

Instead of having an external validation service, each bean instance publishes meta data about its constraints. Every part of the system (validation, form generation, database structure creation, ...) will inspect the meta data and peruse it. Validation is an exception in that after inspecting the meta data, the results of the validation are contributed back for each specific bean instance as ValidationErrors. This again allows every part of the system to easily access the validation results and act accordingly.

Instead of implementing a container that keeps track of all the mappings between bean instances, their meta data and the validation errors, I decided that each bean instance will actually hold this data into fields. However, since people generally dont want to pollute their bean classes with framework-specific constructs, I created the meta-data merging facility. This automatically looks for a class with the MetaData suffix when a bean class is loaded. Through byte code instrumentation, the capabilities of the MetaData class are merged into the original class and you can safely cast each instance of the original class to any of the interfaces that the MetaData class implements. (you can find more details about this feature in the RIFE wiki)

All this is very intuitive when you use it in practice and develop a database-backed website. RIFE's database layer doesn't use an entity container either but rather creates and populates new bean instances each time an entity is fetched from the database. This has the benefit of each thread having a dedicated version of the entity which can be manipulated without risking data races. Only when all the operations are finished, the entity is stored back into the database through an explicit save call.

The code could be like this when editing an existing User entity:

User user = database.restore(userId);
fillSubmissionBean(user);
if (((Validated)user).validate()) {
    database.save(user);
    template.setBlock("content", "success");
} else {
    generateForm(template, user);
}
print(template);

However, when using Terracotta (or something else, like a cache), you typically don't want every entity to go back to the database. Instead, you want to keep them in memory and only store certain parts into the database. So instead of having a database manager, you have some kind of in-memory service that is able to find and store entities.

Intuitively and naively you would replace the code above with the following broken code:

// this code is broken, read below to know why
User user = service.findById(userId);
fillSubmissionBean(user);
if (((Validated)user).validate()) {
    service.store(user);
    template.setBlock("content", "success");
} else {
    generateForm(template, user);
}
print(template);

The problem relies in the fact that each and every thread will be working with the same instance of the User. Your initial reaction might be to just synchronize the accesses to this instance, but hang on, think about what's happening. You actually don't want every thread to have the same instance. You want the instances to be different and isolated, they all have to contain the data that was submitted through their respective web forms, and you all want them to have their own validation errors. This is exactly the same as what happens when you get a new instance from the database.

So, instead of retrieving the user from the service to modify it with possibly invalid data, you want to create a new instance of the entity that will be in local scope until the validation has succeeded. Only then you want to store it back into the service. This guarantees that your data is always correct and also allows perfectly concurrent access on users with the same identifier.

The correct code would thus be:

User user = getSubmissionBean(User.class);
user.setId(userId);
if (((Validated)user).validate()) {
    service.store(user);
    template.setBlock("content", "success");
} else {
    generateForm(template, user);
}
print(template);

This had me scratching my head when I was bitten by the broken code above. However, if you think about it, it's perfectly logical. Working with in-memory containers is fundamentally different from working with relational database, where your data is stored independently from the actual objects that you're using. You have to remember that an in-memory container is a store too, and you don't want invalid or intermediate results to end up in there. However, this is what you risk to happen when you directly manipulate the fields of the instances that are already in the in-memory container ... but it's oh so tempting to do!

posted by Geert Bevin in RIFE on Jul 16, 2008 9:37 AM : 1 comment [permalink]
 

Comments

Re: Gotcha: using RIFE's bean-centric validation with Terracotta (or any entity container/cache)
Geert -- thanks for this.

I agree that it is cleaner and safer to always let each thread use its own instance. In some cases, however, you may have different threads that need to change different fields of some kind of cached instance. You said, "Your initial reaction might be to just synchronize the accesses to this instance." I agree that this is unnecessary in the case that you presented, but it would work (if properly synchronized), right?

Is the only threat (other than the normal thread synchronization issues) that another thread might call valiate() on it and thus change the ValidationErrors? So keeping calls to validate() and accesses to ValidationErrors within the synchronized block would solve the problem?

Just making sure I'm not missing some other subtle danger, especially since I'm still trying to get my head fully around the meta-data merging facility.

Add a new comment

Comments on this blog entry have been closed.

< Videocast : Terracotta tuning and statistics recorder   Beware, Apple TV warranty isn't worldwide >
 
 
 
Google
rifers.org web