For Prevayler users
Some of the topics below assume you have some knowledge of Prevayler
Database
Please note, Prevayler is a special object-oriented, in-memory
database. It has advantages like:
- simplicity of use,
- speed of accessing your
data,
- speed for searching your data,
- data recovery after failure,
- transactions
But it may have one disadvantage for your project: your data may not fit
in memory to use it. Actual 64-bit architecture allows to use a lot of data.
Are present JVMs prepared for memory hungry applications? Many people still
don't know.
But facts are: my friends run ~100K of business objects on 32-bit
machine and they fit in 2G of RAM.
I run a simple application consisting of 13.000 objects and with historical
information of changes on the objects. JMeter load test of concurrent users
searching data in the application just rocks!
The choice is yours..
How it happens that it works?
The answer is simple. Imagine there is only one, very generic command
(
transaction):
org.nthx.pat.GenericCommand.
It's purpose is just like any other Prevayler command.
- stores information of objects which participate in transaction (*)
- run 'execute' method, which involves your business logic
(*) - stores
OIDs instead of
BOs
Another part of systems takes care of storing/creating BOs themselves.
Data saved in GenericCommand consists of three parts:
- business method name - command name
- target object on which method is invoked (OID only)
- arguments passed to business method (OIDs only)
Main ideas:
- First snapshot of data is taken right after construction
of ROOT object
- Every BO is put into IdentityMap
and stored within command right after construction
- Every BO in argument list of a transaction is translated (recovered)
to simple OID and OID is stored instead - prevents "baptism" problem
With this data saved, with proper order of every command executed, Prevayler
users may safely use PAT - you're able to write ZERO Prevayler's commands
(transaction).
Assume that I pass my object graph's root as an argument to transacted
method. What then? It's insane to store it automatically!
Yes, that's true. That is why PAT tests whether argument is one of your
business objects or not
(you've specified
@@pat.bo inside
BO's class
comment for it, or
@@pat.root for
ROOT).
If so, then I do not simply store arguments in
GenericCommand.
Instead I store only his
OID (object identifier). During
deserialising I restore original object based on his OID and pass it to
method.
Keep in mind you cannot simply have
any arguments in transactions.
Where do you get OID from?
AOP allows you to use so called 'introductions'. JBossAOP does this by
so called Mixins.
I introduced IdentityMap into your root class. Keys in the map are OIDs of
objects, and values are real objects from your world - business objects.
Do I have to set OIDs by myself?
No. PAT framework does this for you.
During creation of every BO PAT attaches OID to it and puts object into
IdentityMap.
So the IdentityMap must be quite big..
Yes. It is. Number of objects it holds is even bigger than number of your
business objects. This is so, because _every_
BO is put there. Even those
which you create only temporarily, or those that are not needed by you.
There is feature of removing them from IdentityMap (
WeakHashMap might help - but it isn't Serializable)
Can I get OID for my object?
Yes, you can. Although it is not necessary to use them or even to know about
them.
Cast any of your @@pat.bo'ed objects to
org.nthx.pat.Identifiable and get its OID.
((Identifiable) myObject).getOID();
java.lang.reflect
Because I've chosen to make only one command (
transaction) - generic one,
there must be some way to recover data from it and execute business logic
- your original annotated method.
There is no other way as through reflection. So: reflection will be used
during reading (deserialising) of commands (_transactions_).