Upgrade to Pro — share decks privately, control downloads, hide ads and more …

FW/1: Beyond the Basics

FW/1: Beyond the Basics

CF.Objective(ANZ) 2012 presentation on advanced FW/1 features and techniques.

Marcin Szczepanski

November 01, 2012
Tweet

More Decks by Marcin Szczepanski

Other Decks in Technology

Transcript

  1. Adding Structure to ColdFusion Applications with FW/1: Beyond the Basics

    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.
  2. Sunday, 21 July 13 First a bit about my background.

    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.
  3. FW/1 Sunday, 21 July 13 I started using FW/1 about

    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.
  4. Adding structure to ColdFusion applications Sunday, 21 July 13 The

    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.
  5. Agenda • Quick FW/1 refresher • FW/1 and Dependency Injection

    • 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
  6. FW/1 Sunday, 21 July 13 So first a quick FW/1

    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.
  7. FW/1 • Single CFC • cfcomponent extends="org.corfield.framework" { • Convention

    over Configuration • Configure via variables.framework variables.framework = { reloadApplicationOnEveryRequest = true }; Sunday, 21 July 13
  8. Actions main.default Section Item Sunday, 21 July 13 To access

    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.
  9. Views views / section / item.cfm views / main /

    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.
  10. Minimal FW/1 Application DEMO Sunday, 21 July 13 So let’s

    have a look at the structure of a minimal FW/1 application...
  11. Layouts layouts / default.cfm <cfoutput>#body#</cfoutput> Sunday, 21 July 13 So

    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.
  12. Layouts layouts / section / item.cfm layouts / default.cfm layouts

    / 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. Controllers main.default Section Item controllers/main.cfc function default() Sunday, 21 July

    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".
  14. Controllers Request Context component output="false" { public void function default(required

    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.
  15. Simple FW/1 Application DEMO Sunday, 21 July 13 So now

    let’s have a look at some of these concepts in a “Simple” FW/1 application
  16. Services and Dependency Injection Sunday, 21 July 13 Now let's

    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.
  17. A brief history of FW/1 Services • Pre-2.0: Implicit service

    calls public string function default (required string name) { return UCase(arguments.name); } services/main.cfc public void function afterDefault (required struct rc) { rc.data = "Hello " & rc.data; } controllers/main.cfc Sunday, 21 July 13
  18. A brief history of FW/1 Services • 2.0: Explicit service

    calls - replicate old behaviour: public void function main (required struct rc) { service("default.main", "data"); } public void function afterMain (required struct rc) { rc.data = "Hello " & rc.data; } controllers/default.cfc Sunday, 21 July 13
  19. A brief history of FW/1 Services • 2.0: Explicit service

    calls - better: public void function default (required struct rc) { rc.data = getConversionService().toUpperCase(rc.name); } controllers/main.cfc Sunday, 21 July 13
  20. Dependency Injection default controller person service Sunday, 21 July 13

    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.
  21. Dependency Injection component output="false" accessors="true" { property name="personService"; public any

    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.
  22. Dependency Injection default controller person service person DAO Sunday, 21

    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.
  23. Dependency Injection component output="false" accessors="true" { property name="personService"; public any

    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?
  24. Dependency Injection component output="false" accessors="true" { property name="personService"; public void

    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.
  25. Bean Factory • any getBean (string bean) • boolean containsBean

    (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).
  26. FW/1 Bean Factory public void function setupApplication () { var

    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.
  27. Dependency Injection component output="false" accessors="true" { property name="personService"; public void

    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.
  28. DI/1 and Coldspring DEMO Sunday, 21 July 13 Three demos

    - DI/1, Coldspring, and combined demo about switching implementations.
  29. FW/1 URLs • /index.cfm?action=section.item&id=1 • /index.cfm/section/item?id=1 • /index.cfm/section/item/id/1 Sunday, 21

    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
  30. buildURL buildUrl(action="people.show") => /index.cfm?action=people.show (generateSES = false) buildUrl(action="people.show") => /index.cfm/people/show

    (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.
  31. /index.cfm/people/1 Sunday, 21 July 13 But what if you would

    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.
  32. Routes variables.framework.routes = [ {"/people/:id" = "/people/show/id/:id"} ]; Application.cfc Sunday,

    21 July 13 This route will match the URL on the slide before and actually use the action people.show with an id parameter.
  33. Routes variables.framework.routes = { {"$GET/person/:id" = "/people/show/id/:id"}, {"$POST/person/:id" = "/people/update/id/:id"},

    {"/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.
  34. Regex Routes variables.framework.routes = [ {"/people/(\d+)/$" = "/people/show/id/\1"}, {"/people/(\d+)(\.(\w+))?/$" =

    "/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.
  35. ReST Routes variables.framework.routes = [ { "$GET/people$" = "/people/index", "$GET/people/new"

    = "/people/new", "$POST/people" = "/people/create", "$GET/people/(\d+)(\.(\w+))?/$" = "/people/show/id/\1/contentType/\3", "$GET/people/(\d+)/contacts(\.(\w+))?/$" = "/people/showContacts/person_id/\1/contentType/\3", "$PUT/people/:id" = "/people/update", "$DELETE/people/:id" = "/people/destroy" } ] Application.cfc Sunday, 21 July 13 With these regex routes we can pretty easily actually create a REsT resource from a FW/1 controller
  36. Sub-systems Sunday, 21 July 13 You might want to break

    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.
  37. Sub-systems variables.framework.usingSubsystems = true Application.cfc Sunday, 21 July 13 To

    turn on sub-systems in your application you need to add a config option variables.framework.usingSubsystems = true
  38. main.default Section Item Sub-system Actions home: Sub-system Sunday, 21 July

    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
  39. Sub-system Directories common/layouts/default.cfm home/controllers/... home/views/... home/layouts/... other/controllers/... other/views/... other/layouts/... Sunday,

    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.
  40. Sub-systems and DI public void function setupSubsystem (required string subsystem)

    { 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.
  41. Testing FW/1 Applications • mxunit • test controllers and views

    • 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
  42. Agenda • Quick FW/1 refresher • FW/1 and Dependency Injection

    • FW/1 Routes • FW/1 Sub-systems • Testing FW/1 Applications Sunday, 21 July 13
  43. Thank you! • https://github.com/seancorfield/fw1 • FW/1 source, issues, pull requests

    • “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
  44. Thank you! • https://github.com/seancorfield/fw1 • FW/1 source, issues, pull requests

    • “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