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

  • 276