Tuesday, February 23, 2010

Building a cfcommons application - Battlefield Part I

Overview




cfcommon's pluggable architecture allows you to harness many of its stable modules and plugins bit by bit, piece by piece without inheriting bloated code and unnecessary overhead. Each module is carefully crafted and engineered to ensure stability and developed with clear and concise features. Gone will be the days of installing multiple frameworks for different feature sets and having to deal with the baggage of worrying about collisions and incompatibility issues that can occur between them. 

To get a full appreciation for what cfcommons is all about, in the next few posts we will create a cfcommons application from the ground up, utilizing each module and plugin "as needed". You will see how incredibly simple it is to not only plug and play features, but also how each module plays nicely with each other.

Without further ado let's create our application using first up cfcommon's Neodymium  - an automatic dependency injection plugin.

dependency injection, what is it?

Also known as "Inversion of Control", is one of the most critical pieces in building clean, enterprise level object oriented software. If you are unclear about dependency injection, I encourage you to first read about what it's all about.

Many languages have frameworks that enable your software to leverage dependency injection like Java's Spring to .NET's Castle Windsor to ColdFusion's Lightwire. The old way was to "wire" up these dependencies via XML or bean configuration which is extremely wasteful in terms of keystrokes causing you to run into either a gigantic bean config file or a gigantic XML. A good way to induce coder's coma...


Yea yea, you can break things up into separate XML files or use "shortcut" attributes to help lessen the bloat but still, we're talking about making what will be a thousand line XML template down to hundreds...at least for awhile until it grows even bigger. Thanks, but no thanks..

what do you mean by "automatic" dependency injection?

Neodymium allows you to simply annotate your class properties with the right type and it will automatically inject those properties for you during application startup or reload. Once you ask the factory for a bean it will return to you the object with its dependencies properly injected for you.

so no XML mappings or bean config??!?






None. Nada. Zilch.

what if you don't want objects to automatically be injected?

Then use the exclude annotations.

what if I'm l337 and I want the type to be an interface?

Then use the combination of @type and @implementation, which gives the path to the concrete class. You're so 1337...

Battlefield Application - Part I

Let's create a cfcommons app shall we?! I didn't want to use the played out car/student/shapes/{insert boring, mind numbing tutorial topic here}, so I opted to go with a battlefield app which I'm hoping we can turn it into some sort of army game over the next few posts.

We will start by implementing automatic dependency injection via Neomydium. The battlefield code is also available for you to download and install as you follow along...

install cfcommons and create the web directory for this tutorial



  1. Install the cfcommons core modules library into your web root.
  2. Create a /battlefield/ directory under your web root. This will be the directory that houses our sample application.
  3. Under create a /model/ directory. Here is where we will put all our classes and interfaces.
  4. Under the /model/ directory create an /interfaces/ directory. Self explanatory...
create the context config XML file


This file is the central file that you will drop the cfcommon plugins.
  1. In /battlefield/, create a /config/ directory
  2. Inside the /config/ directory create an XML file called context-config.xml that looks like so:
 

In this XML you'll see we've declared both a development and production environment and told the Battlefield application that we will use the Neodymium automatic dependency injection plugin via one simple plugin tag.

Once complete, your directory structure should look like so (minus the .settings, that's eclipse stuff.): 



ok the good stuff now. let's take a look at what we are about to build.
    Simple right? We are going to create a soldier that "has-a" weapon. For all intents and purposes, let's usimagine that by default the Soldier should always be equipped with a knife by default. Meaning every time we need an instance of a Soldier from the bean factory, let's always make sure it has a knife object attached to it.








    where the rubber meets the road...




    Line 3 baby, it's where the magic happens. By simply annotating the property, Neodymium will register Soldier beans with its dependencies properly injected.


    test drive - index.cfm




    Here, we are:
    1. Creating an instance of the PluggableContextFactory based off of our context-config.xml we created earlier with the Neodymium plugin installed.
    2. Then we simply ask the context factory for the soldier object.
    3. Then dumping the Soldier and it's weapon.
    index.cfm results


    Viola! Notice that the Soldier object has a Knife object dependency attached to it. Automatic dependency injection baby...

    download the code

    Battlefield Part 1 - be sure to also install the latest cfcommons module library here.

    conclusion

    Hopefully you are starting to see the power behind cfcommons. With one simple declaration in the context-config.xml, we've added dependency injection support in literally a few seconds.

    Neodymium also supports the usual suspects when it comes to dependency injection like singletons, interface based typing and custom annotations. Find out more about it here.

    In Part II, we will build upon this application and integrate the SimpleMVC then perhaps SimpleSecurity.

    Visit http://cfcommons.org/ for more information or to be a contributor.

    Till next time, later peeps!

    Mick

    3 comments:

    Brian Carr said...

    Micky, another great post detailing cfcommons library functionality! I love the example you've used for Neodymium ioc. Just as a point to note for your readers, there's overhead incurred when initializing a context - it's creation should only need to occur once during the lifetime of the application that uses the context - so implementing an appropriate caching scheme for the context should always be considered by developers.

    Micky Dionisio said...

    Good point Brian!

    In the next post we will deploy SimpleMVC and incorporate an Application.cfc. We'll explore some caching strategies like utilizing onApplicationStart and walk through some ways to reload the cfcommon's context.

    Anonymous said...

    public function onRequestStart() {

    if (listlast(cgi.PATH_INFO,".") == "json")
    application.context.rest.respond();
    else
    application.context.mvc.respond();
    }

    public function onApplicationStart() {
    var folder = listlast(getdirectoryfrompath(getcurrenttemplatepath()), "\");
    import org.cfcommons.context.impl.*;
    import org.cfcommons.context.standardplugins.mvc.*;
    import org.cfcommons.context.standardplugins.rest.*;
    import org.cfcommons.context.standardplugins.faceplait.FaceplaitPlugin;
    import org.cfcommons.context.standardplugins.ioc.*;

    application.context = {};

    application.context.mvc = new HTTPContext(root = expandPath("/app/#folder#/"));
    var mvcPlugin = new SimpleMVCPlugin(viewRoot="/app/#folder#/view/");
    var faceplaitPlugin = new FaceplaitPlugin(layoutsDirectory="/app/#folder#/view/layout/");
    application.context.mvc.addPlugin(mvcPlugin);
    application.context.mvc.addPlugin(faceplaitPlugin);
    application.context.mvc.finalize();

    application.context.rest = new HTTPContext(root = expandPath("/app/#folder#/"));
    var powernap = new PowernapPlugin();
    application.context.rest.addPlugin(powernap);
    application.context.rest.finalize();

    application.context.ioc = new PluggableContext(root=expandPath("/shared/"));
    var neodymium = new NeodymiumPlugin();
    application.context.ioc.addPlugin(neodymium);
    application.context.ioc.finalize();
    }