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:
- register a new user
- register a new group
- add an user to a group
- list all the groups
- 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:
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:

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?