I hate to say it, but it's not that transparent. By that I simply mean that Prevayler will most definitely make you write your classes in a certain way, following a certain idiom. Sure, Prevayler doesn't require a bytecode enhancer, and it doesn't require you to extend a certain base class, and it doesn't require you to wrap proxies around everything. Still, the fact that your "Business Objects" have to be "deterministic", and any use case that will cause any state change in any Business Object must be called via a fully encapsulated Command object, presents its own quite unique set of design forces on programs that have a lot of interaction with the outside world.
Consider, for example, the example of creating a new mailbox (or folder) for an existing user. Three things need to happen:
- Check if a mailbox directory of this name already exists on the filesystem. If so, throw MailboxCreateException.
- Create the new mailbox directory.
- Register the new mailbox in the hashmap of existing mailboxes for this user.
Originally, I had these three concepts wrapped into a single
MailStore.addMailbox(String newName) method. But when I decided to use Prevayler to persist all metadata, such as the hashmap of mailboxes, and tried to turn this method into a CreateMailbox Command class, I realized that the first two steps couldn't be part of the command. It may not be obvious to all why this is so, so here's a detailed example:
Let's say that at midnight, a full snapshot A is taken. Between midnight and 9:00AM, many commands are executed and logged, including several CreateMailbox calls. Let's refer to the state of the world at this time as B. At 9:01AM, someone hits Ctrl-C and then restarts the server. Now, as soon as Prevayler starts up, all of those commands will be executed again against A in an effort to bring the world back to B. But it won't work, because the state of the filesystem is not part of A--it's part of the external environment.
In other words, re-executing the command log must operate only on the state of A and must bring it to a state precisely equal to B. In an application like an IMAP server, where filesystem changes and metadata changes are often tightly coupled, this leads to a slightly un-OO dichotomy between the two. The metadata management will happen in one package through one set of interfaces, and the filesystem management will happen in another, and only the layer above guarantees the two will be executed together correctly.
I was wondering if maybe one way to handle this would be to let the Command know whether it was being called for the first time or during recovery mode (or whatever they call rolling the changes into A). But that approach would definitely have its own set of pitfalls and probably lead to the most insidious bugs the world has ever known. :)
To put all this in perspective, though, I'm certainly still better off than using an RDBMS or plain Java serialization, in terms of both performance and simplicity. I'm very impressed by the sheer elegance of Prevayler's design and it's nice to know the performance will be orders of magnitude better than it needs to be. Yesterday was just a reality check for me--Prevayler is great, but it still makes its own set of demands on the developer, and those demands can be more significant than they first appear.
For all that Prevayler promises, I'll happily accept those demands. And for projects where external resources aren't tightly coupled to business objects, or a command architecture is already in place (like Struts), the price could be very low indeed.