|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe construction of a well-designed system is challenging, whatever its size, and the main challenges could be summarized as follows:
By objects' responsibilities I mean the ability of designing classes that have a clear, distinct responsibility. In other words, a class should have only one concern and the concern should be very explicit through its name and operations. It might sound trivial but any developer knows it's not. Unfortunately, this only comes with experience, and there is no shortcut. Extensibility, however, is a polemic topic as XP enthusiasts, like myself, advocates that the extensibility exposed by an application should match the requirements brought by the client. Nothing more, nothing less. Reuse is tricky. The more your classes depend on each other, the bigger is your headache to reuse them in different applications or different contexts. Usually, we talk in levels of linkage: is your system tight coupled or loosely coupled? This is the first part of three articles intended to show how inversion of control might result in less code, simpler design and testable software. In this part, I'll mainly focus on what is a decoupled design and how an inversion of control container might help you. I'll also describe Castle's Windsor Container, how it works, and how to augment it. Inversion of ControlWhat's inversion of control, anyway? Inversion of control is a simple principle that dictates that an external entity should send messages to programmer's objects in order to perform some specialized action or to allow the programmer to override some logic. This is the main difference between an API and a framework. On the first case, your object uses the API in an active fashion; in the second case, the framework uses your objects, so your objects are now in a passive mode. Putting off all the hype about Inversion of Control and the wrong meaning some legends of software development try to impose to it, inversion of control is a nice design principle to construct a loosely coupled application. It requires that you shift your paradigm, though. So, before you embrace the principle, do some - or several - experimentations. It might be good to browse the code of some projects that use inversion of control too, like Cocoon, James and Turbine. So, one of the uses of inversion of control that solves the loosely coupled problem (or one aspect of it) is by inverting how your class obtains external object references or configuration. Let's work on a not-so-distant-from-the-real-world example: using System;
using System.Configuration;
public class MyAwfulEmailClass
{
public MyAwfulEmailClass()
{
}
public void SendEmail( String from, String to, String message, String templateName )
{
String host = ConfigurationSettings.AppSettings["smtphost"];
int port = Convert.ToInt( ConfigurationSettings.AppSettings["smtpport"] );
NVelocityTemplateEngine engine = new NVelocityTemplateEngine();
String newMessage = engine.Process( message, templateName );
// Finally send message...
}
}
This class has a few lines of code, but has screaming problems:
So this simple class has two strong dependencies: the means to obtain the configuration and the template engine. Suppose you'd like to use this very class in another project. In order to do that, you'll have to:
And this is just a small class! It could be worse, and I'm positive you have seen a similar situation. Usually, the fastest solution is to copy the code and change a few things. But c'mon, there must be another way. Fortunately, there is. Component and ServicesThere were people using an interesting buzzword: COP (component oriented programming). I think we already have enough of buzzwords nowadays and we should save space on our brains for more important things. Nevertheless, I'd like to depict the concepts of components. A component is a small unit of reusable code. It should implement and expose just one service, and do it well. In practical terms, a component is a class that implements a service (interface). The interface is the contract of the service, which creates an abstraction layer so you can replace the service implementation without effort. To define our email service, we could use the following interface: // The contract
public interface IEmailSender
{
void Send(String from, String to, String message)
}
Please note that this contract is valid for any implementation of e-mail sender, for example, using SMTP, IMAP, SendMail and so on. Now, we can work on one of those implementations, a simple SMTP e-mail sender: // The implementation
public class SmtpEmailSender : IEmailSender
{
private String _host;
private int _port;
public SmtpEmailSender(String host, int port)
{
_host = host;
_port = port;
}
public void Send(String from, String to, String message)
{
// Configures the Smtp class and sends the e-mail
}
}
Better, huh? But where's the inversion of control? Before going further, allow me to make things more complex. You have probably noticed that now the email sender is only responsible for sending the email, no template processing. So let's define the contract for a template processor engine: public interface ITemplateEngine
{
String Process(String templateName)
}
This is a fairly naive example. Any decent template engine will use a context or something similar. Now we can have a different component which processes the templates and dispatches the emails: public interface INewsletterService
{
void Dispatch(String from, String[] targets, String messageTypeName)
}
Let's speculate about the implementation of public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
public SimpleNewsletterService(IEmailSender sender,
ITemplateEngine templateEngine)
{
_sender = sender;
_templateEngine = templateEngine;
}
public void Dispatch(String from, String[] targets, String messageTypeName)
{
String message = _templateEngine.Process(messageTypeName);
foreach(String target in targets)
{
_sender.Send(from, target, message);
}
}
}
As you can see, with small and well defined responsibilities, it's easier to cope with software development. But the problem now is that we have to assemble the application, in other words, we need to connect everything, instantiating and configuring the Castle WindsorCastle Windsor is an inversion of control container. It's built on top of a MicroKernel responsible for inspecting the classes and trying to understand what they need in order to work and then provide the requirements or fail fast if something is wrong. For our small example, the following code will take care of everything: IWindsorContainer container = new WindsorContainer();
container.AddComponent( "newsletter", typeof(INewsletterService),
typeof(SimpleNewsletterService) );
container.AddComponent( "smtpemailsender", typeof(IEmailSender),
typeof(SmtpEmailSender) );
container.AddComponent( "templateengine", typeof(ITemplateEngine),
typeof(NVelocityTemplateEngine) );
// Ok, start the show
INewsletterService service = (INewsletterService) container["newsletter"];
service.Dispatch("hammett at gmail dot com", friendsList, "merryxmas");
First, let me explain what happened in this code snippet:
The fact that a class has a non default constructor has a specific meaning for the container. The constructor says to it: "Look, I really need these in order to work". If our implementation were different, the container would have used a different approach. For example: public class SmtpEmailSender : IEmailSender
{
private String _host;
private int _port;
public SmtpEmailSender()
{
_host = "mydefaulthost";
_port = 110; // default port
}
public String Host
{
get { return _host; }
set { _host = value; }
}
public int Port
{
get { return _port; }
set { _port = value; }
}
...
}
In this case, you may or may not specify the host and port in an external configuration and the container will be able to use it. Another approach is to expose more than one constructor. The container will try to use the best constructor - meaning the one it can satisfy more arguments. "But wait! What about the configuration?" By default, Windsor will try to obtain the component configuration from the XML file associated with the Here is the configuration file you must use to run the example: <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,
Castle.Windsor" />
</configSections>
<castle>
<components>
<component id="smtpemailsender">
<parameters>
<host>localhost</host>
<port>110</port>
</parameters>
</component>
</components>
</castle>
</configuration>
From experience, I know that sometimes it's dumb to have interfaces for all your components. There are classes that are unlikely to have a different implementation, like Data Access Objects. In this case, you can register classes into the container as follows: public class MyComponent
{
private IEmailSender _sender;
public MyComponent()
{
}
public IEmailSender EmailSender
{
get { return _sender; }
set { _sender = value; }
}
}
IWindsorContainer container = new WindsorContainer();
container.AddComponent( "mycomponent", typeof(MyComponent) );
You might want to request a component by the service: IEmailSender emailSender = container[ typeof(IEmailSender) ] as IEmailSender;
But please note that you can register more than one implementation for a given service. The default behavior is to return the first implementation registered for the specified service. If you want a specific implementation, then you need to use the key. As a matter of fact, the last paragraph leads us to an interesting situation. Suppose you have implemented several <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="castle"
type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler,
Castle.Windsor" />
</configSections>
<castle>
<components>
<component id="newsletter">
<parameters>
<sender>#{smtpEmailSender}</sender>
</parameters>
</component>
</components>
</castle>
</configuration>
The nodes inside ' public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
public SimpleNewsletterService(IEmailSender sender, ITemplateEngine templateEngine)
{
_sender = sender;
_templateEngine = templateEngine;
}
...
Lifecycle and LifestyleLifecycle and lifestyle are useful strategies that Apache Avalon have been using for quite some time (since 1999 to be exact). As a former member of Avalon, I like these ideas and decided to provide it too. The lifestyle of a component is related to its instance. It might be singleton (which is the default lifestyle) that means that only one instance will be created for the whole life of the container. Other supported lifestyles are:
There are two ways to specify the lifestyle of a component: by using attributes or by using the external configuration. In fact, the configuration overrides whatever is defined on the component. The following have the same result: using Castle.Model;
[PerThread]
public class SimpleNewsletterService : INewsletterService
{
private IEmailSender _sender;
private ITemplateEngine _templateEngine;
...<castle>
<components>
<component id="newsletter" lifestyle="perthread" />
</components>
</castle>
Lifecycle means the sequence of invocation that happens during the creation of your component (before making it available to the external world) and the sequence of invocation during the destruction. Out of box, Windsor only supports the More about Windsor ContainerWhen you add a component to the container, a few things happen. First, the container creates a The real fun begins when you start to use this process by adding your own contributor that checks for _anything_ and gives the component a different semantic or role when found that _anything_. There's also an important entity called Handler. The Handler is the keeper and orchestrator of the component. If the minimal set of dependencies for the component is not satisfied, the handler will not allow the component to be used (by your code or by any other component).
Now, you're ready for the most interesting section: extending the container. Extending the ContainerI'm going to present you a simple and a not so simple sample applications. The first one will introduce a different semantic to components that implement the Basically, to extend the container, you can use a few extension points as you like. For example, you can add an inspector to look for an attribute, or an entry on the component configuration, or whatever you like. You can also subscribe to the events the When extending the container, you can give the component instance more information, register interceptors (proxies), change the lifestyle, add more lifecycles and so on. At first, let's talk about implementing a new lifecycle step. Startable LifecycleSuppose you'd like that every component that implements an
For a world usage example, imagine a WinForms application so you can register Forms subclasses as components. One component may run the When you decide to implement extensions like that, you can do it ad-hoc, or you can create a Facility. Facility is an extension unit so you can create a library of facilities and apply them to other applications, thus reusing your work. To implement our requirements, we need to:
So, let's work! The first step: create a new Facility: public class StartableFacility : IFacility
{
private IKernel _kernel;
public void Init(IKernel kernel, IConfiguration facilityConfig)
{
_kernel = kernel;
}
public void Terminate()
{
// Nothing to do
}
}
A Facility can have its own configuration which can be useful for settings (like the NHibernate facility that I'll talk about in the next article). Now, let's add a hook to the registration process: public class StartableFacility : IFacility
{
private IKernel _kernel;
public void Init(IKernel kernel, IConfiguration facilityConfig)
{
_kernel = kernel;
kernel.ComponentModelBuilder.AddContributor( new StartableInspector() );
}
public void Terminate()
{
// Nothing to do
}
private class StartableInspector : IContributeComponentModelConstruction
{
public void ProcessModel(IKernel kernel, ComponentModel model)
{
bool startable =
typeof(IStartable).IsAssignableFrom(model.Implementation);
model.ExtendedProperties["startable"] = startable;
if (startable)
{
model.LifecycleSteps.Add(
LifecycleStepType.Commission, new StartableConcern() );
}
}
private class StartableConcern : ILifecycleConcern
{
public void Apply(ComponentModel model, object component)
{
(component as IStartable).Start();
}
}
}
}
So we added a contributor that looks for the public class StartableFacility : IFacility
{
private ArrayList _waitList = new ArrayList();
private IKernel _kernel;
public void Init(IKernel kernel, IConfiguration facilityConfig)
{
_kernel = kernel;
kernel.ComponentModelBuilder.AddContributor( new StartableInspector() );
kernel.ComponentRegistered +=
new ComponentDataDelegate(OnComponentRegistered);
}
...
private void OnComponentRegistered(String key, IHandler handler)
{
bool startable = (bool)
handler.ComponentModel.ExtendedProperties["startable"];
if (startable)
{
if (handler.CurrentState == HandlerState.WaitingDependency)
{
_waitList.Add( handler );
}
else
{
Start( key );
}
}
CheckWaitingList();
}
/// For each new component registered,
/// some components in the WaitingDependency
/// state may have became valid, so we check them
private void CheckWaitingList()
{
IHandler[] handlers = (IHandler[])
_waitList.ToArray( typeof(IHandler) );
foreach(IHandler handler in handlers)
{
if (handler.CurrentState == HandlerState.Valid)
{
Start( handler.ComponentModel.Name );
_waitList.Remove(handler);
}
}
}
/// Request the component instance
private void Start(String key)
{
object instance = _kernel[key];
}
}
Now, if the component that was just registered is startable, we try to start it by requesting it. Note however that sometimes the components are not ready to be started, so we have to keep them on a list and check if they are OK to be started later.
If you'd like to see the application running, check the sample code. A Transaction Framework for database accessIn this example, the goal is to simplify the development of Data Access Objects by automatic handling of the connection and transactions. We purse two possible usages: Through attributes: [Transactional]
public class BlogDao
{
private IConnectionFactory _connFactory;
public BlogDao(IConnectionFactory connFactory)
{
_connFactory = connFactory;
}
[RequiresTransaction]
public virtual Blog Create(Blog blog)
{
using(IDbConnection conn = _connFactory.CreateConnection())
{
IDbCommand command = conn.CreateCommand();
// Not the best way, but the simplest
command.CommandText =
String.Format("INSERT INTO blog (name, blog.desc) " +
"values ('{0}', '{1}');select @@identity",
blog.Name, blog.Description);
object result = command.ExecuteScalar();
blog.Id = Convert.ToInt32(result);
}
return blog;
}
[RequiresTransaction]
public virtual void Delete(String name)
{
// We pretend to delete the blog here
}
...
}
And through external configuration: <component id="postDao" transactional="true">
<transaction>
<method>Create</method>
<method>Update</method>
</transaction>
</component>
Might sound complex, but it's not. We need to implement an It all starts with a Facility again: public class TransactionFacility : IFacility
{
TransactionConfigHolder _transactionConfigHolder;
public void Init(IKernel kernel, IConfiguration facilityConfig)
{
kernel.AddComponent( "transactionmanager",
typeof(ITransactionManager), typeof(DefaultTransactionManager) );
kernel.AddComponent( "transaction.interceptor",
typeof(TransactionInterceptor) );
kernel.AddComponent( "transaction.configholder",
typeof(TransactionConfigHolder) );
_transactionConfigHolder =
kernel[ typeof(TransactionConfigHolder) ]
as TransactionConfigHolder;
kernel.ComponentModelCreated += new
ComponentModelDelegate(OnModelCreated);
}
public void Terminate()
{
}
private void OnModelCreated(ComponentModel model)
{
if (IsTransactional(model))
{
TransactionConfig config = CreateTransactionConfig(model);
_transactionConfigHolder.Register(model.Implementation, config);
model.Interceptors.Add(
new InterceptorReference(typeof(TransactionInterceptor)) );
}
}
...
}
This Facility registers the components it needs. If the component is transactional, we associate an interceptor with it. Please note that only virtual methods can be intercepted (in future releases, we'd be able to proxy everything through interfaces). The interceptor code follows: public class TransactionInterceptor : IMethodInterceptor
{
private ITransactionManager _transactionManager;
private TransactionConfigHolder _transactionConfHolder;
public TransactionInterceptor(ITransactionManager transactionManager,
TransactionConfigHolder transactionConfHolder)
{
_transactionManager = transactionManager;
_transactionConfHolder = transactionConfHolder;
}
public object Intercept(IMethodInvocation invocation, params object[] args)
{
if (_transactionManager.CurrentTransaction != null)
{
// No support for nested transactions
// is necessary
return invocation.Proceed(args);
}
TransactionConfig config =
_transactionConfHolder.GetConfig(
invocation.Method.DeclaringType );
if (config != null && config.IsMethodTransactional( invocation.Method ))
{
ITransaction transaction =
_transactionManager.CreateTransaction();
object value = null;
try
{
value = invocation.Proceed(args);
transaction.Commit();
}
catch(Exception ex)
{
transaction.Rollback();
throw ex;
}
finally
{
_transactionManager.Release(transaction);
}
return value;
}
else
{
return invocation.Proceed(args);
}
}
}
The rest of the implementation is pretty ADO.NET specific, and I encourage you to see the sample attached to this article. Follows some screenshots of the sample implementation:
Stepping back: CastleCastle project's goal is to offer a set of tools and an inversion of control container. The container is the rendezvous of all the tools, but this is a topic for the next article. A few values have driven the development of the Windsor (and MicroKernel) container:
We also don't want to be XML or configuration driven as the component holds enough information to assemble itself. ConclusionIn the next article, I'll talk about the NHibernate and the Prevalence facilities. I'll also explain the integration with Web and the Castle on Rails MVC framework inspired by Ruby on Rails. The Castle project still is on the alpha stage, but the public API is very unlikely to change. There is a lot of work to do, so if you enjoyed the reading, consider joining the team and help us develop tools, facilities and extensions so your next enterprise project can be done in half the time, hopefully! Please visit Castle project site for more information. History
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||