Introduction to Listeners - Mach-II/Mach-II-Framework GitHub Wiki
-
- Introduction
- What are Listeners?
- Anatomy of a Listener CFC
- Registering and Configuring a Listener
- Using a Listener
- What should (and should not) go in a Listener
- The Importance of var Scoping
- Additional Information and Considerations
Mach-II is an object-oriented implementation of an implicit invocation architectural framework. You will need at least a basic understanding of object-oriented programming ("OO") and implicit invocation ("II") architectures. You don't need to be an expert on either OO or II, but being familiar with the concepts will be helpful. For a good introduction to these topics, check out my article called An Introduction to Implicit Invocation Architectures.
Okay, let's learn how to develop listeners.
A listener is a object that marshals data between the controller (Mach-II) and your model layer (core business logic/data persistence). See what is MVC? for more information on how the model-view-controller architecture works. The listener is able to interact with MachII to be able to do things like get and set event-args and parameters, announce events, or clear the event queue; and then to use these abilities to pass data into your model returning a result or performing an action based on the result. It is important to mention that it is not recommended that any business logic appear here, but is kept outside of Mach-II.
Listeners:
- are notified of events they have registered interest in
- perform Mach-II specific logic that should not be located in your model such as getting and setting data to and from the event object and talk to your model
- can announce new events if needed
Listeners should perform Mach-II specific logic such as announcing new events or getting Mach-II properties. If your model contains Mach-II specific logic such as reference to the event object, you should consider refactoring that logic out of your model and into your listener. It may appear that listeners are a good place for model logic, but when you architect your application in this manner you introduce very tight coupling between Mach-II and your model. Your model should have no knowledge about the framework or dependencies on framework related logic. Be aware that reusing your model for Flex or Flash based UI becomes nearly impossible to accomplish since they will not make use of the Mach-II specific logic.
In practice, software architectures are commonly treated as a collection of components and connectors. Components are the system's functional elements. For example, a shopping cart, a contact manager, and a database could be components of a software architecture. Connectors are the protocols for communication between components. Examples of connectors include method calls, SQL queries, and HTTP requests. A system's chosen architecture determines both the vocabulary of components and connectors that can be used as well as the set of constraints defining how they are combined.
Two metrics important for consideration in defining the publicly exposed interfaces of an architecture's components and connectors are a system's cohesion and coupling. Cohesion is a measure of the degree to which a component has a singular purpose. The greater cohesion a component exhibits, the more focused is the component and the fewer are the assumptions about contexts for reuse. Coupling is the degree of interdependence between components. The less a component relies on other components (the looser its coupling), the more independent and reusable it is. Maximized cohesion (simple components) and minimized coupling (fewer connectors) are hallmarks of a flexible, maintainable architecture.
Event-based, implicit invocation is an example of a well-crafted architectural style with high cohesion and loose coupling. As such, it is one of the more broadly accepted architectural styles in software engineering. Examples of implicit invocation systems abound, including virtually all modern operating systems, integrated development environments, and database management systems. Implicit invocation systems are driven by events. Events are triggered whenever the system needs to do something - such as respond to an incoming request. Events can take many forms across different types of implementations; for Mach-II an event is an object whose properties contain any contextual information needed to process the event (similar to the way a HTTP request carries with it all its form and query-string variables).
When an event is announced, the system looks up listener components for that event. Listeners fit the same criteria for components that we've already discussed—they are functional modules of the system. Components that wish to act as listeners are registered at configuration time (by specification in an XML file) to be notified of certain events when they occur at runtime. When an event is triggered, all registered listeners of that event are passed the event by means of a dynamically-determined method call. In this way, functions are implicitly invoked. This process of notifying listeners of an event is called event announcement.
From the perspective of the listener file, all it is is simply a cfc
that extends MachII.framework.Listener
which in turn extends the MachII.framework.BaseComponent
where a
majority of the available helper methods are located. If we were to
graph the inheritance hierarchy, it would look like this:
Your Listener -> MachII.framework.Listener -> MachII.framework.BaseComponent
Each of your listeners must have a configure()
function which Mach-II
calls upon instantiating the object, which is used to run any code that
will be used to set up your listener. The rest of your listener consists
of functions that you create yourself. Each of these Mach-II
automatically passes the Event object for you to use in getting
event-args, setting event-args and announcing events.
Listeners have several important functions used by the framework, and three methods that are particularly important for developers creating their own listeners:
-
announceEvent(string eventName, [struct eventArgs])
- used to announce a new event to the framework -
configure()
– called after a component is initialized, override to specify configuration logic -
getParameter(string paramName)
– used to access configuration parameters specified in the configuration XML
<cfcomponent
displayname="SampleListener"
extends="MachII.framework.Listener"
output="false"
hint="A simple listener example.">
<!---
PROPERTIES
--->
<!---
CONFIGURATION / INITIALIZATION
--->
<cffunction name="configure" access="public" returntype="void" output="false"
hint="Configures the listener.">
<!--- Put custom configuration for this listener here. --->
</cffunction>
<!---
PUBLIC FUNCTIONS
--->
<cffunction name="sampleFunction" output="false" access="public" returntype="void"
hint="I am a boilerplate function">
<cfargument name="event" type="MachII.framework.Event" required="true" />
<!--- Put logic here. --->
</cffunction>
<!---
additional functions would be here
--->
</cfcomponent>
From the perspective of the config file, a listener is employed by
Mach-II by defining it in the listeners section. Then in your
event-handler, a listener is 'notified' that it is to perform a
particular action. Keep in mind that the method
attribute of the
notify node will map directly to one of the functions inside that
listener. Anything that gets returned by the function then gets placed
inside of an event-arg that gets named by its resultArg
attribute.
<listeners>
<listener name="SampleListener" type="listeners.SampleListener"/>
</listeners>
<event-handlers>
<event-handler event="showSomePage" access="public">
<notify listener="SampleListener" method="sampleFunction" resultArg="result"/>
<view-page name="somePage"/>
</event-handler>
</event-handlers>
TODO: show using a listener via the notify and publish commands
The code you put in a specific listener function should focus on manipulating the current Mach-II event and data within the event, and should not contain core business logic. While listeners are great for starting a call to your business logic (usually encapsulated in a service layer) and making a decision on what to do next based on results returned from that call to your business logic, your actual business logic (ie; calls to the database, Web services, etc.) should not go in your listener. It's considered good practice to put your business logic in to your service layer, which should have no knowledge of the Mach-II framework.
Things that can / should go in your listener:
- Calls to your service layer
- Decision logic about what event should be called next based on results returned from calls to your service layer
- Decision logic about what event should be called next based on data in the current event structure
- Manipulation of data within the event structure
Things that should not go in your listener:
- Queries
- File manipulation
- Web service calls
- Business logic (ie; calculating a tax rate, checking to see if a user already has an account in your system, etc.)
If all a listener function is doing is making a one-line call to a function in your service layer, you can skip writing this method in your listener entirely by using the call-method command, introduced in Mach-II 1.8.
Var-scoped variables are declared within <cffunction>
tags, and they
must be declared immediately following the <cfargument>
tags. Unlike
variables in the variables scope, var-scoped variables are local to the
<cffunction>
within which they are declared and are not available
throughout the CFC. This is important because two functions within a
CFC, both of which contain a <cfloop>
with an index variable of i, may
exist within a CFC. Keeping the variable i local to the function is
crucial; otherwise, the value of i would be available to the two
functions simultaneously, which would cause unexpected behavior.
Use of the var scope is also critical to making CFCs thread-safe. Variables that are not var scoped and are not explicitly placed into another scope are put into the variables scope by default, which means they are global to the CFC.
It is extremely important for ALL variables in CFCs that are local to functions to be var scoped.'
For more information, read the section on var scoping in the CFCs: A Primer series.
- All listeners must extend the
MachII.framework.Listener
base component which is included with the core framework files. - All listeners Must be registered in the
<listeners>
section of your Mach-II configuration file. - Do not create an
init()
method in your listener as this overrides an inherited method. Doing this will break the framework. Use theconfigure()
method for instantiation logic. The framework will call this method for you at the appropriate time. - Listeners can take a specialized CFC called an invoker. These are
rarely required in most situations. Starting with Mach-II 1.1.0, the
framework will automatically instantiate the default invoker
(
MachII.framework.invokers.EventArgsInvoker
) for you. If you do not know what an invoker is, you probably don't need to know this.