Groovy Objects in CFML (a la Ben)

Ben Nadel posted an interesting article over on his blog titled Instantiating Groovy Classes In The ColdFusion Context where he demoed how to create a class factory in Groovy and invoke it from CFML to insantiate new instances of Groovy classes without actually reentering a Groovy context.  I wanted to expound on what he demoed a bit more.

First and foremost, these two snippets are identical in functionality:

<cfset variables.map = createObject("java", "java.util.HashMap") />
<g:script script="variables.map = new HashMap()" />

After either line executes, 'variables.map' will contain a HashMap instance that is a full-fledged citizen of the JVM.  The way it was created is irrelevant.  This goes for ANY Java object, whether it's a CFC, Groovy, core Java, etc.  That why Ben's code works; once his factory is created, it can just do it's thing.  The context that it's methods are invoked in is irrelevant becaues the object itself doesn't change based on context.

I'm not sure I recommend a generic factory like this for real applications, though it's certainly good for experimentation.  For actual apps, I usually write one or more singletons (often DAOs) in Groovy that fulfill both a business role in the app as well as the VO creation role.  Then you have newPerson(name, hair, gender) rather than get("Person").init(name, hair, gender) with a proxy and a pile of reflection.

What makes this even more interesting is that because your objects are Groovy objects, they have the Groovy neatness available to them.  In particular, implicit getters.  If you write your persistence layer with Groovy (but not Hibernate), implicit getters can easily synthesize the transitive recall that your Hibernate-managed objects get for free.

At work, we do a lot of stuff on a JSR-270 Java Content Repository (with Magnolia CMS), which can't be fronted by Hibernate, but using Groovy I still get lazy loading across entity relationships.  My DAO (written in Groovy) has this method for retrieving a list of newsletters:

def getNewsletterList(int limit = 999999999) {
  def nodes = daoHelper.query("/*[MetaData/mgnl:template='newsletter']").reverse()
  if (nodes.size() > limit) {
    nodes = nodes.subList(0, limit)
  }
  nodes.collect { new Newsletter(daoHelper, it) }
}

And the Newsletter class has this implicit getter:

def _articles
def getArticles() {
  if (! _articles) {
    _articles = daoHelper.query("/*[jcr:uuid='$uuid']/*[MetaData/mgnl:template='newsletter_article']")
      .collect { new Article(daoHelper, it) }
  }
  _articles
}

When first invoked, it'll use daoHelper (which was passed to the Newsletter in the first snippet) to pull out a list of article nodes from the JCR and inflate them into Article instances.  That list of instances is cached in '_articles' so it doesn't have to be created multiple times.  This lets me grab a newsletter, and if needed, lazy load the newsletter's articles without paying the cost of loading the articles if I'm not going to need them.  But even better is the CFML that uses these objects.

Here's a snippet that renders a list of newsletters (retrieved via getNewsletterList) with a list of articles:

<ul class="issue-list">
<cfloop array="#newsletters#" index="newsletter">
  <li><h3 class="title"><a href="#newsletter.handle#">#newsletter.title#</a></h3>
    <h4>In This Issue:</h4>
    <ul>
      <cfloop array="#newsletter.articles#" index="article">
        <li><a href="#article.handle#">#article.title#</a></li>
      </cfloop>
      <li class="full"><a href="#newsletter.handle#">View Full Issue</a></li>
    </ul>
  </li>
</cfloop>
</ul>

What you'll notice is that I'm treating the 'newsletters' variable as if it were an array of structs, when it's actually an array of Groovy instances.  In particular, notice the way I loop over the articles.  No method call, it's just a property.  This is ridiculously powerful, because it lets you flip-flop between properties and getters/setters at will without affecting calling code.

This code doesn't illustrate it, but I can traverse much more deeply that this.  Say for each article I wanted to list the distinct set of categories that the article's author has posted at least one article in.  Easy:

cats = article.author.articles.sum({ it.categories }).unique

So this will hit the implicit getAuthor() method of Article to lazy-load an author, then call getArticles() on Author to get all of their articles, then iterate over them and calling getCategories() on each one and adding all the collections together, and then finally pull unique values.  The result will be a list of Category objects which can be further traversersed:

newslettersForFirstCategory = cats.first().articles.collect({ it.newsletter }).unique

This sort of traversal might seem really hard to read, but it's easily picked up and becomes very intuitive.  You start at the left, and each step transforms your initial data into a new piece of data and passes it down the chain.

It is worth mentioning that just like Hibernate, this sort of lazy loading does result in the N+1 query problem.  Tune your database, optimize your SQL, and you probably won't have a problem.

11 responses to “Groovy Objects in CFML (a la Ben)”

  1. Ben Nadel

    I know that in the long run, Reflection is never a great thing, but seeing as I just started looking into this stuff, I couldn't figure out what else to do. I would have liked to do something like what ColdFusion provides where I can use CFInvoke or CreateObject() to instantiate objects based on name.

    Also, I couldn't figure out how to work very well with variable number of arguments; I expect that no one really makes this as easy as ColdFusion.

    I really like what you're saying, though, about the implicit getters and setters. Very cool stuff and it really opens the door up for changing things later on.

  2. Ben Nadel

    Ah, ok, cool. I just figured CFInvoke / CFInvokeArgument was some sort of more elegant dynamic code execution. I'll be sure to check out that link.

    And, again, thanks for the CFUNITED presentation – it was very inspiring.

  3. Ben Nadel

    Actually, I think I read that link this morning! I think there is a problem – the variable arguments seemed to work perfectly fine from within Groovy; but, I think when called from ColdFusion:

    … I was method signature errors that CF was trying to find this:

    method( Object[] )

    It looks like within the Groovy context, N methods => Object[] args happens implicitly; but, when going from CF to Groovy, you need to explicitly define the collection.

    Of course, again, I am new – I might very well be way off here. But, that's why I went with the 10 explicit args rather than (Object… args) notation.

  4. Ben Nadel

    I was looking up missingMethod and some articles said that it was very slow. Is this true? I only ask because in the CF world, it has been explained that since CF checks for the target method before sending the message to OnMissingMethod(), it actually has almost no overhead (and no exception handling). Does Groovy work differently? Or, is the "slow" that Groovy is talking about relative?

  5. Sean Corfield

    Where did you read that onMissingMethod() was slow? The actual invocation is very fast because it's built right into the function lookup so it adds only a few ms to the call. Of course, performance depends on what you do *inside* onMissingMethod() …

    I've used onMissingMethod() very successfully to provide automatic delegation across tiers in an application as well as generic CRUD methods which cuts out huge amounts of boilerplate code. Yes, there's an overhead in the logic *inside* oMM() that decodes what the method should do and how to process the 'real' call but in the grand scale of things the RAD benefits overwhelmed that completely for Phase I of a project…

  6. Ben Nadel

    @Sean,

    I was saying that onMissingMethod() was *not* slow. In fact, I think I was actually thinking of a conversation that you and I had (online) in regards to that. I was saying that I was surprised that Groovy's missingMethod() was considered slow, since CF's was considered fast.

  7. Sean Corfield

    Ah, I misread you, sorry. Again, I don't think Groovy's missingMethod() is slow per se but what you then do in order to process the 'missing' method call may well be slow. Of course, in Groovy it can optimize 'regular' method calls down to native Java calls so compared to that, both Groovy's missingMethod() and *any* of CFML's method calls are slow since they look a lookup and, potentially, reflection…

  8. Ben Nadel

    I've been trying to play around with Groovy, but I feel so handicap because I am trying to, perhaps, get too dynamic without truly knowing much about the language. Everything in CF is so easy, it seems, but I guess it's just that I don't know the syntax in Groovy yet.

    This morning, I tried to play around with two ideas that stopped me dead in my tracks:

    1. Extending CF classes
    2. Overwriting cast operators

    1 – its seems that a lot of the CF classes are considered "final"?? and Groovy will not let me extend them. :(

    2 – this might just be something that is not possible across the groovy / cf context. Plus, I probably don't have the correct syntax.

    … sorry, I know this comment is a total non-sequitur.