Last week I wrote an article titled SOA Simplified in which I attempted to provide a short and relatively simple explanation of "service oriented architecture". The Devon CFUG Manager, John Whish commented that he'd always thought of SOA as "communicating with web services". While it's true that web services are an example of SOA it's something of a misnomer to think of them as being synonymous. Being service oriented really just means designing an architecture that allows services to be easily discovered and used by other entities (maybe other services). So there are lots of examples of SOA in the wild that have nothing in particular to do with web services.
Last week I mentioned the IoC Manager in the onTap framework as being one of the ways the onTap framework provides SOA within an application. Today I'd like to elaborate on that a little bit.
It's become common in the ColdFusion community to use Inversion of Control (IoC) frameworks in our applications. For the uninitiated an IoC framework mostly just creates objects. The reason this is so useful (and important) is because it provides a single place where these objects can be created and manages the dependencies between them (and should probably be called "Dependency Injection (DI)" although the acronym hasn't really caught on). The 800-pound gorilla in the ColdFusion community is ColdSpring although Pete Bell's LightWire has also been around for a while.
So here's a before and after to show what an application looks like with or without an IoC framework:
<cfset dep1 = application.dep1 />
<cfset dep2 = application.dep2 />
<cfset thingService = CreateObject("component","com.myCompany.thingService").init(dep1,dep2) />
<cfset thingService = application.IoCFactory.getBean("thingService") />
The word "bean" in the IoC factory example is an unfortunate convention which I believe started with Java. A much better name for that method would be "getObject", but I use getBean merely because it's become the defacto standard for IoC frameworks. The word "bean" really doesn't mean anything -- they're just objects.
In a typical application the majority of objects managed by an IoC framework are "singletons", which is a fancy way of saying the application should only have one of them. In a pre-ioc application these objects are typically created when the application starts (in the onApplicationStart event of Application.cfc). This ensures that there is only one of each of these objects for the entire application because onApplicationStart only executes once and so each object is only created once.
This is called "aggressive loading" because everything is created or "loaded" well in advance.
While this works for small applications, as the size of the application grows it takes longer for the application to start. And just because the application may need a particular object doesn't mean that object will be used on every request. For example your application may have several areas like RIAForge which has blogs, forums, wikis and bug trackers. When the server is idle a request for any page on the site starts the application and aggressively loads all its objects. So if a user visits the blog, the application will load not only the objects for the blog, but also for the wiki, the forum and the bug tracker even though none of those other objects will be used. Even though the visitor isn't using them, he's still forced to wait while they load. (The RIAForge site doesn't work that way, this is just an example.)
This leads to what is probably the second most important purpose of an IoC framework, which is "lazy loading".
As you might imagine lazy loading is an alternative to aggressive loading. As an application grows and becomes more complex, a given page request will use a smaller percentage of its business objects. In the above example when a visitor requests a page of the blog, that request will use only about a quarter of the business objects (with forum, wiki and bug tracker being unused). So because each request will use only a small number of objects, a lazy-loading system will create these objects only when they are needed or in other words "on demand". For this reason IoC frameworks usually default to lazy-loading all objects unless otherwise specified. This way when the application loads, only the IoC factory needs to be loaded in advance. As the application grows and becomes more complex, the IoC factory remains the only aggressively loaded object and visitors are waiting only for the objects that are needed to deliver their content.
The popular frameworks for ColdFusion, the "big 4" (Fusebox, Mach-II, Model-Glue and ColdBox) include integration for IoC frameworks. What differentiates the onTap framework is that where the big 4 focus on having a single IoC factory for your application, the onTap framework adds another layer called an "IoC Manager" which places the emphasis on having multiple IoC factories for different "services" within your application. This is a large part of what makes the onTap framework an SOA approach to ColdFusion development. To make it clear, it's not the fact that you can have multiple IoC factories in the onTap framework that makes it SOA. There's nothing in the big 4 frameworks that might prevent you from using multiple IoC factories, there's just nothing in them to make it easier. What makes the onTap framework SOA is the fact that there is an extra layer that standardizes management of IoC factories, providing easy and consistent methods of discovering, accessing and utilizing those factory "services".
Ultimately this basically boils down to the definition of a "plugin" because it's fundamentally different for the onTap framework as compared to the big 4. A plugin for the big 4 frameworks is a single CFC. A plugin for the onTap framework is a sub-application like a blog or a forum, composed of multiple files. In ColdBox specifically your IoC factory (ColdSpring or LightWire) is configured as a plugin. In the onTap framework, each plugin can (and typically will) have its own IoC factory.
The ColdBox config XML file contains three properties for configuring your IoC factory: IoCFramework, IoCDefinitionFile and IoCObjectCaching. This loads a single IoC factory which you can then fetch later with getPlugin("IoC") and from there you can request your singletons with IoCFactory.getBean("nameOfBean").
This works well to accomodate applications that place an emphasis on the programming work being done by your own development team. If your application has a forum, your team built it. If it has contacts, your team built it. If it has ecommerce, your team built it. If it has blogs, wikis, bug trackers, your team built them. This is an exaggeration, but only a very small exaggeration.
You're likely to use tools developed by other people, such as for AJAX (jQuery, Spry, etc) or custom tags for various display widgets, so I'm not talking about NIH Syndrome. These are however very small and minor helpers. However easy they might be to use, your programming team still needs to work to make them useful. A custom tag or an AJAX framework is great, if the bulk of your development is already done - if you already have a database schema, if you already have business objects, etc. You're unlikely however to use business objects or database schemas created by anyone outside your team, which is the bulk of any given application.
Unless your company is a social networking site like Ning or Tribe, it's unlikely at best that "forums" are part of your core business. That being the case, why should your team be tasked with creating a forum? The same is true of most of these small one off kind of features. Your application stores contact information or a wiki or a help system or a knowledge base. Why have your own developers devoting their time to these tasks that are non-essential to your core business? Why not leverage the community?
The reality of working in the ColdFusion community today is that it's difficult to get any kind of collaboration on these kinds of sub-applications, because the popular frameworks only focus on the work done within your own programming team. There's been no attention given to the idea of bringing services in from outside. Single sign-on works, but it's a weak solution. It has to be developed and integrated by your team because generally speaking there's not standardization. There's at best minimal communication between the sub-apps, and it does nothing to enrich the user experience. In fact it often detracts from the user experience because the forum doesn't look or feel like the rest of the application, etc.
So back to the example, ColdBox is a good example of the approach taken by the "big 4". I mentioned before that ColdBox includes three parameters in its XML config file. When the application loads (onApplicationStart), ColdBox creates a single IoC factory that you can later use via getPlugin("IoC"). By comparison the onTap framework doesn't have an XML config file. It offers a directory structure for configuring any IoC factories you might need for your application. So in the /_tap/_config/ioc/ directory you might have several CFCs named things like forum.cfc, blog.cfc, wiki.cfc etc. or better yet they would be named something more specific like "mangoblog.cfc". Each of these config objects declares an IoC factory needed by some part of your application.
The config object looks like this:
<cffunction name="configure" access="public" output="false" returntype="void">
<cfset newContainer(name="MyAppName",className="ColdSpringAdapter").init("/path/to/coldspring/config.xml") />
These created containers are then later fetched with request.tap.getIoC("nameOfContainer") or you can check to see if a container exists with request.tap.getIoC().hasContainer("nameOfContainer"). This allows you to use whatever IoC framework you want to use and the onTap framework itself offers its own default IoC factory class which is much, much simpler than either ColdSpring or LIghtWire. The built-in IoC factory class is not intended to compete with other IoC frameworks, it's just offered as a simplified alternative for people who don't need the extra features and would like to avoid an extra dependency. But the className can be whatever class you want, so you could have a totally hand-written IoC factory if you wanted it and this is the case for both DataFaucet and the PluginManager.
You might notice however that it's described as a ColdSpring "adapter". This component merely declares an IoC factory -- it doesn't actually create one. It does however create an IoC "container". An IoC container in the context of the onTap framework is an object that holds an IoC factory declaration and knows how to create that factory. So what it's actually doing here is lazy-loading the IoC factories. When the application loads, it creates the IoC Manager and through the help of these config objects it declares a set of containers. The actual IoC factories then are merely waiting for a call that says "hey, I need that blog service!" The manager then returns the blog service container and it isn't until the container is asked for a specific "bean" (object) that the factory is created. That factory may be a ColdSpring factory, it may be a LightWire factory or it may be something all together different. Your application doesn't care. It just knows that it has a blog service.
So this basically has taken the concept of IoC and moved it back a step from the application. In essence, it's a standardized IoC factory for your IoC factories. :)
The really nice thing here is that this makes it easy for people in the community to bundle up their applications as plugins. Each plugin is likely to have an IoC config object like I've just shown that declares the IoC factory needed for that plugin. This IoC declaration immediately makes that plugin available as a "service" to the rest of your application. So if you then want to integrate your calendar sub-application with your contact system, its really easy to do that because most of the work is already done. You can fetch the service objects for your calendar with request.tap.getIoC("calendar") or you can fetch the service objects for your contact system with request.tap.getIoC("contacts"). Then it's a simple matter of wiring them together in your controller. What could be easier? :)