Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
In order to create a module you must first create a nicely named directory within the modules conventions directory. For example, let's build a simple hello world module. CommandBox to the rescue!
Here is the output:
The layout of a ColdBox Module is optional except for one file: ModuleConfig.cfc
. This is a simple CFC that boots up your module and tells the host application how your module is loaded, unloaded and behaves. If you are leveraging CommandBox then you can also declare a box.json
for the module itself in order to declare dependencies and development dependencies for it.
Below are all the possible combinations of a module layout, you will notice that it is EXACTLY the same as a ColdBox application.
As you can see, the only mandatory resources for a module is the directory name in which it lives and a ModuleConfig.cfc
. The module developer can choose to implement a simple module or a very complex module. All folders are optional and only what is used will be loaded. Not only are modules reusable and extensible, but you can easily create a module with dual functionality: A standalone application or a module. This is true reusability and flexibility. I don't know about you, but this is really exciting (Geek Alert!).
ColdBox Modules are self-contained subsets of a ColdBox application that can be dropped in to any ColdBox application and become alive as part of the host application. They will bring re-usability and extensibility to any ColdBox application, as now you can break them down further into a collection of modules instead of monolithic approaches. CommandBox will also help you manage, install, track and uninstall modules as part of your development workflow.
This concept of modularization has been around in software design for a long time as it is always essential to partition a system into manageable modules or parts.
"In structured design and data-driven design, a module is a generic term used to describe a named and addressable group of program statements" by Craig Borysowich (Chief Technology Tactician)
The key to the statement is that these modules are named and addressable, which is exactly what ColdBox Modules will offer to your application. So by building an application with a methodology of modules, you will inherently achieve:
Manageability (i.e., small and simple parts that can be easily understood and worked on)
Independence (i.e., a module can live on its own if necessary and tested outside of its environments, produces very nice low coupling between core and modules)
Isolation (i.e., some modules can be completely isolated and decoupled)
Extensibility (i.e., you can easily extend ANY application by just building on top of the modular architecture)
Reusability (i.e., modules have independence and can therefore be shared and reused)
Sanitability (Yes! That's a word) from the root Sanity
ColdBox Modules will change the way you approach application development as you can now have a foundation architecture that can scale easily and provide you enough manageability to reduce maintenance and increase development. Such a design means that development, testing and maintenance becomes easier, faster and with a much lower cost. Welcome to a brave new world!
There are a few parent application settings when dealing with modules. In your ColdBox.cfc
you can have a modules
structure with some configuration settings.
If you are picky and you would like to change the folder layout for your module, you can. This is achieved from within the ModuleConfig.cfc
by adding a conventions
structure that will explain to the ColdBox Module engine how to locate and configure your module.
By convention every ColdBox application will have two folders for modules:
/modules : Used by CommandBox to place tracked modules. You would usually NOT commit these modules to source control.
/modules_app : Used for custom non-tracked modules
You can also have more external locations that ColdBox will scan for modules by leveraging the coldbox.modulesExternalLocation
setting. This setting is an array of locations you want to tell ColdBox to look for modules in your ColdBox.cfc
. Each array element is the instantiation location which can use ColdFusion mappings or an absolute reference from the root of your application.
Caution Internally each of those entries will be expanded for you, so please be aware of this.
You might be asking yourself, what happens if there is another module in the external locations with the same name as in another location? Or even in the conventions? Well, the first one found takes precedence. So if we have a module called funky
in our conventions folder and also in our external locations, only the one in the conventions will load as it is discovered first.
However, before we start reviewing the module service methods let's review how modules get loaded in a ColdBox application. Below is a simple bullet point of what happens in your application when it starts up:
ColdBox main application and configuration loads
ColdBox Cache, Logging and WireBox are created
Module Service calls on registerAllModules()
to read all the modules in the modules locations (with include/excludes) and start registering their configurations one by one. If the module had parent settings, interception points, datasoures or webservices, these are registered here.
All main application interceptors are loaded and configured
ColdBox is marked as initialized
Module service calls on activateAllModules()
so it begins activating only the registered modules one by one. This registers the module's SES URL Mappings, model objects, etc
afterConfigurationLoad
interceptors are fired
The beauty of ColdBox Modules is that you have an internal module service that you can tap in order to dynamically interact with the ColdBox Modules. This service is available by talking to the main ColdBox controller and calling its getModuleService()
method or via dependency injection.
Below you can see a diagram of what happens when modules get registered:
If you want to load a new module in your application that you have just installed you need to do a series of steps.
Drop the module in any of the module locations defined or an a-la-carte location
Call registerAndActivateModule(moduleName,[instantiationPath])
to register and activate the module
The module service also announces several events or interception points as you saw from the life cycle diagrams. Below are the events announced:
Fired before a module is about to be activated.
Data:
moduleLocation
- The location of the loaded module
moduleName
- The name of the module
Fired after a module has been successfully activated
Data:
moduleLocation
- The location of the loaded module
moduleName
- The name of the module
moduleConfig
- The module configuration structure
Fired before a module is about to be unloaded
Data:
moduleName
- The name of the module
Fired after a module has been unloaded
Data:
moduleName
- The name of the module
Below you can see a diagram of what happens when modules get activated right after registration:
Here is a listing of all public properties that can be defined in a module.
Below you can see an example of declarations for the configuration object:
Property | Type | Required | Default | Description |
title | string | false | --- | The title of the module |
author | string | false | --- | The author of the module |
webURL | string | false | --- | The web URL associated with this module. Maybe for documentation, blog, links, etc. |
description | string | false | --- | A short description about the module |
version | string | false | --- | The version of the module |
viewParentLookup | boolean | false | true | If true, coldbox checks for views in the parent overrides first, then in the module. If false, coldbox checks for views in the module first, then the parent. |
layoutParentLookup | boolean | false | true | If true, coldbox checks for layouts in the parent overrides first, then in the module. If false, coldbox checks for layouts in the module first, then the parent. |
entryPoint | route | false | --- | The module's default route (ex: |
inheritEntryPoint | boolean | false | false | If true, then the |
activate | boolean | false | true | You can tell ColdBox to register the module but NOT to activate it. By default, all modules activate. |
parseParentSettings | boolean | false | true | If true, ColdBox will merge any settings found in |
aliases | array | false | [] | An array of names that can be used to execute the module instead of only the module folder name |
autoMapModels | boolean | false | true | Will automatically map all model objects under the models folder in WireBox using |
cfmapping | string | false | empty | The ColdFusion mapping that should be registered for you that points to the root of the module. |
disabled | boolean | false | false | You can manually disable a module from loading and registering |
dependencies | array | false | [] | An array of dependent module names. All dependencies will be registered and activated FIRST before the module declaring them. |
modelNamespace | string | false | moduleName | The name of the namespace to use when registering models in WireBox. By default it uses the name of the module. |
The module configuration object: ModuleConfig.cfc
is the boot code of your module and where you can tell the host application how this module will behave. This component is a simple CFC, no inheritance needed, because it is decorated with methods and variables by ColdBox at runtime.
The only requirement is that this object MUST exist in the root of the module folder and contain a configure()
method. This is the way modules are discovered, so if there is no ModuleConfig.cfc
in the root of that folder inside of the modules folder, the framework skips it. So let's investigate what we need or can do in this component.
You can also use the same module service methods to load ANY module in ANY ColdFusion reachable location. This is huge for applications that need granular control of loading and unloading of modules. You will do this with our magic method:
This time, you will pass an instantiation path to the method. This is the dot notation path up to the folder that contains the module. So let's say your module to load is located here:
You will then issue the following:
Caution The instantiation path does NOT include the name of the module on disk as the name is the module name you already pass into the method.
Here are the most common methods you can use to manage modules:
reloadAll()
: Reload all modules in the application. This clears out all module settings, re-registers from disk, re-configures them and activates them
reload(module)
: Target a module reload by name
unloadAll()
: Unload all modules
unload(module)
: Target a module unload by name
registerAllModules()
: Registers all module configurations
registerModule(moduleName,[instantiationPath])
: Target a module configuration registration
activateAllModules()
: Activate all registered modules
activateModule(moduleName)
: Target activate a module that has been registered already
getLoadedModules()
: Get an array of loaded module names
rebuildModuleRegistry()
: Rescan all the module locations for newly installed modules and rebuild the registry so these modules can be registered and activated.
registerAndActivateModule(moduleName,[instantiationPath])
: Register and Activate a new module
With these methods you can get creative and target the reloading, unloading or loading of specific modules. These methods really open the opportunity to build an abstraction API where you can install modules in your application on the fly and then register and activate them. You can also do the inverse and deactivate modules in a running application.
Below you can see a diagram of what happens when modules get deactivated and unloaded
At runtime, the configuration object will be created by ColdBox and decorated with the following private properties (available in the variables
scope):
You can use any of these private variables to create module settings, load CFCs, add binder mappings, etc.
The module configuration object is also treated as an Interceptor once it is created and configured.
There are two life-cycle callback events you can declare in your ModuleConfig.cfc
:
onLoad()
: Called when the module is loaded and activated
onUnLoad()
: Called when the module is unloaded from memory
This gives you great hooks for you to do bootup and shutdown commands for this specific module. You can build a or like application very easily all built in to the developing platform.
Also, remember that the configuration object itself is an interceptor so you can declare all of the framework's interception points in the configuration object and they will be registered as interceptors.
This is a powerful feature, you can create an entire module based on a single CFC and just treat it as an interceptor. So get your brain into gear and let the gas run, you are going for a ride baby!
Modules configurations are stored in the host parent configuration structure under a modules
key according to their name. To retrieve them you can do
getModuleSettings( module )
: Returns the structure of module settings by the module name.
getModuleConfig( module )
: Returns the module's configuration structure
You can also use our injection DSL to inject module settings and configurations:
The injection DSL pattern is the following:
coldbox:setting:mySetting@module
: You can now inject easily a module setting by using the @module directive.
coldbox:moduleSettings:{module}
: You can now inject the entire module settings structure
coldbox:moduleConfig:{module}
: You can now inject the entire module configuration structure
If you have parseParentSettings
set to true in your ModuleConfig.cfc
(which is the default), ColdBox will look for a struct inside the moduleSettings
struct of your config/ColdBox.cfc
with a key equal to your module's this.modelNamespace
(default is the module name) and merge them with your modules settings
struct (as defined in your module's configure()
method) with the parent settings overwritting the module settings. This allows a user of your module to easily overwrite specific settings for their needs.
If parseParentSettings
is set to false
, your module's settings
will instead overwrite the settings set in the same moduleSettings
struct.
ModuleConfig.cfc
If you want to use the overridden settings in your ModuleConfig.cfc
, you will need to use it in the postModuleLoad
interceptor. Remember, all your modules register the ModuleConfig.cfc
as an interceptor, so all you need to do is add the postModuleLoad
function and you're off!
If you are using per-environment control in your parent application via the ColdBox.cfc
, you can also use that in your Module Configuration object. So the same conventions that are used in the parent configuration can be used in the module; having the name of the environment match a method name in your module config. So if the following environments are declared in your parent configuration file and the dev environment is detected, the dev()
method is called in your configuration object:
But if you declare that dev()
method in your Module Configuration object, it will be called as well after your configure()
method is called:
Module event executions are done almost exactly the same way we are used to in our ColdBox applications using event syntax patterns. By now we understand that an event comes in via the URL/FORM or Remotely to the framework and then the framework decides what event to execute. We also know that we can abstract our incoming events by using the ColdBox URL Routing, but at the end of the day, we will always have an event
variable in the request collection that tells the framework what event to execute. The typical event syntax pattern we have learned is:
This typical approach maps the event to a package, handler and method combination. With the addition of modules our event syntax pattern now morphs to the following:
As you can see, we can prefix the event syntax with a module name and then followed by a colon {module}:
. This is how ColdBox can know to what module to redirect the execution to. You can even execute the runEvent()
methods and target modules:
Hint Please remember that this is great for securing your applications as the event patterns you can match against with regular expressions will help you tremendously, as you can pinpoint modules directly.
In summary, the event syntax has been updated to support module executions via the {module:}
prefix. However, please note that our preference is to abstract URLs and incoming event variables (via FORM/URL) by using ColdBox URL Routing. In the next section we will revise how to make module URL Routings work.
A module can also include one or more custom routing files in order to take advantage of our routing DSL and also have better separation as they will be stored outside of the module configuration object. You do this by giving the path to the custom file to include in your routes structure:
This will look for a routes.cfm
template in your module's config
folder and load it. Please note that you do not need to specify a .cfm
if you don't want to. You can load as many route files as you like.
Now, let's say you want to override the layout for a module. Go to the host application's layouts folder and create a folder called modules
and then a folder according to the module name, in our case simpleModule
:
What this does, is create the override structure that you need. So now, you can map 1-1 directly from this overrides folder to the module's layouts folder. So whatever layout you place here that matches 1-1 to the module, the parent will take precedence. Now remember this is because our layouParentLookup property is set to true. If we disable it or set it to false, then the module takes precedence first. If not found in the module, then it goes to the parent or host application and tries to locate the layout. That's it, so if I place a main.cfm layout in my parent structure, the that one will take precedence.
These features are great for skinning modules or just overriding view capabilities a-la-carte. So please take note of them as it is very powerful.
By now, we should all know the default SES route ColdBox offers: addRoute(":handler/:action?")
. This means that we can target a handler with or without a package and an action for execution. In ColdBox we have also added a package resolver that will detect this pattern for module and package directories so we can do URL safe and SES friendly URLs for these executions by convention.
If we do not have this feature, this is how the URLs would look like:
However, thanks to package resolving in the SES interceptor we can do links like this:
Much better and nicer huh? Of course! So potentially, with one route we could write entire applications.
All modules have the capability to leverage URL routing in a portable manner. They will automatically register an entry point URL pattern via the this.entryPoint
setting. If an incoming URL has that specific pattern, then it will search for the module's routing table for a match.
You can also add these entry points manually in the host application's routing file: config/Routes.cfm
. However, you will loose all module portability. We do this by using a method called addModuleRoutes()
method.
addModuleRoutes(pattern, module)
: Insert the module routes at this location in the configuration file with the applied URL pattern.
The URL pattern is the normal URL Mappings pattern we are used to and the module is the name of the module you want to target. What this does is create an entry point pattern that will identify the module's routing capabilities. For example, we create the following:
What the previous method calls do is bind a static URL entry pattern to a module. So if the framework detects an incoming URL with the starting point to be /blog, it will then match the simpleblog routes. Once matched, it will now try to match the rest of the incoming URL with the module's custom routes. Let's do a full example, below are some custom routes for my blog module in its ModuleConfig.cfc
:
Now, let's say the URL that is incoming is:
Then this will resolve to the /blog
pattern in the host that says, now look in the module simpleblog for routes. The left over part of the URL is nothing or /
so the pattern that matches is my first declared pattern:
This means, that we will execute the modules' blog
handler and the showPosts
method. Now, let's say the next URL that comes is:
Then this will match the simpleblog
module via the static /blog
entry point and then it tries to find a match for /2009/09
in the modules' routes and it does!
When you declare a module and you define a models
folder then the framework automatically register all models in that folder for you using a namespace of @moduleName
. This means that all models are registered according to their CFC name plus the namespace.
Info Internally, ColdBox uses WireBox's
mapDirectory()
to map the entiremodels
directory for you.
Let's say you have a module called store
and a OrderService.cfc
inside of the models
folder. That object will have a WireBox id of OrderService@store
.
Hint You can alter this behavior by setting the
this.autoMapModels
configuration setting to false. You can also alter the namespace used via thethis.modelNamespace
configuration property.
Every module can tell ColdBox what ColdFusion mapping to register for it that points to the module root location on disk when deployed. This is a huge feature for portability and the ability to influence the ColdFusion mappings for you via ColdBox. Just create a this.cfmapping
property in your ModuleConfig.cfc
:
You can also implicitly setup a default layout to use when rendering views from the specific module. This only works when you call the event.setView()
method from your module event handlers. Once called, the framework will discover the default layout to render that view in. You set up the default layout for a module by using the layoutSettings structure in your configuration:
Caution The set default layout MUST exist in the layouts folder of your module and the declaration must have the
.cfm
extension attached.
You can also explicitly render layouts or views directly from a module via the Renderer plugin's renderLayout()
and renderView()
methods. These methods now can take an extra argument called module.
renderLayout(layout, view, module)
renderView(view, cache, cacheTimeout,cacheLastAccessTimeout,cacheSuffix, module)
So you can easily pinpoint renderings if needed.
Caution Please note that whenever these methods are called, the override algorithms ALSO are in effect. So please refer back to the view and layout parent lookup properties in your modules' configuration.
The request context object (event
parameter received in handlers/layouts/views) has also been expanded to have the following module methods:
getCurrentModule()
: Returns the module name of the currently executing module event.
getModuleRoot([moduleName])
: Returns the web accessible root to the modules root directory. If you do not pass the explicit module name, we will default to use the getCurrentModule()
in the request.
The last method is what is really interesting when building visual modules that need assets from within the module itself. You can easily target the web-accessible path to the module by using the getModuleRoot()
method. Below are some examples:
As you can see, this is essential when building module UIs or layouts.
Property
Description
appMapping
The appMapping
setting of the current host application
binder
The WireBox configuration binder object
cachebox
A reference to CacheBox
controller
A reference to the application's ColdBox Controller
log
A pre-configured LogBox Logger object for this specific class object (coldbox.system.logging.Logger
)
logBox
A Reference to LogBox
moduleMapping
The moduleMapping
setting of the current module. This is the path needed in order to instantiate CFCs in the module.
modulePath
The absolute path to the current loading module
wirebox
A Reference to WireBox
Once the public properties are set, we are now ready to configure our module. You will do this by creating a simple method called configure()
and adding variables to the following configuration structures:
It is important to note that there is only ONE running application and the modules basically leach on to it. So the following structures will append their contents into the running application settings: parentSettings, datasources, webservices, customInterceptionPoints and interceptors.
All of the configuration settings that are declared in your configuration object will be added to a key in the host application settings called modules. Inside of this structure all module configurations will be stored according to their module name and remember that the module name comes from the name of the directory on disk. So if you have a module called helloworld then the settings will be stored in the following location: ConfigSettings.modules.helloworld
Below is an example of some settings:
The default order of overrides ColdBox offers is both viewParentLookup & layoutParentLookup
to true. This means that if the layout or view requested to be rendered by a module exists in the overrides section of the host application, then the host application's layout or view will be rendered instead. Let's investigate the order of discover:
viewParentLookup = true
Host override module specific (e.g. {HOST}/views/modules/myModule/myView.cfm)
Host override common (e.g. {HOST}/views/modules/myView.cfm)
Module view (e.g. /modules/myModule/views/myView.cfm)
Default view discovery from host (e.g. {HOST}/views/myView.cfm)
viewParentLookup = false
Module view (e.g. /modules/myModule/views/myView.cfm)
Host override module specific (e.g. {HOST}/views/modules/myModule/myView.cfm)
Host override common (e.g. {HOST}/views/modules/myView.cfm)
Default view discovery from host (e.g. {HOST}/views/myView.cfm)
layoutParentLookup = true
Host override module specific (e.g. {HOST}/layouts/modules/myModule/myLayout.cfm)
Host override common (e.g. {HOST}/layouts/modules/myLayout.cfm)
Module layout (e.g. /modules/myModule/layouts/myLayout.cfm)
Default layout discovery from host (e.g. {HOST}/layouts/Default.cfm)
layoutParentLookup = false 1. Module layout (e.g. /modules/myModule/layouts/myLayouts.cfm)
2. Host override module specific (e.g. {HOST}/layouts/modules/myModule/myLayout.cfm)
3. Host override common (e.g. {HOST}/layouts/modules/myLayout.cfm)
4. Default layout discovery from host (e.g. {HOST}/layouts/Default.cfm)
Let's do some real examples, I am building a simple module with 1 layout and 1 view. Here is my directory structure for them:
Now, in my handler code I just want to render the view by using our typical event.setView()
method calls or implicit views.
The ColdBox rendering engine has been adapted to support module renderings and also to support multiple discovery algorithms when rendering module layouts and views. When you declare a module you can declare two of its public properties to determine how rendering overrides occur. These properties are:
You can bundle your modules into an organizational folder that has the convention name of {name}-bundle
. This will allow you to bundle different modules together for organizational purposes only. Great for git ignores.
ColdBox 4 allows you to nest modules within modules up to the Nth degree. You can package a module with other modules that can even contain other modules within them. It really opens a great opportunity for better packaging, delivery and a further break from monolithic applications. To use, just create a modules
folder in your module and drop the modules there as well. You can also just create a box.json
in the root of your module and let CommandBox manage your module dependencies. Here is an example of the ColdBox ORM Module:
Modules can declare other module dependencies in the ModuleConfig.cfc
via the this.dependencies
property. This means that before the declared module is activated, the dependencies will be registered and activated FIRST and then the declared module will load.
This means that the javaloader
module HAS to load and activate first before this module can finish registering and activating.
This tells the framework to render the simple/index.cfm
view in the module simpleModule
. However, let's override the view first. Go to the host application's views folder and create a folder called modules
and then a folder according to the module name, in our case simpleModule
:
What this does, is create the override structure that you need. So now, you can map 1-1 directly from this overrides folder to the module's views folder. So whatever view you place here that matches 1-1 to the module, the parent will take precedence. Now remember this is because our viewParentLookup
property is set to true. If we disable it or set it to false, then the module takes precedence first. If not found in the module, then it goes to the parent or host application and tries to locate the view. That's it, so if I place a simple/index.cfm
view in my parent structure, the that one will take precedence.
Property
Type
Description
parentSettings
struct
Settings that will be appended and override the host application settings
settings
struct
Custom module settings that will only be available to the module. If parseParentSettings
is set to true (default), then settings from config/Coldbox.cfc
for this module will be merged with these settings. (Think of these as default settings in that case.) Please see the retrieving settings section
conventions
struct
A structure that explains the layout of the handlers, plugins, layouts and views of this module.
datasources
struct
A structure of datasource metadata that will append and override the host application datasources configuration
interceptorSettings
struct
A structure of settings for interceptor interactivity which includes the following sub-keys:
interceptors
array of structs
An array of declared interceptor structures that should be loaded in the entire application. Follows the same pattern as the ConfigurationCFC interceptor declarations.
layoutSettings
struct
A structure of elements that setup layout configuration data for the module with the following keys:
routes
array
An array of declared URL routes or locations of routes for this module. The keys of the structure are the same as the addRoute() method of the SES interceptor or a simple string location to the routes file to include.
wirebox
struct
A structure of WireBox configuration data, please refer to the [WireBox Configuration](http://wiki.coldbox.org/wiki/WireBox.cfm#Configure()_method) area or just use the injected binder object for mappings.
Property
Type
Required
Default
Description
viewParentLookup
boolean
false
true
If true, coldbox checks for views in the parent overrides first, then in the module. If false, coldbox checks for views in the module first, then the parent.
layoutParentLookup
boolean
false
true
If true, coldbox checks for layouts in the parent overrides first, then in the module. If false, coldbox checks for layouts in the module first, then the parent.