The Manager Convention
This stage in the development of Charlie concerned its plugins. Through a combination of the PluginManager, the PluginFactory, and the IPlugin interface, the base functionality was in place. It remained however to come up with a convention so that all the plugins worked in a similar way, even though they all would do different things.
Why was I seeking some sort of convention? Technically, there is no requirement for the plugins to be anything alike (with the single exception being that they all need to implement the simple IPlugin interface). The benefit of a convention is one of simplicity. To use an example, an application could choose to transport data around in many different forms. A particular application might transport some data as XML documents, some data as DataSets, and some data as business objects. While this would work, it makes the application more complex to write, more complex to read, and more complex to maintain. This is why developers are advised to pick one method of transporting data around in an application—to pick a data convention—and apply that convention in all situations possible. Similarly, I was seeking a plugin convention that could be applied to all plugins, thereby making the Plugin system as simple as possible.
I started by looking at the existing IPlugin interface:
public interface IPlugin { IWebModule[] GetModules(); IWebFilter[] GetFilters(); }
I was quite happy with this interface, because it was so simple. But I did notice something about this interface that was significantly different to another simple interface, the IHttpModule interface:
public interface IHttpModule { void Init(HttpApplication application); void Dispose(); }
A class that implements the IHttpModule interface is a “doer,” meaning that it does its own work in the two methods (both methods are void—they return nothing). Conversely, a class that implements the IPlugin interface is a not a doer, but instead returns objects that will do some work on its behalf (both methods return worker objects). It occurred to me that a simple convention would be for the plugins to never do work themselves, but to instead return objects that do work for them. It took only a little more scribbing on my graph paper to realise that I already had a candidate for what could be returned by the Plugins: the Managers from the Entity System.
I think it will help if I provide a concrete example of this Entity System.
Charlie’s security system will undoubtedly involve User objects and Role objects. Both types of objects need to be persisted, so they will inherit from the base Entity class (the definition of which is a persistable business object). In order for other classes in Charlie to load or save a User or Role object, it must make a call to the UserManager or RoleManager. We can zoom in on the SecurityPlugin, and see where these Managers are located using the Entity System developed.
Charlie needs to load and save the Users and Roles provided by the SecurityPlugin, and there are two main ways to do this.
First, the SecurityPlugin could expose all necessary methods itself:
public class SecurityPlugin : IPlugin { public User GetUserById(Int32 id) { return UserManager.GetUserById(id); } public void SaveUser(User user) { UserManager.SaveUser(user); } public Role GetRoleById(Int32 id) { return RoleManager.GetRoleById(id); } public void SaveRole(Role role) { RoleManager.SaveRole(role); } IWebModule[] GetModules() { return null; } IWebFilter[] GetFilters() { return null; } }
The above is very awkward. For one thing, the plugin is just passing requests through to its UserManager or RoleManager. If the UserManager or the RoleManager changes, so too does the SecurityPlugin and its “pass through” methods. The above is also awkward because it would lead to very little, if any, consistency between the various plugins (they would all expose completely different “pass through” methods).
The other way that Charlie can load and save Users and Roles is if the SecurityPlugin returns the UserManager and the RoleManager, which Charlie can then talk to directly:
public class SecurityPlugin : IPlugin { IUserManager GetUserManager() { return new UserManager(); } IRoleManager GetRoleManager() { return new RoleManager(); } IWebModule[] GetModules() { return null; } IWebFilter[] GetFilters() { return null; } }
This is a much neater solution. So that one SecurityPlugin can be replaced by another (the purpose of plugins), the ISecurityPlugin interface would therefore look like the following:
public interface ISecurityPlugin : IPlugin { IRoleManager GetRoleManager(); IUserManager GetUserManager(); }
And the LoggingPlugin would implement the following interface:
public interface ILoggingPlugin : IPlugin { ILoggingManager GetLoggingManager(); }
And the GlobalizationPlugin would implement the following interface:
public interface IGlobalizationPlugin : IPlugin { ICultureManager GetCultureManager(); IResourceManager GetResourceManager(); ILanguageManager GetLanguageManager(); }
You will see then that the convention of Plugins returning Managers leads to the plugins all working in a similar fashion, even though the functionality they provide will be vary widely. This is precisely the sort of simplicity I was hoping to find, so the above convention has been adopted by Charlie.
I did sneak into the code a few new interfaces, such as the IRoleManager interface and the IUserManager interface. I’ll discuss the manager interfaces in the next weblog entry.
by Alister Jones | Next up: The Manager Interfaces →
----
Post a Comment