Bamboo

This is a rough rendering of a page from the old Prevayler wiki. Please see the new wiki for current documentation.

Bamboo.Prevalence, a .NET object prevalence engine.
http://bbooprevalence.sourceforge.net
Language: C#

One thing I would like to point out is the introduction of query objects: entities that model queries to the system's state. Query objects are very similar to command objects with the following notable differences:
# query objects don't change the state of the system
# and because of 1) - query objects don't need to be serializable (there's no need to write them to the command log)

Since the engine knows that query objects will not change the state of the system during execution it can use a more performant locking strategy - the reader/writer lock idiom - thus allowing multiple query objects to execute in paralel. The process is really simple and goes like this:
* before a query object is allowed to execute the engine must acquire a reader lock on the system
* before a command object is allowed to execute the engine must acquire a writer lock on the system

Thoughts?

Rodrigo B. de Oliveira


It is cool to have other people experiment with different prevalence styles. Diversity is life. --KlausWuestefeld


Does the writer lock wait for all reader locks to be returned, then holds them until done writing?  I'd like to see that in the Java version.
JonathanCarlson

That's exactly how it works.
RodrigoOliveira


I thought you would like to know that the latest release of
Bamboo.Prevalence includes a totally transparent prevalence engine. What
I mean by that is: you don't need to write command and/or query objects anymore if you don't want to. The engine can intercept the calls to your system class and automagically transform them in command/query calls. Of course, there's a
performance penalty here but I didn't measure it just yet.

Follows the code for the AddingSystem class with transparent prevalence:

[Serializable]
public AddingSystem : System.MarshalByRefObject
{
    private int _total;

    public int Total
    {
        get
        {
            return _total;
        }
    }

    public void Add(int amount)
    {
        _total += amount;
    }
}

Yep, that's pretty much it. Property accessor are treated as if being query
objects and public method calls are treated as commands (you can use
attributes to change the default behavior though). The secret lies in the
client code:

PrevalenceEngine engine =
PrevalenceActivator.CreateTransparentEngine(typeof(AddingSystem), "data");
AddingSystem system = (AddingSystem)engine.PrevalentSystem;
system.Add(10);
Console.WriteLine(system.Total);

A similar feature could be added to prevayler by using java.lang.reflect.Proxy.

RodrigoOliveira

I've implemented exactly this thing as a sample in Nanning. I'm using it successfully at two of my projects, it's really the most transparent and performant persistence solution I've seen.
JonTirsen


I presume the wrapping is done recursively, not only at the first level. How do you guys deal with object identities? If I query for the same object twice, will I get the same wrapper or will I get two different wrappers containing the same delegate? --KlausWuestefeld

In Bamboo.Prevalence only the first level, the PrevalentSystem itself, is wrapped by a proxy. This is intentional and it's similar to the design pattern Remote Facade (document by Martin Fowler somewhere). It's just a way to save some typing on creating all those command and query objects. --RodrigoOliveira

How do commands find the objects they operate on? Is every business object given an ID? --KlausWuestefeld

Every business object is given an ID at instantiation. (In Nanning you can intercept/advise an object-instantiation.) The PrevalentSystem must implement IdentifyingSystem (extends PrevalentSystem) which has methods like: registerOID, getObjectWithID and getIDForObject. The object must either be advisable according to Nanning (all public methods in an interface and instantiated via factory) or must extend a base-class. Only objects that is used as arguments to commands needs to be identifiable. Which methods are supposed to be Prevayler-commands are indicated so with runtime attributes (also a feature of Nanning).
--JonTirsen

For the systems I've written, yes, a GUID (System.Guid) is given to each top level object. --RodrigoOliveira

What is a "top level" object? What is a GUID? Is that ID transaparently given to the object? Does its class have to extend a specific class? --KlausWuestefeld

Hmmm... Maybe top level object is not a good description, sorry. What I mean by top level object is: any object that the application might need to find directly. Maybe some code will make clearer:

class Enquete
{
   private System.Guid _id;

   private string _text;

   private ArrayList _choices;

   public Enquete(string text)
   {
      _id = System.Guid.NewGuid(); // this wil create a new UUID
      _text = text;
   }

   public System.Guid ID
   {
       get { return _id; }
   }

   pubic string Text
   {
      get { return _text; }
   }

   public EnqueteChoice[] Choices
   {
      get { return (EnqueteChoice[])_choices.ToArray(typeof(EnqueteChoice)); }
   }

   public void AddChoice(EnqueteChoice choice)
   {
      _choices.Add(choice);
   }

   public void Vote(int choice)
   {
      ((EnqueteChoice)_choices[choice]).Vote();
   }
}

class EnqueteChoice
{
   private string _text;

   private int _votes;

   public EnqueteChoice(string text)
   {
      _text = text;
      _votes = 0;
   }

   public string Text
   {
      get { return _text; }
   }

   public int Votes
   {
       get { return _votes; }
   }

   public void Vote()
   {
      ++_votes;
   }
}


Enquete objects are the ones the UI operate on... So they need a ID. EnqueteChoice objects are dependent objects, they don't need an ID.

The prevalent system would include operations such as:

public void AddEnquete(Enquete enquete);

public Enquete GetEnquete(System.Guid enqueteID);

pubic void Vote(System.Guid enqueteID, int choice);

Does it make it clear? Thoughts?