Skip to content

M2SFP 1.8 Call Method Command

thofrey edited this page Apr 1, 2014 · 3 revisions

By Kurt Wiersma (kurt@…)

Table of Contents

  1. Overview
  2. Comparing the Call-Method Command to Using the Notify Command
  3. Using Call-Method with Argument Collections
  4. Nested Arguments
  5. Implementation Concerns
  6. Additional Information and Considerations
  7. Using call-method with ColdSpring in Modules
  8. Comments

Overview

Often when writing Mach II applications the developer is required to create a method in a listener that simply calls a method in the application's service layer with out any additional logic. This requires the developer to write extra listener methods that don't really serve any purpose. Mach II should make this easier by providing a command that can be used in the Mach II configuration file to allow the framework to call a method in the service layer and optionally put the result in a event argument. Simple argument passing should be allow via the use of the new expression syntax for access elements in the event or properties objects.

Comparing the Call-Method Command to Using the Notify Command

When creating an application developers often have a listing screen that lists out all the particular entities are available for the user to edit. They might have a PlayerListener which has method like getPlayers() which returns query listing all the players entered into the system. Currently this event would be configured like this:

    <event-handler event="public.availablePlayers" access="public">
        <event-arg name="subhead" value="Available Players" />
        <notify listener="playerListener" method="getPlayers" resultArg="players" />
        <view-page name="players.search" contentArg="content" />
        <view-page name="players.available" contentArg="content" append="true" />
        <execute subroutine="public.layout" />
    </event-handler>

The getPlayers() method in the PlayerListener looks like this:

    <cffunction name="getPlayers" access="public" returntype="query" output="false">
        <cfargument name="event" type="MachII.framework.Event" required="true" />
        <cfreturn variables.playerService.getPlayers() />
    </cffunction>

As you can see the getPlayers() method simply calls the PlayerService's getPlayers() method and passes in no arguments. This listener method could be eliminated by allowing the use of the call-method command in the Mach II configuration file. The new command would require the PlayerService be availabe via the ColdSpringProperty.

    <event-handler event="public.availablePlayers" access="public">
        <event-arg name="subhead" value="Available Players" />
        <call-method bean="playerService" method="getPlayers" resultArg="players" />
        <view-page name="players.search" contentArg="content" />
        <view-page name="players.available" contentArg="content" append="true" />
        <execute subroutine="public.layout" />
    </event-handler>

The Call-Method command should also allow arguments to be passed in either as a comma delimited list of values or a list of name value pairs, just like you can do when calling a method in CFML. The following examples are equivalent.

    <call-method bean="fantasyteamService" method="getFantasyTeam"
        args="fantasyteam_id=${event.id},bar='foobar'"
        resultArg="fantasyTeam" />

    <call-method bean="fantasyteamService" method="getFantasyTeam"
        args="${event.id},'foobar'"
        resultArg="fantasyTeam" />

Sometimes it is necessary to default values for arguments especially when event arguments are used to populate argument values. The expression syntax could be modified to allow a ":" followed by a default value. For example ${event.id:0} could be used to signify that if the "id" event argument is not defined a "0" should be used as the value of the expression.

Using Call-Method with Argument Collections

Another situation that is common is to have a search method in your service layer which takes a structure of search fields. Typically these search values come directly from the event arguments like the example below.

    <cffunction name="getPlayers" access="public" returntype="query" output="false">
        <cfargument name="event" type="MachII.framework.Event" required="true" />
        <cfreturn variables.playerService.getPlayers(arguments.event.getArgs()) />
    </cffunction>

In this case we need a way to pass all the event arguments to the PlayerService via the call-method command. This could be done using the following syntax.

    <call-method bean="playerService" method="getPlayers"
        args="${event.getArgs()}" resultArg="players" />

In some cases we may also need to pass in the event arguments as an argument collection to the method in the service layer.

    <call-method bean="playerService" method="getPlayers"
        args="argumentCollection=${event.getArgs()}" resultArg="players" />

Nested Arguments

Sometimes the list of arguments becomes unwieldy long and cumbersome to work with. Call-method supports nesting arguments nodes instead of using the arguments attribute.

Using arguments attribute:

    <call-method bean="fantasyteamService" method="getFantasyTeam"
        args="teamId=${event.fantasyTeam.teamId},teamName=${event.fantasyTeam.teamName}"
        resultArg="fantasyTeam" />

Using nested arguments using example above:

    <call-method bean="fantasyteamService" method="getFantasyTeam" resultArg="fantasyTeam">
        <arg name="teamId" value="${event.fantasyTeam.teamId}" />
        <arg name="teamName" value="${event.fantasyTeam.teamName}" />
    </call-method>

Using nested arguments passed by position (by leaving off the name attribute of the argument node):

    <call-method bean="fantasyteamService" method="getFantasyTeam" resultArg="fantasyTeam">
        <arg value="${event.fantasyTeam.teamId}" />
        <arg value="${event.fantasyTeam.teamName}" />
    </call-method>

Implementation Concerns

The bean (service layer object) is auto-wired into the call-method command by the ColdSpringPropery that comes bundled with Mach-II. Any IoC framework or developer created property CFC could leverage the API provided by Mach-II to auto-wire the correct object into the command, however this would require the developer to build a custom Property.cfc to accomplish this. If you are not using ColdSpring in your application, this feature will not work out of the box without additional work because it would be impossible for Mach-II to map the bean name in the call-method command to an instantiated object in memory.

The call-method command internally to Mach-II is a configurable command. This means any system or component can register itself as an onObjectReload call-back and receive a call-back when an object is loaded or reloaded. This is how the ColdSpringProperty that is bundled with the Mach-II core works.

Additional Information and Considerations

  • The call-method command is best used with a service layer in your model. We recommend avoiding make any direct calls to DAOs or Gateways and to limit your calls to service level objects in your model. However, the call-method command does not enforce this recommendation so please heed our warning. It is best practice to use a service layer in your model.
  • You cannot use both named and positional arguments at the same time in the arguments attribute and argument nodes. You must choose to use either positional arguments or named arguments.

Using call-method with ColdSpring in Modules

If you have a Mach-II module, are using ColdSpring to manage dependencies in the parent application, the Mach-II module will not automatically inherit the ColdSpring setup from the parent base application. For example, pretend you had a simple module that did not have any of its own objects to be managed by ColdSpring but instead would exclusively use objects managed at the parent application level. In the "home" view in this simple module, you needed some data from an object managed by ColdSpring in the parent app and made a <call-method> command to it. You would received an error stating that the bean I was looking for could not be found.

After some debugging, you might realize that a ColdSpring property was never setup in this simple module. Without the ColdSpring property being defined in the module, Mach-II had no idea how to call the bean specified in the <call-method> command.

If you are creating a module that has no ColdSpring managed objects of its own, but do want to use ColdSpring-managed objects in the parent application, you still have to set up a basic ColdSpring property in your module and point it to an (essentially) empty ColdSpring config file. Your ColdSpring config file can look like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans>
    </beans>

Once that is in place (and as long as you also define the parentBeanFactoryScope in your module's ColdSpring property CFC), your module will have access to all the ColdSpring managed objects in the parent application.

Comments

Comment by anonymous on Wed Nov 19 16:21:29 2008

Would you have to have coldspring to use this?

Comment by peterfarrell on Wed Nov 19 16:21:37 2008

@anonymous11/19/2008, yes this command would required the use of ColdSpring (or any other IoC framework or other system) in order to function "out of the box". Team Mach-II feels this dependency is ok at the moment. ColdSpring is the only viable IoC framework currently available for CFML development, however we have architected this feature in a manner that this feature is not tied to any specific IoC framework or implementation and can be swapped at runtime. This allows us to not be tied to ColdSpring specifically. For example you could write a Property.cfc that takes the bean value from the command and map that to objects you have created in the application scope by leveraging the API provided by Mach-II that details the Mach-II elements available for auto-wiring. It would be a trivial amount of code, however since all applications are not implemented in an identical manner, we cannot ship some type of autowire property to accomplish this task "out of the box". This feature is actually an enhancement to the framework as well as an addition to the ColdSpringProperty to autowire additional targets.

Comment by anonymous on Thu 19 Feb 2009 05:38:43 AM EST

could we load up jars (using javaloader) and execute commands using something similar. maybe this can be expanded beyond simply calling cfcs...?

Comment by peterfarrell on Thu 19 Feb 2009 12:54:46 PM EST

@anonymous on Thu 19 Feb 2009, I don't think this is something we can support in this iteration. Call-method will call anything that get's auto-wired into it so if say in this example it is in a ColdSpring bean factory -- it can be loaded into the call-method command.

Comment by anonymous on Fri 20 Feb 2009 05:03:07 AM EST

Nice feature, I may never create a listener again. :) My first thought was why not just use the notify command: <notify bean="" ...>, but I bet those of you closer to the code have good reasons for not doing that.

Am I correct in saying this would allow you to directly access any CS managed bean directly be it a Service, DAO, Gateway, TransferFactory or whatever? I suppose best practices will have to be preached here as it will become very easy to bypass the API and call whatever bean you want. Though that could be handy in some cases.

Comment by peterfarrell on Fri 20 Feb 2009 05:22:30 AM EST

@anonymous on Fri 20 Feb 2009, Honestly it never occurred to me to use notify command (Kurt developed this feature). However after a few seconds of thought, merging the call-method into notify might cause some confusion for people learning the framework on what notify actually does. Plus, it would make for future enhancements to either feature harder (we don't know how different they will become in the future). Also, notify doesn't support the args attribute or nested <arg> nodes. Good idea though -- I love it when people suggest things I've never thought about (especially when they are plainly obvious as in this case).

Yes, you are correct that any managed bean can be accessed via call-method. And you are right that the we should highlight the best practices even more (it's mentioned in the Additional Information and Considerations).

I do want to point out that call-method doesn't depend on ColdSpring -- it could be any IoC container. It's just that CS is only support IoC container that works out of the box (we don't have much choice at the moment). The infrastructure behind the scenes is CS agnostic so any future frameworks can leverage our API to insert itself as callback to wire in objects.

Comment by anonymous on Thu 12 Mar 2009 01:21:03 AM UTC

This seems to be moving closer to Fusebox's handling of CFCs and would allow calls to *any* bean managed by ColdSpring (which you acknowledge and discourage). It seems to be moving further away from Mach-II's Implicit Invocation roots. I understand the motivation but it really seems to be blurring the lines too much for my taste - and it gets closer and closer to writing actual control logic directly in the XML event handlers.

-- Sean Corfield

Comment by mattwoodward on Thu 12 Mar 2009 03:56:48 AM UTC

Thanks Sean--just to clarify this has an *extremely* limited use case and is just provided as a convenience method. The sole purpose of this is to eliminate listener methods that only call a service method and return a result. That's it. And of course the current way of doing things will always be available.

I understand the philosophy behind the objection, and we discussed this at some length, but in this narrow use case we decided to make this available as an option as a matter of practicality and offering the potential to eliminate a very specific type of listener method.

Comment by peterfarrell on Thu 12 Mar 2009 04:43:58 AM UTC

Just to extend on Matt's thoughts. The big difference here versus Fusebox is that we are not adding any conditional logic to call-method. So you will not be able to announce new events based on the result of call to your service layer. If I remember right, Fusebox has "if" commands and we are definitely not headed in that direction.

Comment by mattwoodward on Thu 12 Mar 2009 05:43:19 AM UTC

I'll chime in again--I thought about this a bit more and you can look at it this way--what's the difference between this: <notify listener="myListener" method="myMethod" resultArg="myResult" />

And this: <call-method bean="myBean" method="myMethod" resultArg="myResult" />

Assuming the listener method is simply calling the service method, in terms of how "implicit" it is, there's no difference. You just have an intervening listener in the first case.

And remember that we added the pub/sub bits in 1.6 so people can ignore this convenience method entirely and go even more implicit if they so desire.

Clone this wiki locally