db4o Developer Community

db4o open source object database, native to Java and .NET
Welcome to db4o Developer Community Sign in | Join
in Search
More Search Options

Product News from the Core Team

This blog features product news right from the core developer team, once new features and functions get checked into Subversion, available as Continuous Build every 2 hours.

From Transparent Activation To Transparent Persistence

Activation has been discussed in depth (no pun intended) before.

Transparent Activation is db4o's ability to defer loading an object's state until just before it's accessed for the first time.

Transparent Activation relieves programmers from keeping track of which objects must be completely available before an operation can be executed.

That's a great relief already but there's a lot more to be done in transparency land. For one thing, programmers still have to keep track of which objects are modified so they can be properly stored to the database after every operation.

Let's have a closer look at the issue in the context of a simple object model that represents the bidirectional association between users and groups:

 

 1:
2: /// <summary>
3: /// A Group has a name and a list of users that belong to it.
4: /// </summary>
5: public class Group
6: {
7: private string _name;
8: private List<User> _users = new List<User>();
9:
10: public Group(string name)
11: {
12: _name = name;
13: }
14:
15: public string Name
16: {
17: get { return _name; }
18: }
19:
20: public IEnumerable<User> Users
21: {
22: get { return _users; }
23: }
24:
25: public void AddUser(User user)
26: {
27: if (_users.Contains(user)) return;
28:
29: user.AddedToGroup(this);
30: _users.Add(user);
31: }
32:
33: public override string ToString()
34: {
35: return _name;
36: }
37: }
38:
39: /// <summary>
40: /// An User has a name and a list of group it belongs to.
41: /// </summary>
42: public class User
43: {
44: private string _name;
45: private List<Group> _groups = new List<Group>();
46:
47: public User(string name)
48: {
49: _name = name;
50: }
51:
52: public string Name
53: {
54: get { return _name; }
55: }
56:
57: public IEnumerable<Group> Groups
58: {
59: get { return _groups; }
60: }
61:
62: public override string ToString()
63: {
64: return _name;
65: }
66:
67: /// <summary>
68: /// Notifies the user it was added to a group.
69: /// </summary>
70: internal void AddedToGroup(Group g)
71: {
72: _groups.Add(g);
73: }
74: }
75:

Around that simple model we're going to build a console application that allows us to:

  1. register a new user
  2. register a new group
  3. add an user to a group
  4. list all the groups
  5. list all the users

We'll first develop the complete application without worrying too much about persistence.

For the sake of minimizing the places we have to touch as the application evolves we're going to structure it around the Model-View-Controller pattern.

We already have the model.

The view renders the console output and interacts with the user:

ConsoleView

Whenever the user asks the application to do something useful like registering a new object, the view simply gathers the data and ask the controller to execute the operation:

 

 1:
2: private void AddUserToGroup()
3: {
4: User user = SelectUser();
5: Group group = SelectGroup();
6: _controller.AddUserToGroup(user, group);
7: }
8:
9: private Group SelectGroup()
10: {
11: return ConsoleUI.SelectObject(_controller.Groups);
12: }
13:
14: private User SelectUser()
15: {
16: return ConsoleUI.SelectObject(_controller.Users);
17: }
18:

The controller keeps track of the objects and sends them the right messages at the right times:

 

 1:
2: internal class ApplicationController
3: {
4: private List<User> _users = new List<User>();
5: private List<Group> _groups = new List<Group>();
6:
7: public void AddUser(User user)
8: {
9: _users.Add(user);
10: }
11:
12: public void AddGroup(Group group)
13: {
14: _groups.Add(group);
15: }
16:
17: public void AddUserToGroup(User user, Group group)
18: {
19: group.AddUser(user);
20: }
21:
22: public IEnumerable<User> Users
23: {
24: get { return _users; }
25: }
26:
27: public IEnumerable<Group> Groups
28: {
29: get { return _groups; }
30: }
31: }
32:

Our application is ready except that it doesn't remember anything from one session to the next.

Let's now add persistence to our controller. The steps are simple:

1) move all the controller's state to IObjectContainer

 

 1:
2: internal class ApplicationController : IDisposable
3: {
4: private readonly IObjectContainer _container;
5:
6: public ApplicationController()
7: {
8: _container = Db4oFactory.OpenFile("adhoc.db4o");
9: }
10:
11: public IEnumerable<User> Users
12: {
13: get { return _container.Query<User>(); }
14: }
15:
16: public IEnumerable<Group> Groups
17: {
18: get { return _container.Query<Group>(); }
19: }
20:
21: public void Dispose()
22: {
23: _container.Dispose();
24: }
25:

2) any time objects are added or changed, IObjectContainer.Store them:

 1:
2: public void AddUser(User user)
3: {
4: _container.Store(user);
5: _container.Commit();
6: }
7:
8: public void AddGroup(Group group)
9: {
10: _container.Store(group);
11: _container.Commit();
12: }
13:

There are tricky scenarios though. Consider how to add persistence to our controller's AddUserToGroup method. We see two objects are involved in the operation:

1:
2: public void AddUserToGroup(User user, Group group)
3: {
4: group.AddUser(user);
5: }
6:

So one could assume the right persistence code would be something like:

 

1:
2: public void AddUserToGroup(User user, Group group)
3: {
4: group.AddUser(user);
5: _container.Store(user);
6: _container.Store(group);
7: _container.Commit();
8: }
9:

Which is not the case because of something called Update Depth, how deep db4o will go into the object graph persisting state. The default Update Depth is 1 which means only the immediate members of user and group get persisted in the code above. The _groups collection inside User doesn't get its members persisted just as the _users collection inside Group doesn't get its members persisted. Our changes are lost.

We know everything about the implementation of Group.AddUser so we can actually write:

 

1:
2: public void AddUserToGroup(User user, Group group)
3: {
4: group.AddUser(user);
5: _container.Ext().Store(user, 2);
6: _container.Ext().Store(group, 2);
7: _container.Commit();
8: }
9:

And that will work. The collection members will get persisted this time.

Explicitly stating the required update depth for each operation can quickly lead to code that it's very fragile to changes in the object model.

db4o provides a way to eliminate the duplication of knowledge at each call site through configuration options:

 

1:
2: IConfiguration config = Db4oFactory.NewConfiguration();
3: config.ObjectClass(typeof(User)).UpdateDepth(2);
4: config.ObjectClass(typeof(Group)).UpdateDepth(2);
5:
6: _container = Db4oFactory.OpenFile(config, "adhoc.db4o");
7:

The controller code is still dependent upon the internals of the object model but the dependency is now confined to a single place. Better.

Both approaches are still not optimal though. They violate encapsulation and incur the risk of storing much more state than it would actually be necessary - an important performance consideration.

Taking AddUserToGroup as an example we can see that the user and group objects are not changed but only the collections they hold.

We can do better.

The Unit of Work pattern as documented in the Patterns of Enterprise Application Architecture:

Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

A simplistic but useful UnitOfWork can be implemented in a few lines of code:

 

 1:
2: public class UnitOfWork
3: {
4: /// <summary>
5: /// Adds the object to the list of affected objects in the current
6: /// unit of work.
7: /// </summary>
8: /// <param name="o">the affected object</param>
9: public static void Affected(object o)
10: {
11: List<object> current = UnitOfWork._current;
12: if (current == null) return;
13: if (current.Contains(o)) return;
14: current.Add(o);
15: }
16:
17: public delegate void Block();
18:
19: /// <summary>
20: /// Runs a block of code in the scope of a unit of work and returns the
21: /// list of affected objects.
22: /// </summary>
23: /// <param name="code"></param>
24: /// <returns></returns>
25: public static List<object> Run(Block code)
26: {
27: List<object> affectedList = new List<object>();
28: List<object> saved = _current;
29: _current = affectedList;
30: try
31: {
32: code();
33: }
34: finally
35: {
36: _current = saved;
37: }
38: return affectedList;
39: }
40:
41: [ThreadStatic] private static List<object> _current;
42: }
43:

A list of affected objects is kept in a thread local variable. The object model collaborates with the UnitOfWork class to keep the affected list current:

 

 1:
2: public class Group
3: {
4: ...
5:
6: public void AddUser(User user)
7: {
8: if (_users.Contains(user)) return;
9:
10: user.AddedToGroup(this);
11: _users.Add(user);
12:
13: UnitOfWork.Affected(_users);
14: }
15: }
16:
17: public class User
18: {
19: ...
20:
21: internal void AddedToGroup(Group g)
22: {
23: _groups.Add(g);
24:
25: UnitOfWork.Affected(_groups);
26: }
27: }
28:

 

The controller executes the operation in the scope of a UnitOfWork and gets a list of affected objects which can be easily persisted:

 

 1:
2: public void AddUserToGroup(User user, Group group)
3: {
4: List<object> affected = UnitOfWork.Run(delegate { group.AddUser(user); });
5: foreach (object o in affected)
6: {
7: _container.Store(o);
8: }
9: _container.Commit();
10: }
11:
12:

Group and User are now free to evolve independently of ApplicationController. As long as the UnitOfWork protocol is respected the application will work.

Additionally only those objects actually affected by the operation are ever persisted. A welcome performance improvement.

Can we still do better?

Quoting Dijkstra:

if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent"

Could we spend less lines?

By employing some form of metaprogramming such as bytecode engineering we could insert the right UnitOfWork calls into the model classes. We could even go an extra mile and integrate the UnitOfWork machinery with db4o transactions. In this hypothetical world, AddUserToGroup would be as simple as:

 

1:
2: public void AddUserToGroup(User user, Group group)
3: {
4: group.AddUser(user);
5: _container.Commit();
6: }
7:

 

Enter Transparent Persistence.

The latest version of db4o ships with enhancements to the Transparent Activation framework that include the ability to detect when an object retrieved from an IObjectContainer is about to be changed. It also includes a few collection classes that play well with this framework. Together with db4o's bytecode enhancement tool's (Db4oTool.exe) ability to inject the required boilerplate code we get to spend much less lines.

Let's go back to the first version of our application now, the version written with no persistence concerns and reconsider the steps of making it persistent in the light of Transparent Persistence:

1) move all the controller's state to IObjectContainer: same as before

2) configure transparent persistence:

 

 1:
2: public ApplicationController()
3: {
4: _container = Db4oFactory.OpenFile(TransparentConfiguration(), "transparent.db4o");
5: }
6:
7: private IConfiguration TransparentConfiguration()
8: {
9: IConfiguration configuration = Db4oFactory.NewConfiguration();
10: configuration.Add(new TransparentPersistenceSupport());
11: return configuration;
12: }
13:

 

3) use tranparent persistence aware collections:

 

 1:using Db4objects.Db4o.Collections;
2:
3:namespace UGMan6000.Transparent.Model
4:{
5: public class Group
6: {
7: private ArrayList4<User> _users = new ArrayList4<User>();
8:
9: ...
10: }
11:
12: public class User
13: {
14: private ArrayList4<Group> _groups = new ArrayList4<Group>();
15:
16: ...
17:
18: }
19:}

 

4) use Db4oTool to enhance the model:

PostBuild Event

Db4oTool.exe can be found in the bin directory of the db4o distribution. In the screenshot above we can see how to configure it as a Visual Studio Post-build task that enhances the generated assembly for transparent persistence (-tp) touching only the code in a specific namespace (-by-name UGMan6000.Transparent.Model).

As an exercise to the reader a quick look through Reflector's eyes should unveil how transparency is achieved.

The complete applications for each one of the design strategies discussed in this post is available here.

So what do you think?

Published Thursday, January 10, 2008 8:32 PM by Rodrigo B. de Oliveira
Attachment(s): TransparentPersistenceBlog.zip

Comments

 

Carl Rosenberger said:

What do *I* think?

You did a very nice unit of work. :-)

We still have a couple of these units to go to integrate things nicely with standard collections and then this will be really niiiiiice.

January 15, 2008 7:00 PM
 

Andrei.hu said:

That's what I missed most!!! This level of transparency makes db4o far more powerful. Can't wait to test it. Will it be released in the near future? We're considering the purchase of an object db (db4o or FastObjects) and that feature is definitely a plus.

Good work, two thumbs up!

January 16, 2008 3:40 PM
 

BrettM said:

Rodrigo,

Great post.  It really shows the power of what you're trying to achieve.

Brett

January 16, 2008 4:22 PM
 

Zvonimir Vanjak said:

Well, I've been looking for SIMPLE solution for persisting .NET object models for years !

I have great hopes for Microsoft's Entity Framework, but this post convinced me to give db4o a deeeep look :-) And I'm pretty sure I'll like it.

Great job guys.

January 22, 2008 9:43 PM
 

Product News from the Core Team said:

Bytecode analysis and enhancement becomes increasingly important as we strive to improve on persistence

February 11, 2008 5:17 PM
 

db4o Newsletter said:

The efficiency of Transparent Persistence is added to .NET LINQ provider New “Stable” Release 6.4 is

May 22, 2008 10:17 PM
 

elshinnawy said:

Was this ever integrated to work with the standard 1.1 Collections?

I'm having some hard time integrating TA/TP in my 1.1 project.

June 23, 2008 9:34 AM
 

elshinnawy said:

Was this ever integrated to work with the standard 1.1 Collections?

I'm having some hard time integrating TA/TP in my 1.1 project.

June 23, 2008 10:15 AM
 

Product News from the Core Team said:

Most of those who pair programmed with me know that I'm not very keen of debugging code by stepping through

July 29, 2008 1:26 AM
 

db4o Newsletter said:

Articles Object Manager Enterprise (OME) for Java and .NET Released Final Month for Performance Challenge

August 6, 2008 8:42 AM
 

stanlick said:

Is there no way to simply leverage updateDepth(noMax) on the Configuration and bypass the bytecode manipulation?  My employer frowns on modifying tested class files in the way.

Peace,

Scott

August 10, 2008 1:46 PM
 

Product News from the Core Team said:

Now is the time for the newer and better Stable version - 7.4 . As usual it is available in download

December 1, 2008 3:51 PM
 

jimitndiaye said:

Is it absolutely necessary to use the db4o persistence-aware collections in order to use Transparent Activation?

April 25, 2009 7:06 AM
Anonymous comments are disabled

This Blog

Syndication RSS Feeds

News

Get the latest features every 2 hours with the Continuous Build!