In Depth Guide to Modules - Mach-II/Mach-II-Framework GitHub Wiki

Table of Contents

  1. Mach-II and Module Config Files
  2. Mach-II Modules and ColdSpring
  3. Mach-II Modules and Views
  4. A Few Good Practices When Building Applications with Many Modules

If you have not already reviewed the information in Introduction to Includes and Modules, you should do so before reviewing the content on this page.

A Mach-II module is an application which runs inside of a parent Mach-II application. A module usually groups together common functionality in to a related package, encapsulates that package nicely within your Mach-II app, and inherits a lot of the base configuration from the parent Mach-II application. Some people build Mach-II modules that are meant to be reused across multiple applications, for multiple sites, while others build open-source modules which are meant to be redistributable and used by anyone, anywhere. This guide focuses on architecting an application so that there is very little business functionality at the top level of the Mach-II application, and most of the business functionality of the application is encapsulated in modules that are children of the parent application.

Mach-II modules are a powerful way of organizing and architecting large-scale applications. Mach-II's implementation of modules is quite mature and separates it from many other ColdFusion frameworks currently available.

Mach-II and Module Config Files

Below is a really simple Mach-II XML config file for a module.

    <mach-ii version="1.8">

        <!-- INCLUDES -->
        < includes>
            < include file="/myApp/modules/sampleMod/config/sampleMod-coldSpringProperty.xml" />
        </includes>

        <!-- PROPERTIES -->
        <properties>
            <property name="applicationRoot" value="/myApp/modules/sampleMod/" />
            <property name="defaultEvent" value="sampleModHome" />
        </properties>

        <!-- LISTENERS -->
        <listeners>
            <listener name="simpleListener" type="myApp.modules.sampleMod.model.simpleListener" />
        </listeners>

        <!-- EVENT-FILTERS -->

        <!-- EVENT-HANDLERS -->
        <event-handlers>

            <event-handler event="sampleModHome" access="public">
                <notify listener="simpleListener" method="getRandomNumber" resultArg="randomNumber" />
                <notify listener="simpleListener" method="getHTTPString" resultArg="httpAddress" />
                <view-page name="sampleModHome" />
            </event-handler>

            <event-handler event="sampleProtectedEvent" access="public">
                <filter name="adminsOnlyFilter" />
                <view-page name="sampleProtectedView" />
            </event-handler>

        </event-handlers>

        <!-- SUBROUTINES -->

        <!-- PAGE-VIEWS -->
        <page-views>
            <!-- View Loaders -->
            <!-- This loads all pages which match /views/**/*.cfm,
                so a page-view called aboutUs.index would translate to /views/aboutUs/index.cfm -->
            <view-loader type="MachII.framework.viewLoaders.PatternViewLoader" />
            <!-- Define all other page-views here -->
        </page-views>

        <!-- PLUGINS -->
    </mach-ii>

<includes>

You can include other configuration XML files in your module XML configuration file with no problem. In this example, a Mach-II property for ColdSpring is included because there are certain objects that need to be managed by ColdSpring and that are only pertinent to this module. Mach-II modules and ColdSpring are covered in more detail below.

<properties>

At a minmum, you need to include two properties in your module's Mach-II config file:

  1. applicationRoot — The absolute path to your module under Webroot
  2. defaultEvent — The default event to call should no event be specified by a request

Notice that the following Mach-II properties you would have in a Mach-II XML configuration are not listed here:

  • eventParameter
  • parameterPrecedence
  • maxEvents
  • MACHII_CONFIG_MODE
  • urlBase or any of the URL rewriting properties

All of these properties have to be defined at the main application level and cannot be overridden at the module level.

You can include the following properties in your module's Mach-II XML config to override what's being used in the main application:

  • exceptionEvent
  • Module-specific caching property
  • Module-specific logging property

While you can include these here, it is recommended that you handle exceptions, caching and logging at the main application level. This way you consolidate your exception handling, caching and logging setup in to a single location. There are, however, perfectly legitimate reasons to handle exceptions at the individual module level. Consolidation in to a central location can bring simplicity and clarity to the debugging process.

<listeners>, <event-handlers> and <page-views>

Listeners, event handlers, and page views are defined exactly as they are in the main Mach-II XML configuration file.

<filters>

Any filters that are needed for this module and this module only are defined here. Note that in the sample configuration file above that a filter is referenced in the sampleProtectedEvent event, but no such filter is defined in this sample XML config file. How can this possibly work?

A module inherits all of the properties, event filters, and plugins defined in its parent (main) Mach-II configuration file. So if you have a "admins only" filter which you use to only let administrators access certain events in your application, define it in your parent (main) Mach-II configuration file. You can then refer to that filter in any of the module Mach-II configuration files that operate under the parent app. This is much cleaner, simpler, and more encapsulated than defining a "admins only" filter in each of your modules. You're free to write a special "admins only" filter for a specific module that has special requirements, of course, and you'd define it in this block if that is what you need to do.

<plugins>

Again, as a module inherits all of the properties, event filters, and plugins defined in its parent (main) Mach-II configuration file. As such, you'd find common plugins like "is the user logged in plugin" and "put properties from a property CFC in to the current event plugin" defined at the main application level. Every event in your module would be subject to the plugins in your main application running on each and every event in the module, just as they do in the main application.

If you have a plugin which needs to run on every event in your module, define it here and it will work as expected.

There is one additional configuration option you can provide for plugins in a module, and that's the runParent attribute. This attribute tells Mach-II when your module plugins should be run in relation to the plugins in the parent/main app. This configuration option is detailed here.

Including Your Module's Config File in the Main App Config File

To add a module to an existing Mach-II application, you simply need a <modules> block which tells Mach-II where to find each module you want to include.

    ...all the rest of your main Mach-II config file, including plugins, listeners, events, etc...
    <!-- MODULES -->
    <modules>
        <module name="sampleMod" file="/myApp/modules/sampleMod/sampleModConfigFile.xml" />
    </modules>

The name that you give the module here (in the name="" attribute) is important. The name gets prepended (that is, put before) every event name generated by Mach-II functions such as BuildURL(), BuildURLToModule(), and the <view:a> tag. You also have to include that module name in any links you hand-build to the other events in your module, or other modules in the larger application. It's what tells the framework to look for an event within a specific module, and not in the main Mach-II app itself. So an event name like "dashboard:main" would look for an event called "main" inside of the "dashboard" module. To use the sample module XML config file above, an event with the name "sampleMod:sampleProtectedEvent" would look for the event "sampleProtectedEvent" inside the module named "sampleMod."

If you fail to prepend the module name (or identifier) to the event name, Mach-II looks for an event with the given event name in the the main application. You'll most likely end up with a "missingEvent" exception as a result.

Mach-II Modules and ColdSpring

The key to using ColdSpring in Mach-II modules is the ColdSpring property that comes bundled with Mach-II 1.6 and later. You need to be using the ColdSpring property and not the old ColdSpring plug-in which was available prior to Mach-II 1.6. If you need to learn how to get the ColdSpring property working in your Mach-II application, please [review the documentation][idwiki_UsingColdSpringWithMach-II].

The key point about using ColdSpring in Mach-II modules is that you can both set up individual ColdSpring factories for each module in your application and have access to a main, or parent, factory defined at the base level of your application. This is quite powerful for two reasons:

  1. ColdSpring managed objects become specific to the module in which they are needed, and don't clutter up the rest of the application, and,
  2. Commonly used objects can be created at the main/parent application level and be re-used throughout the entirety of the application, as needed.

In order to use ColdSpring in a module, you need to create a ColdSpring property and define it in your Mach-II XML configuration file for the module:

    <mach-ii version="1.8">
        <!-- INCLUDES -->
        < includes>
            < include file="/myApp/modules/sampleMod/config/sampleMod-coldSpringProperty.xml" />
        </includes>

        ...listeners, events, plugins, etc...

    </mach-ii>

The basic information you need to provide in your coldSpringProperty.xml file to use ColdSpring in a module is as follows:

    <mach-ii version="1.0">
        <properties>
            <property name="coldSpringProperty" type="coldspring.machii.ColdspringProperty">
                <parameters>
                    <parameter name="beanFactoryPropertyName" value="serviceFactory"/>
                    <parameter name="configFile" value="/path/to/my/coldSpringServicesFile.xml"/>
                    <parameter name="configFilePathIsRelative" value="false"/>
                    <parameter name="resolveMachIIDependencies" value="true"/>
                    <parameter name="parentBeanFactoryScope" value="application"/>
                    <parameter name="parentBeanFactoryKey" value="serviceFactory"/>
                </parameters>
            </property>
        </properties>
    </mach-ii>

Here's what each parameter does:

  • beanFactoryPropertyName — This isn't required, but you really should give your ColdSpring bean factory a name. You must do this in the ColdSpring property XML file for your base application, for reasons lsited below.
  • configFile — This is the path to the ColdSpring XML file for your module, in which ColdSpring-managed beans are defined. Required.
  • configFilePathIsRelative — This lets Mach-II know if it should be using an absolute or relative path to find the ColdSpring XML file defined above. Defaults to false.
  • resolveMachIIDependencies — This defaults to false, but if you plan on using the Mach-II autowriring introduced in Mach-II 1.6, then this must be set to true, or nothing gets autowired in to your plugins, listeners, or filters.
  • parentBeanFactoryScope — Here's where you tie in the bean factory from your base/parent Mach-II application to your module. By setting this to true, you complete one half of the steps needed to pull in beans defined in the base application but that are used across your whole application. This merely defines the scope in which your parent bean factory is stored. That's most often going to be the application scope.
  • beanFactoryPropertyName — The value provided here must match the beanFactoryPropertyName value defined in your parent/base app. If it does, you've completed the second half of the steps needed to pull in beans defined in the base application but that are used across your whole application.

An example

This example assumes that you have a userService that is defined in your base ColdSpring XML file. This is a common scenario, as you'll most likely need that userService (for things like getting user information) throughout your application, and in all of your modules. The userService defined in the base ColdSpring XML file as follows:

    <beans>
        <bean id="userService"
            class="path.to.userService">
            <constructor-arg name="dsn">
                <value>${dsn}</value>
            </constructor-arg>
        </bean>

    ...more beans defined here...
    </beans>

In your module's ColdSpring XML file, you can then utilize the userService like this:

    <beans>
        <bean id="reportingService"
            class="path.to.reportingService">
            <property name="userService">
                <ref bean="userService"/>
            </property>
        </bean>

        ...more beans defined here...
    </beans>

You don't define the userService in the module's ColdSpring XML file. Because you've defined it in the parent/base app's ColdSpring XML file, and you set the parentBeanFactoryScope property to "application" (because that's where the parent bean factory is stored) and you set the beanFactoryPropertyName property to the same value as defined in the parent/base app's ColdSpring XML file, Mach-II's ColdSpring property will grab the userService and put it in the right place.

You do need to be careful when using ColdSpring and Mach-II modules that there are no bean name collisions. If you define a bean in your parent/base app called "page" and you also define a bean in your module ColdSpring XML called "page," the one defined in the module takes precedence over the one in the parent/base app. You should always use module-specific bean names for the beans defined in your module's ColdSpring XML file to avoid naming collisions.

Finally, you can use ColdSpring in your module, but not worry about utilizing beans defined in the parent/base application at all. A particular module may have no good need to use the parent bean factory. In that case, just omit the parentBeanFactoryScope and parentBeanFactoryKey parameters from your module's ColdSpring property XML file.

Mach-II Modules and Views

Using BuildURL() and BuildURLToModule()

By using BuildURL() and BuildURLToModule(), you let Mach-II handle the writing of your <a href> tags. These functions are available in views only, and not inside plugins, filters, or listeners. If you use Mach-II modules, you're going to want to use BuildURLToModule() over BuildURL() in most cases. Here's why:

  • BuildURLToModule() ensures that the module is preprended to the event name in the resulting URL. This ensures that the user is routed to the right module when the event is requested.
  • BuildURLToModule() allows for an empty module name in the module argument of the function. This lets you write events which point to the main/parent application and forces the request to go to the main/parent application.

If you just use BuildURL(), Mach-II is going to assume that you want an event in the same/current module. This works fine if you are at the top level of your application, in the main/parent portion of the application. This may not result in the expected behavior if you are inside a module. For example, if you have an event called "logout" tied to a "Logout" link and you are inside a module and a user clicks on the "Logout" link, BuildURL() is going generate a link that looks for an event called "logout" in the current module. As the logout event is (probably) defined in the main/parent application, Mach-II won't be able to find the event in the current module, and an exception will be thrown.

It's a good idea to use BuildURLToModule() instead of BuildURL() in both the parent and child (module) application views. This way, you ensure that the user is being routed to the right event in the right module, even if it's the default/parent module.

Using the <view> Library

The <view:a> tag is a handy way to let Mach-II write your <a href> links. It does much of what the BuildURL() and BuildURLToModule() functions have done for some time now, only in a simpler and more elegant wrapper.

The basic format of generating a link is as follows:

    <view:a module="blog" event="showEntry" p:entryId="456"></view:a>

Simply specify the event name in the event attribute, the module name in the module attribute, and any key/value pairs you want to add to the link in the p attribute. If you have multiple key/value pairs you want to add, put multiple p attributes in the tag, as follows:

    <view:a module="blog" event="showComments" p:entryId="456" p:commentID="89"></view:a>

You can put CF variable names in lieu of static values, or use the expression language syntax available in Mach-II to generate the values:

    <view:a module="blog" event="showComments" p:entryId="${event.entry.entryID}" p:commentID="89"></view:a>

Again, you should provide a blank value for the module attribute if you want the link to point to an event in the main/parent application. This ensures that Mach-II looks for an event in the main/parent application and not in the current module.

Using the <form> Library

When it comes to generating form action attributes, the

tag library works just like the view:a tag. An example:
    <form:form actionEvent="createEntry" actionModule="blog">

A Few Good Practices When Building Applications with Many Modules

  • Use BuildURL and the View Tag Library generously. BuildURL and the View Tag Library encapsulate the creation of URLs/links in your views so that when you change servers or need to roll out a new version of a module in a different directory, you do very little (to no) work to make the URLs/links all update correctly. You can even use them to create links in your inline JavaScript.
  • Avoid redundancies in your ColdSpring configuration files. If you're going to share functionality across modules and you manage your object dependencies with ColdSpring, you're going to run in to instances where you define a bean multiple times in multiple ColdSpring configuration files. If you have a userService, for example, you're probably going to refer to this userService throughout the modules in your application. It's easy to define that userService in each of the ColdSpring configuration files in each of the modules that uses the userService. This is redundant and wasteful. Instead, simply define the userService bean in the ColdSpring configuration file at the top level of the application, and then if you inherit beans defined in the parent (top level) app as discussed above, you have access to that userService in your module. However, if your module needs a specific version of the service (eg; it needs to keep an old version for compatibility with something else), you can define it in the child ColdSpring config file as needed.
  • Avoid per-module session objects. This is a really easy trap to fall in to if you are porting existing Mach-II applications to modules inside a new, larger application. Most of your existing apps will have information stored in the session scope. It's simply bad form to not encapsulate everything you need in the session scope in a single variable created and managed at the top layer of your application. In all likelihood, redundant session variables generated in your modules largely contain the same information — userIDs, user objects, security or role-permission information, and the like. Managing this in one reduces memory consumption and makes debugging simpler.
  • Handle exceptions and errors at the main app level. As with redundant bean definitions or per-module session objects, per-module exception and error handling is most often redundant. Unless you need to handle an exception in a special way in a module, there's no good reason to have different exception or error views in each module. Provide a consistent way of handling errors and exceptions for the developers behind the app and a consistent experience for the people using the app.

This page was adapted from a series of blog posts on www.iterateme.com by Brian Klaas.

⚠️ **GitHub.com Fallback** ⚠️