Russian Dolls and X-ray Specs
In an earlier entry titled ‘The Cache is a Shadow, Not a Box,’ I discussed how an incorrect mental image played havoc with Charlie. It may come as no surprise that another incorrect mental image has caused yet more problems.
The mental image I have of object-oriented inheritance is that of Russian Dolls. Here is a little bit of code to provide an example of how I see inheritance working:
public class MediumDoll { private Object SmallDoll; } public class LargeDoll : MediumDoll { }
To put the code into words, I see the private SmallDoll as being inside its containing MediumDoll. If LargeDoll inherits MediumDoll, then I see the SmallDoll as being deep within LargeDoll. Because SmallDoll is of private scope, LargeDoll cannot access it directly. But even if LargeDoll cannot access the SmallDoll, the SmallDoll is still inside the LargeDoll—but with the MediumDoll separating the two.
If you know the basics of reflection in .NET, you will know that it allows us to both see and use members that are otherwise hidden from view. Without reflection, SmallDoll would be completely hidden from any code outside of MediumDoll. But with reflection, we can see and use SmallDoll even from outside of MediumDoll. A completely unrelated class, such as ToyMaker, could employ reflection to see and use the SmallDoll that is otherwise hidden inside MediumDoll.
Because reflection gives our code x-ray specs, I believed that if I pointed reflection at the LargeDoll, I could see through to the SmallDoll. This is not the case.
Putting the analogy aside for a moment, what I was trying to do was rectify a weakness in Charlie’s Entity System. In writing Charlie’s First Code, I gave each entity an ID value, and a public method that changed that ID value.
namespace Charlie.Framework.Business { public class Entity { public Int32 Id { get { return this.id; } } private Int32 id = -1; //// While this method has the same scope (public) of the Id // property, we'll use a separate method to set the Id, as // the Id property is properly meant to be a read-only // property from the point of view of all consuming code. // While we could use reflection to "hide" this SetId method // as private, that seems like needless complexity. // public void SetID(Int32 id) { this.id = id; } } }
After an entity is retrieved from the database, its ID value should not be changed, which is why its ID property is read-only. But as an entity is being retrieved from the database, there needs to be some way to set that ID value. For this reason, I kept the ID property as read-only, but gave the class a separate SetID method. In the notes I admitted that this was a hack to avoid the complexity of using reflection to set an otherwise read-only value.
Inspired by the success of using reflection to inspect Charlie’s Bucket Full of Plugins, I decided to finally get rid of that little SetID hack.
Here then is a concrete example of the Russian Doll analogy:
public class Entity { private Int32 id; } public class Article : Entity { }
What I needed to do was update Charlie so that if it was given an Article that had been retrieved from the database, it could set the private id field. The id field is inside the Entity class, from which the Article class derives. To repeat, I believed that reflection would give Charlie the x-ray specs needed to see through the Article to the private id field hidden within. Here is a cut-down version of the code I tried:
FieldInfo field = article.GetType().GetField("id"); if (field != null) field.SetValue(entity, 3);
The code says, “For this article, grab the field named ‘id’, and set its value to 3.’ This did not work.
I firmly believed that if given an Article (or a LargeDoll), reflection should allow me to see through to the private id field (or a SmallDoll). My persistent mental image of reflection as x-ray specs meant that all of my debugging efforts were directed at the x-ray specs. There are many ways to fine-tune the GetField method shown above, including a promising-looking FlattenHierarchy option. Yet every attempt failed.
After failing continuously to set the private id field, I realized that my concept of reflection as x-ray specs must be wrong. (That seems obvious in retrospect, but we are typically unaware of the mental images that we are using.) So once I became aware that I was using a mental image of x-ray specs, I put it to one side and looked instead at the Russian dolls. Specifically, I changed the code to “open” the dolls until it got down to the tiny doll within, at which point the reflection would work to peek inside. Here is a simplified version of the code that did work.
Type type = article.GetType(); while (type != typeof(Entity)) { type = type.BaseType; } FieldInfo field = type.GetField("id"); if (field != null) field.SetValue(article, 3);
The emboldened code keeps opening a derived class until it gets down to the base Entity class (keeps opening dolls until it gets down to the MediumDoll), at which point the reflection can see inside and change a private member.
I am a little loathe to publish this weblog entry, as I feel sure that I am missing something. In reading about reflection, I have never before seen a discussion about unravelling derived classes in order to see and use private members. Still, this weblog represents an honest account of Charlie’s development, and this does represent another little step.
by Alister Jones |
----
A Bucket Full of Plugins
In the previous weblog entry I said that Charlie included a hack where the name of each plugin was hard-coded. The example given was as follows:
// Security Plugin ISecurityPlugin securityPlugin = (ISecurityPlugin)pluginFactory.InstantiatePlugin( "Charlie.Security.SecurityPlugin, Charlie.Security");
In the above code, the string parameter consists of two parts. The first part of ‘Charlie.Security.SecurityPlugin’ is the name of the class to load up, and the second part of ‘Charlie.Security’ is the name of the assembly in which this class can be found. With these two bits of information, Charlie can create an instance of the Security plugin.
An easy solution to removing the hack would be to relocate this string information to the Web.config file. In that way, the required string value is no longer hard-coded. But there is a problem with such a simple solution. The problem is that the Web project must still have intimate knowledge of how all of the plugins are structured. The Web project must know both the names of plugin classes and the names of the assemblies. This somewhat defeats the purpose of plugins, which is that Charlie can operate without intimate knowledge of its plugins.
I decided that each plugin would be given a friendly name (such as ‘Charlie.Security’), and each would be referenced by its friendly name. This removes the need for plugins to be referenced by class names and assembly names. In turn this allows plugins can change their structure without affecting Charlie’s ability to use them.
I started by creating a new PluginInfo attribute which, when applied, looks like this:
namespace Charlie.Security { [PluginInfo("Charlie.Security")] public class SecurityPlugin : ISecurityPlugin, IPlugin { } }
That’s all well and good, but how can Charlie find plugins when all it has is a friendly name, and does not have the assembly name or class name? I applied a technique I had seen previously in Sprocket. When Charlie first loads up, it inspects all the assemblies in the \bin folder, and picks out any classes that implement the IPlugin interface. Charlie then maintains a record of each plugin and the friendly name associated with that plugin. Now if Charlie is asked to use the ‘Custom.Security’ plugin, it knows which plugin goes by that friendly name. In other words, Charlie is no longer told the assembly names and class names of plugins to use, but now finds this information for itself. The cut-down version of the code looks like this:
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { foreach (Type type in assembly.GetTypes()) { if (type.GetInterface("IPlugin") != null) { ConstructorInfo constructor = type.GetConstructor(new Type[0]); Object[] attributes = type.GetCustomAttributes(typeof(PluginInfo), true); PluginInfo info = null; if (attributes != null && attributes.Length > 0) { info = (PluginInfo)attributes[0]; } String name = (info != null) ? info.Name : String.Empty; /* * At this point we know this type is a plugin, we know * its friendly name, and we have its constructor. * Charlie now stores the name and constructor as a * key/value pair. This means that Charlie can create * an instance of the plugin whenever it is passed its * friendly name. */ } } }
I then updated the Web.config file so that it passed in the friendly name of each plugin to use. Here is an example:
<add setting="DefaultSecurityPlugin" value="Charlie.Security" />
So what have we achieved here? Well, previously Charlie had to be told the name of the class to be used as its Security plugin, and the name of the assembly in which to find this class. If the class name or assembly name ever changed, Charlie would stop working. Now, Charlie only needs to know the friendly name of the Security plugin, and behind the scenes the plugin can change its name or assembly.
I then decided to push it one final step further. I noticed that all of the default plugins were Charlie plugins. A particular web site can specify another security plugin, such as ‘Customer.Security’, in the absence of which Charlie will default to its own plugin, which is ‘Charlie.Security’. I throught to myself, “Charlie knows the friendly names of its own default plugins—it does not have to be told these names.” So I removed the Web.config entries that specified the default plugins, and instead left it to Charlie to recognise one of its own. A particular web site can still specify an alternative, but otherwise Charlie uses its own default plugin.
So the hack has been removed. When it starts up, Charlie inspects the \bin folder and keeps a record of any plugins it finds. Charlie also recognises one of its own plugins, which it will use if a particular web site does not specify the use of an alternative plugin. Charlie no longer needs to be given a class name, assembly name, or friendly name. This is the sort of self-configuration that I am now trying to get Charlie to achieve. So far, so good.
by Alister Jones | Next up: Russian Dolls and X-ray Specs →
----
Charlie’s Own Two Feet
If you have been following my weblog for a while, you will know that it has been nearly three months since I last created an entry. The reason is that shortly after the last entry I accepted an invitation from Telligent to work on the www.asp.net website. In order to meet my new responsibilities, I needed to put Charlie to one side for a while. However I miss working on Charlie, and I would like to keep the project ticking along. And so we return to the story of Charlie.
Right now Charlie includes two hacks and suffers one nuisance. I have decided to address these issues as a way of getting back in to Charlie.
The first hack is that the names of the plugin assemblies are hard-coded. Here is an example:
// Security Plugin ISecurityPlugin securityPlugin = (ISecurityPlugin) pluginFactory.InstantiatePlugin( "Charlie.Security.SecurityPlugin, Charlie.Security");
The second hack is that while Charlie is designed to serve multiple websites from a single installation, a fudge exists to “feed” Charlie a single site ID value. Charlie should properly figure out the ID from the URL of the incoming request.
The one nuisance is that any change to Charlie’s database schema or database content requires making the change in two places. To start with I fire up Enterprise Manager and make the change (since Charlie currently has no administration screens). I then need to duplicate the change in the SQL installation scripts I maintain for the occasions in which I need a completely fresh database installation. This duplication of work has proven to be error-prone. I would like to be able to make the changes just in the installation scripts, and then force Charlie to perform a fresh database installation.
If I address these issues, Charlie will largely be able to self-configure. So let’s see if I can encourage Charlie to stand on its own two feet.
by Alister Jones | Next up: A Bucket Full of Plugins →
----