FW/1 Example Application - Project Structure
29 Mar 2015This article talks about the topic of project structure in a ColdFusion and FW/1 Example Application and is the first one in the following series:
- Project Structure
- Forms and Validation Patterns
- Use of User Defined Function Libraries
- Interaction with a Remote Service
- BDD and Integration Tests
It assumes you are not new to CFML, so don’t take this as a detailed guide or tutorial, but more of a ‘getting started’ reference on using this MVC framework to create Object-Oriented ColdFusion apps.
Project Structure for a FW/1 Application
Reading pre-requisites: Developing Applications with FW/1.
The Clipping app was designed with Subsystems enabled. Using those is completely optional, but I decided to use them so that, in the future, it will be easier to add functionality, by incorporating third party apps.
The suggested project structure is:
├── clipping
│ |
│ ├── common
│ │ └── layouts
│ ├── customtags
│ ├── home
│ │ ├── controllers
│ │ ├── model
│ │ │ ├── beans
│ │ │ └── services
│ │ └── views
│ │ ├── clipping
│ │ ├── helpers
│ │ └── main
│ ├── lib
│ ├── setup
│ ├── static
│ └── tests
│ └── specs
│
├── testbox
└── CFSelenium
With the /clipping
folder being the project’s root, we have:
/common
- This folder holds the layout files that will be used in every subsystem.
/customtags
- This is where CFML tags are stored. I added a mapping in Application.cfc pointing to this folder.
/home
- The directory that contains the clipping application’s code - A.K.A. the default subsystem
-
with its own controllers, models and views.
/lib
- Stores User Defined Functions and Libraries, that will be available to all subsystems.
/setup
- Stores SQL scripts used to prepare the databases
(those files are not used by the application, so feel free to delete this).
/static
- Keeps all static files, separated in /js, /css, /images and /ckeditor subfolders.
/tests
- This is where we keep and run ‘unit’ and integration tests (more on that later.)
Important:
The /testbox
and /CFSelenium
folders are int the webserver’s root for simplicity.
You should not use this configuration in a web-accessible environment.
Application.cfc file
Here’s the interesting bits of code:
// ------------------------ APPLICATION SETTINGS ------------------------ //
this.name = "clipping_app";
this.sessionManagement = true;
this.sessionTimeout = createTimeSpan(0,2,0,0);
this.dataSource = "dtb_clipping";
this.test_datasource = "dtb_clipping_test";
this.ormEnabled = true;
this.ormsettings = {
dbcreate="update",
dialect="MySQL",
eventhandling="False",
eventhandler="root.home.model.beans.eventhandler",
logsql="true",
flushAtRequestEnd = "false"
};
Seems pretty straight forward, right? Notice that we also set a 'test_datasource'
variable
(that should point to a mirror database), to be used exclusively when running tests.
This is completely optional, and I’ll write about some pros and cons in the “running tests” article.
// mappings and other settings
this.mappings["/root"] = getDirectoryFromPath(getCurrentTemplatePath());
this.customTagPaths = this.mappings["/root"] & "customtags"
this.triggerDataMember = true // so we can access properties directly (no need for getters and setters)
The second mapping tells our application server to look for custom tags in this app’s specific folder (I like this approach since it makes source control and distribution easier)
Framework settings below:
// ------------------------ FW/1 SETTINGS ------------------------ //
variables.framework = {
reloadApplicationOnEveryRequest = true, //use only in dev
trace = false,
// places where you don't want to load the framework
unhandledPaths = '/clipping/tests/',
.....
.....
// enable the use of subsystems
usingSubsystems = true,
defaultSubsystem = 'home',
siteWideLayoutSubsystem = 'common',
// changes for FW/1 3.0
diLocations = "./home/model/"
}
unhandled paths
is a list of subdirectories where we don’t want to use FW/1.
In this case it’s only the tests
folder, but I can see this being used when
integrating with legacy apps, for example.
diLocations
, according to the docs, is “the set of folders that DI/1, AOP/1,
or WireBox will scan for CFCs.” - This parameter was added in version 3.0.
In this case, it’s referencing the path to the models in the ‘home’ subsystem.
Here’s a way to define settings according to environment:
// ------------------------ ENVIRONMENT DEFINITIONS ------------------------ //
public function getEnvironment() {
if ( findNoCase( "localhost", CGI.SERVER_NAME ) ) return "prod";
if ( findNoCase( "127.0.0.1", CGI.SERVER_NAME ) ) return "dev";
else return "prod";
}
variables.framework.environments = {
dev = { reloadApplicationOnEveryRequest = true, trace = true,},
prod = { password = "supersecret" }
}
Setting application scoped variables when the app starts:
function setupApplication() {
// copy dsn names to application scope
application.datasource = this.datasource;
application.recordsPerPage = 12 // pagination setting,
// used in all services and tests
// include UDF functions
// functions inside the CFC can be referred by application.UDFs.functionName()
application.UDFs = createObject("component", "lib.functions");
// settings used in tests
application.test_datasource = this.test_datasource;
application.testsRootMapping = "/clipping/tests/specs";
application.testsBrowseURL = "http://" & CGI.HTTP_HOST & "/clipping";
}
Please notice how the line application.UDFs = createObject("component", "lib.functions");
saves an instance of the ‘functions’ library to the application scope,
so those functions can be used anywhere.
Finally:
function setupRequest() {
// CSRF Token, unique for each user/request
request.csrftoken = CSRFGenerateToken();
}
This generates a CSRF Token at the start of the every request. We are going to be using this when forms are submitted, to prevent CSRF attacks.
There are other setup methods available to be used in the Application.cfc
.
They are optional but I left them in the code for future reference.
Please see the fw1-clipping github project page for the full source code.