Marcin Szczepanski webqem Sunday, 21 July 13 Good morning, my name's Marcin Szczepanski and today I'm going to be talking to you about adding some structure to Coldfusion applications, specifically using FW/1. I'll be covering some more of the "advanced" features in the framework, for want of a better word, but essentially just showing you some of the features available that you might not be aware of or have used.
I work for webqem, a digital agency based in Sydney I’m a "developer" - essentially covering everything from back-end to front-end. I've been developing with ColdFusion for almost 6 years, basically from around the CFMX/CF7 days, but have been involved with the web professionally since 1999.
two years ago, as it seemed to be a nice change from some of the "heavier" CF frameworks out there like FuseBox, ColdBox, etc. What attracted me to this framework was it's simplicity - it's a single CFC that you drop into your application and it’s convention over configuration, so you need to do very little in order to get a simple app up and running using the framework.
reason this presentation includes the phrase "adding structure to Coldfusion Applications" is because I believe that CF often gets a bad rap for the same sort of reasons PHP does - the mixing of code and presentation, and is seen as spaghetti code. However as with PHP there's no reason you can't write well organised CF applications, and I think FW/1 is a great way to add this structure without a huge amount of overhead.
• FW/1 Routes • FW/1 Sub-systems • Testing FW/1 Applications Sunday, 21 July 13 So what I'm going to cover in today's presentation is: * a quick background and refresher on FW/1 and the basics, but not in any huge detail * we'll dive into how FW/1 works with dependency injection frameworks like Sean Corfield's own DI/1 or the more common Coldspring * We'll talk about FW/1 routes and how they can be used to provide "pretty" URLs and REST style APIs * We'll cover sub-systems and how you can use them to break up your application into re- useable logical units * And lastly we'll touch on some ideas for how you can test the various components of your FW/1 applications
refresher - we'll do a whirlwind tour of the major components of an FW/ 1 application, hopefully if you're new to FW/1 you'll still at least be able to get the general gist of how FW/1 applications are structured.
functions within an FW/1 application you use actions - an example of an action is "main.default" - it is comprised of a section and item name. This is actually FW/1's default action. Each action in an FW/1 application has a corresponding view.
default.cfm Sunday, 21 July 13 Views are CFM pages named the same as the item, and they sit in a subdirectory with the name of the section, within the views directory. A view is the only required part of a FW/1 application, but without a view there's nothing to render so the framework throws an error. With the default configuration all a minimal FW/1 app requires a single view in views/main/ default.cfm.
then going up another level a view can be rendered inside a layout - the default layout is in layouts/default.cfm. A layout needs output the contents of the "body" variable to include the output of views lower in the hierarchy.
/ section.cfm <cfset request.layout = false> Sunday, 21 July 13 Layouts can be nested, so FW/1 will first check for an item specific layout in "layouts/section/ item.cfm" Then a section layout in "layouts/section.cfm", and finally the default layout. Each layout must cfoutput "body" to render the output from lower down the stack. Nesting can be stopped by setting request.layout = false in the layout CFM file - this can even be used in a view to prevent the default layout from being used.
13 Of course a minimal application like you just saw isn’t very useful - there needs to be somewhere to run your logic - and this is where Controllers come in. Using the default action example, with FW/1s convention over configuration approach the framework will look for a controller called main.cfc in the directory "controllers" - it then calls a function in this controller called "default".
struct rc) { rc.upperCaseName = UCase(rc.name); } } <p><strong>Hello <cfoutput>#rc.upperCaseName#</cfoutput>!</strong></p> controllers/main.cfc views/main/default.cfm Sunday, 21 July 13 So when the item function is called, your controller is passed a "request context" structure in a variable called "rc". The request context is automatically populated with the content of any FORM and GET variables. The job of your controller job is to read any user supplied data out of rc, and set any new properties on rc that will then accessible by the view.
talk about services and dependency injection. I didn't cover services in the basics above, but services are what your controllers call on in order to perform the business logic required for your application to function. The controller shouldn’t be making any of these business logic calls itself.
Let’s say we have our default controller, and we’d like to use a “Person Service” to get some data about people to display to the user. This means that the person service is now a dependency for the controller, and we need some way to provide the default controller with a person service.
function init () { var service = createObject("component", "model.service.person").init(); setPersonService(service); return this; } public void function default (required struct rc) { rc.qPeople = getPersonService().getPeople(); } } controllers/main.cfc Sunday, 21 July 13 Without Dependency Injection your code might look like this. When you init your controller you create a person service and assign it to the personService property. All seems fair enough at this point.
July 13 Now let’s say that the person service requires a DAO to actually perform the data fetch from an external data store of some sort. This now means that person service has a dependency of it’s own - the person Data Access Object.
function init () { var dao = createObject("component", "model.dao.person").init(); var service = createObject("component", "model.service.person").init(dao); setPersonService(service); return this; } public void function default (required struct rc) { rc.qPeople = getPersonService().getPeople(); } } controllers/main.cfc Sunday, 21 July 13 This means that in our constructor we now need to do something like this, initialise the personDAO, initialise the personService passing it the personDAO, and finally set our personService. You could also have the personService initialise it’s own personDAO, but the end result is the same - you’re tightly coupling the controller, service and DAO. If I wanted to change the component that’s used as the DAO I’d need to modify my controller or service. So what does this look like with a dependency injection framework?
function default (required struct rc) { rc.qPeople = getPersonService().getPeople(); } } controllers/main.cfc Sunday, 21 July 13 .. like this. Your controller no longer needs to concern itself with where the personService comes from, it can just assume that it’s there. The Dependency Injection framework handles resolving the dependencies.
(string bean) Sunday, 21 July 13 - DI framework provides a “Bean Factory” - Bean is just a java object - Bean Factory is any component that implements these methods A Dependency Injection framework provides what is called a Bean Factory. The name “bean” comes from the Java world (Java Bean - get it?) - it effectively refers to a component that contains public getters and setters for it’s properties - for our purposes we just need to know that a “bean” is a component. So the responsibility of the bean factory, as the name implies, is to produce beans or components. At it’s core a Dependency Injection framework, at least those used by FW/1, needs to provide two functions - getBean and containsBean. Both take a bean name as a string, getBean will return some an instanciated object representing the bean asked for (if it exists).
beanFactory = ...; setBeanFactory(beanFactory); } Application.cfc Sunday, 21 July 13 - bean factory set in setupApplication You tell FW/1 about your bean factory by using the setBeanFactory function, here you can see how you would set a bean factory in your setupApplication function, we’ll get to the specifics of creating the bean factory shortly.
function default (required struct rc) { rc.qPeople = getPersonService().getPeople(); } } controllers/main.cfc Sunday, 21 July 13 - Auto-wiring So looking back at this code, when FW/1 creates the instance of your controller it will do what is called “auto-wiring” - for any properties it will ask it’s bean factory whether there is a bean matching the property name, in this case personService, and set the property to an instance of that bean. Internally the DI framework will also look at the properties of personService and satisfy those dependencies and so on down the line - so when your controller gets a personService object you can be fairly sure that it is ready to use.
July 13 The canonical URL format to access a FW/1 action is index.cfm?action=section.method, however FW/1 supports a number of ways to provide cleaner URLs, the first of these is the ability to take advantage of the PATH_INFO CGI variable to accept URLs like index.cfm/action/ section. As a part of this you can actually also pass the parameters into the path rather than using the query string - eg. index.cfm/action/section/id/5 is equivalent to index.cfm/action/ section?id=5
(generateSES = true) buildUrl(action="people.show", queryString="id=1") => /index.cfm/people/show/id/1 buildUrl(action=".show") => /index.cfm/people/show buildUrl(action="main.default") => /index.cfm buildUrl(action="people.show") => /people/show (SESOmitIndex = true) buildUrl(action="people.show", queryString="id=1") => /people/show/id/1 buildUrl(action=".show") => /people/show buildUrl(action="main.default") => / Sunday, 21 July 13 The recommended way to create links to framework actions within your views is using the buildURL function.
like to use a URLs like this? I’ve left the index.cfm in there because to take that out you need to use some web server rewrite rules, so we’ll keep it simple.
{"/people" = "301:/people/list"} }; Application.cfc Sunday, 21 July 13 Routes also support a few other features like limiting which HTTP method they apply to, and also being able to do a redirect.
"/people/show/id/\1/extension/\3"}, {"/(people|widgets)/(\d+)/$" = "/\1/show/id/\2"} ]; /people/1 => /people/show/id/1 /people/1.html => /people/show/id/1/extension/html /widgets/1 => /widgets/show/1 Application.cfc Sunday, 21 July 13 In the current “bleeding edge” version of FW/1, which will become 2.1 at some point, there is support for doing your own custom regular expression matches in routes. You can get pretty fancy with these.
up your application either because you want to more easily re-use parts in other applications or just that they're logically different parts of a whole - like say an admin system vs a public site. FW/1 allows you to do this with support for something called sub-systems.
13 So since you use actions to access your FW/1 controllers the action syntax needs to change when youBy turn on sub-systems. As the default sub-system is called home, the default action is now becomes: home:default.main
21 July 13 The default sub-system is called “home”, so say you had the “home” sub-system and wanted to add an “other” sub-system you would end up with a folder structure like this. As you can see each sub-system has it's own self-contained set of controllers and views - this means you could easily take this whole sub-system and drop it into another FW/1 application. Apart from the sub-system specific stuff there is a common layout folder. This means layouts now bubble up from the sub-system specific layout to common.
{ if (directoryExists(expandPath(subsystem & "/model"))) { var subSystemBeanFactory = new ioc(subsystem & "/model"); subSystemBeanFactory.setParent(getDefaultBeanFactory()); setSubsystemBeanFactory(subsystem, subSystemBeanFactory); } } Application.cfc Sunday, 21 July 13 When you use subSystems FW/1 will call a function called “setupSubSystem” in your app CFC, passing the name of the sub-system that is being initialised. You can use this call to initialise a bean factory specific to your sub-system. By default auto-wiring in sub-systems will only check that sub-system’s bean factory (if it exists), if it doesn’t exist then only the default bean factory will be checked. However if your bean factory supports setParent (which both Coldspring and DI/1 do) then if your bean factory doesn’t contain the requested bean the parent will be asked for it.
• JSoup • tag soup parser Sunday, 21 July 13 - mxunit is the de-facto unit testing framework for Coldfusion - supports running normal unit tests and providing output in a number of formats - supports stubbing and mocking to keep your tests focussed - test controllers and views in isolation - you’re not testing the framework - use JSoup for testing views - jQuery selector like syntax from HTML - “tag soup” passing ability useful as most views are just a fragment
• “framework-one” on Google Groups - Mailing List • https://github.com/seancorfield/di1 • http://www.coldspringframework.org/ • http://mxunit.org/ http://jsoup.org/ • https://github.com/marcins/cfobjective2012 • @MarcinS or [email protected] Sunday, 21 July 13 ENDE
• “framework-one” on Google Groups - Mailing List • https://github.com/seancorfield/di1 • http://www.coldspringframework.org/ • http://mxunit.org/ http://jsoup.org/ • https://github.com/marcins/cfobjective2012 • @MarcinS or [email protected] Sunday, 21 July 13 ENDE