Building Modules
If I have seen further than others, it is by standing on the shoulders of giants.
- Isaac Newton
Scott Steinbeck
- Software Developer
- 20+ Year of experience
- Father
- Hardware Tinkerer
Hobbies
- Coding on my free time
- Overcommitting myself
- Automating everything
- IOT Development
- Teaching Coding/Electronics
- Contributing to open source
What is modular thinking?
Separating a program’s functions into
independent pieces,
each containing all the parts needed to execute a
single aspect
of the functionality
Why should i build a module?
Modular programming enforce logical boundaries between components and improve maintainability
Managing Code
Why should i build a module?
The same code can be used in many applications.
Code Reuse
Why should i build a module?
Teams can develop modules separately and do not require knowledge of all modules in the system.
SEPARATION OF CONCERNS
Why should i build a module?
Easily extend ANY application by just building on top of the modular architecture
EASIER EXTENDABILITY
USING A Public/private MODULE
FORGEBOX.IO
TYPES OF MODULES
INTERNAL MODULE Examples
EXTERNAL (THIRD PARTY)
MODULE Examples
API Versioning /API/v1
Validation & Security
Shared Utility
Mailing Services
Internationalization
ORM & Query Services
Logging & Debugging Service Integration
API Wrappers (s3,twilio,RabbitMQ,stripe,swagger, etc.)
How do i install a module into coldbox?
box install jsondiff
Using commandbox
Manual Install
Download Zip from Repo
Unzip
COPY/MOVE FOLDER INTO
/MODULES FOLDER
IN COLDBOX APP
HOW DO I USE A MODULE?
RTFM
Adam Cameron (TM)
(BUT FOR A MAJORITY...)
FYI some modules don't need to be injected
(eg. cbsecurity)
HOW DO I change settings/
configuration
in A MODULE?
moduleSettings = {
qb = {
defaultGrammar = "MySQLGrammar@qb"
}
};
I can't find a module that solves my problem!!! What should i do?
Step 1. Check to make sure you are searching for the right solution
If you have made it here its because you need to build it yourself!
Great where do we start
DONT REINVENT THE WHEEL....
CHANCES ARE YOUR NOT THE FIRST TO HAVE THIS PROBLEM
LOOK FOR INSPIRATION FROM PROJECTS IN OTHER LANGUAGES
interceptor LIBRARIES
Maintenance Mode
SSL Support Interceptor
Deploy
Multi Domain SES
Reserved Routes
WRAPPER LIBRARIES
STRIPE API
PINGDOM API (UPTIME MONITOR)
SPOTIFY API
TWITTER API
RACKSPACE CLOUD API
CFSMARTYSTREETS (ADDRESS VERIFICATION/AUTOCOMPLETE)
TWILIO API (SMS/CALL SERVICES)
ALGOLIA API
300+ ADDITIONAL API WRAPPERS ON FORGEBOX.IO
"INSPIRED" LIBRARIES
COMMANDBOX BULLET TRAIN
QB (FLUENT QUERY BUILDER)
CFFLOW (WORKFLOW ENGINE)
QUICK (COLDBOX ORM)
INTEGRATED (INTEGRATION TESTING)
RULEBOX (RULE ENGINE)
COLDBOX TEMPLATING LANGUAGE
WEIGHTED ROUND ROBIN (RANDOMIZE WIDGETS)
INSPIRATION FROM JAVA, NODE, PHP, RUBY
JAVA WRAPPER LIBRARIES
CFZXING (BARCODE GENERATOR)
CB STREAMS (JAVA STREAMS)
ITEXTCFC (iTEXT PDF CREATOR)
SQL FORMATTER
CBMARKDOWN (MARKDOWN TO HTML)
RECURRENCE (RECURRING CAL EVENTS)
CBCODEHIGHLIGHT (PYGMENTS SYNTAX HIGHLIGHT)
JWT CFML (JWT TOKEN CREATION)
ALLOWING FOR SIMPLIFIED USAGE
OF EXISTING JAVA LIBRARIES
variables available to the
module_config
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 |
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 |
Module Helpers
Every module can declare an array of helper templates that contain methods that will be added as global application helpers.
This is a great way to collaborate global functions to the running MVC application.
This is done via the this.applicationHelper
directive in your ModuleConfig.cfc
.
This is an array of relative paths in the module that ColdBox will load as mixins.
this.applicationHelper = [
"includes/helpers.cfm"
];
// Here is a sample of a helpers.cfm:
<cfscript>
function auth() {
return wirebox.getInstance( "AuthenticationService@cbauth" );
}
</cfscript>
What is wirebox?
- Dependency Injection Framework for CFML
- Creates, assembles, and stores your objects
- Controls the lifetime of your objects (Scopes)
- Easily define your object dependencies
- Used in ANY CFML application
- Removes creation boilerplate, duplicate code
<cfdump var=#( wirebox.getBinder().getMappings().keyArray() )#>
Helpful for debugging
ON DI COMPLETE()
Scopes
- Scopes tell WireBox where and how long to store our objects
- Common Scopes
- NOSCOPE (default) - new instance each time requested
- SINGLETON - single instance for injector lifetime
- REQUEST - single instance for request lifetime
- SESSION - single instance for session lifetime
- APPLICATION - single instance for application lifetime
- SERVER - single instance for server lifetime
javaloader
-
create( class )
- Create a loaded Java class -
appendPath( dirPath, filter)
- Appends a directory path of .jar's,.classes to the current loaded class loader. -
getLoadedURLs()
- Get all the loaded URLs
component accessors=true{
// DI
property name="javaLoader" inject="loader@cbjavaloader";
property name="settings" inject="box:modulesettings:bcrypt";
/**
* Constructor
*/
BCrypt function init(){
return this;
}
function onDIComplete(){
variables.bcrypt = variables.javaLoader.create( "org.mindrot.jbcrypt.BCrypt" );
}
}
Life-cycle Events
function onLoad(){
// Register some tables in my database and activate some features
controller.getWireBox().getInstance('MyModuleService').activate();
log.info( 'Module #this.title# loaded correctly' );
}
function onUnLoad(){
// Cleanup some stuff and logit
controller.getWireBox().getInstance('MyModuleService').shutdown();
// Log we unloaded
log.info( 'My module unloaded successfully!' );
}
Lets see
some code....
coldbox module-template
- .github/workflows - These are the github actions to test and build the module via CI
- build - This is the CommandBox task that builds the project. Only modify if needed. Most modules will never modify it. (Modify if needed)
- test-harness - This is a ColdBox testing application, where you will add your testing files, specs etc.
- .cfformat.json - A CFFormat using the Ortus Standards
- .cflintrc - A CFLint configuration file according to Ortus Standards
- .editorconfig - Smooth consistency between editors
- .gitattributes - Git attributes
- .gitignore - Basic ignores. Modify as needed.
- .markdownlint.json - A linting file for markdown docs
- box.json - The box.json for YOUR module. Modify as needed.
- changelog.md - A nice changelog tracking file
- ModuleConfig.cfc - Your module's configuration. Modify as needed.
- readme.md - Your module's readme. Modify as needed.
- server-xx@x.json - A set of json files to configure the major engines your modules supports.
coldbox module-template
.github/workflows -
These are the GitHub actions to test and build the module via CI
FYI this template is set up for the normal ortus team workflow so if you decide to use the GitHub action workflow you will need to modify the settings
Automated Tests for your module in coldbox with multiple server versions
coldbox module-template
build -
This is the CommandBox task that builds the project. Only modify if needed. Most modules will never modify it. (Modify if needed
Does some initial scaffolding for your app to name it and set up its module name
box task run taskFile=build/setupTemplate
coldbox module-template
test-harness -
This is a ColdBox testing application,
where you will add your testing files, specs etc.
coldbox module-template
.cfformat.json -
A CFFormat using the Ortus Standards
{
"array.empty_padding": false,
"array.padding": true,
"array.multiline.min_length": 50,
"array.multiline.element_count": 2,
"array.multiline.leading_comma.padding": true,
"array.multiline.leading_comma": false,
"alignment.consecutive.assignments": true,
"alignment.consecutive.properties": true,
"alignment.consecutive.params": true,
"alignment.doc_comments" : true,
"brackets.padding": true,
"comment.asterisks": "align",
"binary_operators.padding": true,
"for_loop_semicolons.padding": true,
"function_call.empty_padding": false,
"function_call.padding": true,
"function_call.multiline.leading_comma.padding": true,
"function_call.casing.builtin": "cfdocs",
"function_call.casing.userdefined": "camel",
"function_call.multiline.element_count": 3,
"function_call.multiline.leading_comma": false,
"function_call.multiline.min_length": 50,
"function_declaration.padding": true,
"function_declaration.empty_padding": false,
"function_declaration.multiline.leading_comma": false,
"function_declaration.multiline.leading_comma.padding": true,
"function_declaration.multiline.element_count": 3,
"function_declaration.multiline.min_length": 50,
"function_declaration.group_to_block_spacing": "compact",
"function_anonymous.empty_padding": false,
"function_anonymous.group_to_block_spacing": "compact",
"function_anonymous.multiline.element_count": 3,
"function_anonymous.multiline.leading_comma": false,
"function_anonymous.multiline.leading_comma.padding": true,
"function_anonymous.multiline.min_length": 50,
"function_anonymous.padding": true,
"indent_size": 4,
"keywords.block_to_keyword_spacing": "spaced",
"keywords.group_to_block_spacing": "spaced",
"keywords.padding_inside_group": true,
"keywords.spacing_to_block": "spaced",
"keywords.spacing_to_group": true,
"keywords.empty_group_spacing": false,
"max_columns": 115,
"metadata.multiline.element_count": 3,
"metadata.multiline.min_length": 50,
"method_call.chain.multiline" : 3,
"newline":"\n",
"property.multiline.element_count": 3,
"property.multiline.min_length": 30,
"parentheses.padding": true,
"strings.quote": "double",
"strings.attributes.quote": "double",
"struct.separator": " : ",
"struct.padding": true,
"struct.empty_padding": false,
"struct.multiline.leading_comma": false,
"struct.multiline.leading_comma.padding": true,
"struct.multiline.element_count": 2,
"struct.multiline.min_length": 60,
"tab_indent": true
}
coldbox module-template
.cfformat.json - A CFFormat using the Ortus Standards
https://github.com/Ortus-Solutions/coding-standards/blob/master/coldfusion.md
coldbox module-template
.cflintrc - A CFLint configuration file according to Ortus Standards
{
"rule": [],
"includes": [
{ "code": "AVOID_USING_CFINCLUDE_TAG" },
{ "code": "AVOID_USING_CFABORT_TAG" },
{ "code": "AVOID_USING_CFEXECUTE_TAG" },
{ "code": "AVOID_USING_DEBUG_ATTR" },
{ "code": "AVOID_USING_ABORT" },
{ "code": "AVOID_USING_ISDATE" },
{ "code": "AVOID_USING_ISDEBUGMODE" },
{ "code": "AVOID_USING_CFINSERT_TAG" },
{ "code": "AVOID_USING_CFUPDATE_TAG" },
{ "code": "ARG_VAR_CONFLICT" },
{ "code": "ARG_HINT_MISSING" },
{ "code": "ARG_HINT_MISSING_SCRIPT" },
{ "code" : "ARGUMENT_INVALID_NAME" },
{ "code" : "ARGUMENT_ALLCAPS_NAME" },
{ "code" : "ARGUMENT_TOO_WORDY" },
{ "code" : "ARGUMENT_IS_TEMPORARY" },
{ "code": "CFQUERYPARAM_REQ" },
{ "code": "COMPARE_INSTEAD_OF_ASSIGN" },
{ "code": "COMPONENT_HINT_MISSING" },
{ "code" : "COMPONENT_INVALID_NAME" },
{ "code" : "COMPONENT_ALLCAPS_NAME" },
{ "code" : "COMPONENT_TOO_SHORT" },
{ "code" : "COMPONENT_TOO_LONG" },
{ "code" : "COMPONENT_TOO_WORDY" },
{ "code" : "COMPONENT_IS_TEMPORARY" },
{ "code" : "COMPONENT_HAS_PREFIX_OR_POSTFIX" },
{ "code": "COMPLEX_BOOLEAN_CHECK" },
{ "code": "EXCESSIVE_ARGUMENTS" },
{ "code": "EXCESSIVE_FUNCTIONS" },
{ "code": "EXPLICIT_BOOLEAN_CHECK" },
{ "code": "FUNCTION_TOO_COMPLEX" },
{ "code": "FUNCTION_HINT_MISSING" },
{ "code": "FILE_SHOULD_START_WITH_LOWERCASE" },
{ "code": "LOCAL_LITERAL_VALUE_USED_TOO_OFTEN" },
{ "code": "GLOBAL_LITERAL_VALUE_USED_TOO_OFTEN" },
{ "code": "MISSING_VAR" },
{ "code" : "METHOD_INVALID_NAME" },
{ "code" : "METHOD_ALLCAPS_NAME" },
{ "code" : "METHOD_IS_TEMPORARY" },
{ "code": "NESTED_CFOUTPUT" },
{ "code": "NEVER_USE_QUERY_IN_CFM" },
{ "code": "OUTPUT_ATTR" },
{ "code" : "QUERYPARAM_REQ" },
{ "code": "UNUSED_LOCAL_VARIABLE" },
{ "code": "UNUSED_METHOD_ARGUMENT" },
{ "code": "SQL_SELECT_STAR" },
{ "code": "SCOPE_ALLCAPS_NAME" },
{ "code": "VAR_ALLCAPS_NAME" },
{ "code": "VAR_INVALID_NAME" },
{ "code": "VAR_TOO_WORDY" }
],
"inheritParent": false,
"parameters": {
"TooManyFunctionsChecker.maximum" : 50
}
}
coldbox module-template
.editorconfig - Smooth consistency between editor (vscode coding settings)
# http://editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
indent_style = tab
indent_size = 4
tab_width = 4
[*.yml]
indent_style = space
indent_size = 2
[*.{md,markdown}]
trim_trailing_whitespace = false
insert_final_newline = false
coldbox module-template
.gitignore - Basic ignores. Modify as needed
# Artifacts and temp folders
.artifacts/**
.tmp/**
# Engine + Secrets
.env
.engine/**
# Dependencies
test-harness/coldbox/**
test-harness/docbox/**
test-harness/testbox/**
test-harness/logs/**
test-harness/modules/**
# modules
modules/**
# log files
logs/**
coldbox module-template
box.json - The box.json for YOUR module. Modify as needed
{
"name" : "JSON Diff",
"version" : "1.0.0",
"author" : "Scott Steinbeck,
"homepage" : "https://github.com/scottsteinbeck/cbjsondiff",
"documentation" : "https://github.com/scottsteinbeck/cbjsondiff",
"repository" : { "type" : "git", "url" : "https://github.com/scottsteinbeck/cbjsondiff" },
"bugs" : "https://github.com/scottsteinbeck/cbjsondiff",
"shortDescription" : "Description goes here",
"slug" : "cbjsondiff",
"type" : "modules",
"keywords":"",
"license" : [
{
"type" : "Apache2",
"url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
}
],
"contributors" : [
],
"dependencies" :{
},
"devDependencies" :{
"commandbox-cfformat":"*",
"commandbox-docbox":"*",
"commandbox-dotenv":"*",
"commandbox-cfconfig":"*"
},
"ignore":[
"**/.*",
"test-harness",
"/server*.json"
],
"scripts":{
"setupTemplate": "task run taskFile=build/SetupTemplate.cfc",
"build:module":"task run taskFile=build/Build.cfc :projectName=`package show slug` :version=`package show version`",
"build:docs":"task run taskFile=build/Build.cfc target=docs :projectName=`package show slug` :version=`package show version`",
"release":"recipe build/release.boxr",
"format":"cfformat run helpers,models,test-harness/tests/,ModuleConfig.cfc --overwrite",
"format:watch":"cfformat watch helpers,models,test-harness/tests/,ModuleConfig.cfc ./.cfformat.json",
"format:check":"cfformat check helpers,models,test-harness/tests/,ModuleConfig.cfc",
"cfpm":"echo '\".engine/adobe2021/WEB-INF/cfusion/bin/cfpm.sh\"' | run",
"cfpm:install":"echo '\".engine/adobe2021/WEB-INF/cfusion/bin/cfpm.sh\" install ${1}' | run",
"install:2021":"run-script cfpm:install zip,debugger"
},
"testbox":{
"runner":"http://localhost:60299/tests/runner.cfm"
}
}
coldbox module-template
changelog.md - A nice changelog tracking file
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
----
## [1.0.0] => 2021-JAN-01
* First iteration of this module
coldbox module-template
ModuleConfig.cfc - Your module's configuration. Modify as needed
/**
* Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
* www.ortussolutions.com
* ---
*/
component {
// Module Properties
this.title = "JSON Diff";
this.author = "Ortus Solutions";
this.webURL = "https://www.ortussolutions.com";
this.description = "Take 2 coldfusion structs or arrays and return the differences between them";
this.version = "@build.version@+@build.number@";
// Model Namespace
this.modelNamespace = "cbjsondiff";
// CF Mapping
this.cfmapping = "cbjsondiff";
// Dependencies
this.dependencies = [];
/**
* Configure Module
*/
function configure(){
settings = {
ignoreKeys = []
};
}
/**
* Fired when the module is registered and activated.
*/
function onLoad(){
}
/**
* Fired when the module is unregistered and unloaded
*/
function onUnload(){
}
}
coldbox module-template
readme.md - Your module's readme. Modify as needed
coldbox module-template
readme.md - Your module's readme. Modify as needed
coldbox module-template
server-xx@x.json - A set of json files to configure the major engines your modules supports.
These can be used for testing manually or will automatically be run by Github Actions
cd test-harness
box install
start lucee@5
browse to the webpage
coldbox module-template
API Docs - build/Build.cfc
The build task will take care of building API Docs using DocBox for you but **ONLY** for the `models` folder in your module.
if you need more items documented you can modify the build.cfc file
Copy of Building Coldbox Modules
By Eric Peterson
Copy of Building Coldbox Modules
learning how to construct modules and how others have constructed modules for ColdBox
- 384