-
Notifications
You must be signed in to change notification settings - Fork 24
M2SFP 1.8 Call Method Command
By Kurt Wiersma (kurt@…)
- Overview
- Comparing the Call-Method Command to Using the Notify Command
- Using Call-Method with Argument Collections
- Nested Arguments
- Implementation Concerns
- Additional Information and Considerations
- Using
call-method
with ColdSpring in Modules -
Comments
- Comment by anonymous on Wed Nov 19 16:21:29 2008
- Comment by peterfarrell on Wed Nov 19 16:21:37 2008
- Comment by anonymous on Thu 19 Feb 2009 05:38:43 AM EST
- Comment by peterfarrell on Thu 19 Feb 2009 12:54:46 PM EST
- Comment by anonymous on Fri 20 Feb 2009 05:03:07 AM EST
- Comment by peterfarrell on Fri 20 Feb 2009 05:22:30 AM EST
- Comment by anonymous on Thu 12 Mar 2009 01:21:03 AM UTC
- Comment by mattwoodward on Thu 12 Mar 2009 03:56:48 AM UTC
- Comment by peterfarrell on Thu 12 Mar 2009 04:43:58 AM UTC
- Comment by mattwoodward on Thu 12 Mar 2009 05:43:19 AM UTC
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.
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.
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" />
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>
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.
- 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.
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.
Would you have to have coldspring to use this?
@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.
could we load up jars (using javaloader) and execute commands using something similar. maybe this can be expanded beyond simply calling cfcs...?
@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.
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.
@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.
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
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.
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.
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.