OOPhoto - A Painful Transition To Object-Based Controllers
The latest OOPhoto application can be experienced here.
The OOPhoto code for this post can be seen here.
So this morning, I took OOPhoto, my latest attempt at learning object oriented programming (OOP) in ColdFusion, and converted it from a CFM-based front controller to a ColdFusion-Component-based front controller methodology. As I dicussed in my previous post, I did not think that this transition was going to be easy; as it turns out, that prediction was quite accurate. There was nothing easy about it even though I took many short cuts.
The first thing I had to do was package all of my variables into a single object so that I could pass it around to the Controllers without violating object coupling rules. I didn't know what to call this object. It held data about the application, it held data about the page we were going to render, it held data about the queries we were running; nothing specific like "event" or "request" really seemed to fit the bill. As such, I decided to go with something descriptive yet generic enough to cover everything that was contained: Data.
So now, in the OnRequestStart() event method of my Application.cfc, I create a REQUEST-scoped Data structure. I wasn't sure if I should be creating my variables in the REQUEST scope and then storing them into the Data struct afterwards. I figured that would duplicate my efforts, so now, all of my request-specific data is stored directly into the Data structure. And, it is a Structure. I didn't see any advantage to going with an object for this type of data transportation. Since the data can change freely as it passes through the work flow of the page request, I didn't feel that the encapsulation or validation provided by Get/Set methods would actually contribute any functionality (but would, instead, only add to the overhead of use).
Creating the Data structure was the easy part - that didn't take much more than moving around my variable declarations. It was the whole Controller stuff that got much more complicated very fast. Let's take a quick look at my primary front controller, the index.cfm page. If you remember from my last iteration, this page was just a CFSwitch statement that included other CFM files. Now, it is a CFSwitch statement that creates other objects:
<!---
Check the first event item to see which controller
we have to create. Once we have created the appropriate
Controller, we will pass control off to it.
--->
<cfswitch expression="#REQUEST.Data.Do[ 1 ]#">
<cfcase value="api">
<cfset strControllerPath = "content.api" />
</cfcase>
<cfcase value="error">
<cfset strControllerPath = "content.error" />
</cfcase>
<cfcase value="gallery">
<cfset strControllerPath = "content.gallery" />
</cfcase>
<cfcase value="home">
<cfset strControllerPath = "content.home" />
</cfcase>
<cfcase value="photo">
<cfset strControllerPath = "content.photo" />
</cfcase>
<cfdefaultcase>
<cfset strControllerPath = "content.home" />
</cfdefaultcase>
</cfswitch>
<!--- Create the controller and pass off the execution. --->
<cfset CreateObject( "component", "#strControllerPath#.Controller" ).Execute( REQUEST.Data ) />
<!--- Include display template controller. --->
<cfinclude template="extensions/templates/_index.cfm" />
As you can see, the CFSwitch statement no longer includes any files; now, it simply defines the base CFC path for the Controller to which we are going to hand off control.
I decided that all of my Controllers would have a single public method: Execute(). As far as I am concerned, that is all the calling page should know. After all, that's why we are "handing off" control. If the calling page knew what methods on the Controller should be executed, then why would we even bother handing off the control in the first place. Remember, divide and conquer - hand off the request to the controller and let it take care of the processing and implementation.
I also decided that the Execute() method should take a single argument - the Data structure that we prepared in the Application.cfc. This way, all we need to know about Controllers is that they uphold the "Controller Interface" and nothing more.
I didn't bother creating an Init() method for these ColdFusion components as I never really looked at them as objects; I know this isn't very "OOP" of me, but while they are taking care of the Control, they still seem very procedural to me. But anyway, let's take a look at a sample Controller:
<cfcomponent
output="false"
hint="I am a page controller.">
<cffunction
name="Execute"
access="public"
returntype="void"
output="false"
hint="I handle the page flow for this section.">
<!--- Define arguments. --->
<cfargument
name="Data"
type="struct"
required="true"
hint="I am the request data."
/>
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!--- Define global web attributes for this section. --->
<cfset ARGUMENTS.Data.Web.Template = "standard" />
<!--- Check to see what our next action should be. --->
<cfswitch expression="#ARGUMENTS.Data.Do[ 2 ]#">
<cfcase value="src">
<cfinclude template="_src_act.cfm" />
</cfcase>
<cfcase value="view">
<cfinclude template="_view_act.cfm" />
<cfinclude template="_view_dsp.cfm" />
</cfcase>
<cfdefaultcase>
<!--- No valid action was requested. --->
<cfthrow type="OOPhoto.InvalidAction" />
</cfdefaultcase>
</cfswitch>
<!--- Return out. --->
<cfreturn />
</cffunction>
</cfcomponent>
As you can see, I tried to keep this as simple as possible! Rather than having the Controller turn around and execute other methods within itself, I simply moved the original CFSwitch statements into the Execute() method and tweaked them to use the right variable references. While this might seem lame, it actually flexes the beauty of the Execute() method; my implementation right now is old-school, but I could change it to work in any way that I wanted and the world outside the Controller would never know the difference. Let us take a moment of silence to recognize the beauty of encapsulation.
Implementation aside, what do you notice about the Controller? What's different than the way it worked before (other than the obvious). Well, maybe even this is obvious to you, but when I thought about writing this code for the first time, I totally forgot about this little beauty:
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
The LOCAL scope! Oh man! Do you realize what this means? There is no more page variables scope! When I wrote that line for the first time in the Controller, it hit me like a ton of bricks. Suddenly, any variable that isn't scoped is considered "leaking". So let me get this straight - not only do I have to go back and change all of my existing scoped variables to point to my passed-in "Data" variables ARGUMENT, I also have to make sure that every unscoped variable that I used (think index loops, think CFImage objects, think intermediary variables) is essentially "var-ed" to make sure that those variables don't leak outside of their designated methods?
Bottom Line: Unless you VAR a variable directly, no code within a controller can have unscoped variables. Meaning: 99.9% of the variables in your application will exist inside of an explicit scope.
Something about this seems horrible to me.
But, maybe I am totally overreacting. After all, I am fan of scoping my variables in general. So why is this such a big deal? I think my gut reaction is stemming from the fact that in my coding standards, a scope variable has a very different use and meaning than an unscoped variable. As such, having all variables scoped going forward means that there is a fundamental shift in the meaning of my code. So really, I am probably reacting to that shift rather than to the code requirement itself. Also, I am sure that because I am using CFC-mixin type templates, I am sort of walking the edge between two worlds. Had my code been directly inside of a method, I am sure that it would have been psychologically less painful.
That's really all I did. The Controllers themselves were easy enough to write. The bulk of the time went into changing every single variable Declaration and Use within the entire application. I think that that's part of why this seemed like such a huge step. Had I written the code from a method-contained perspective to start with, I am sure that it would have seemed like a far easier task.
Object Oriented Reality Check
This step was no small effort. Even with a tiny application containing very little functionality, this shift in style took me about two hours to finish. So, now, let's step back and do our reality check.
Was This Step Worth Implementing?
This is a hard question to answer. To be honest, from a refactoring point of view, this felt completely worthless. It was a lot of effort and required me to fundamentally changed the way the code was written; but really, in the end, I still only moved my procedural code inside of methods inside of CFCs. This did create an encapsulation of variable usage, but for what end? In my application, that serves no purpose. Sure, I am now able to hand my "control" off to an object to do something for me - but, wasn't that what my CFM-based CFSwitch statements were doing anyway? I can't see how I gained anything from this step at all. In fact, I am not sure if I even want to keep it. Unless someone can give me a great reason to keep object-based controllers, I think I will roll back to my previous application before moving forward.
Is My Application Object Oriented?
Not even close. All I really did was move my procedural code into objects. You might try to argue that the Execute() method is our way of "Asking" an object to do something. And yes, that does maybe feel like OOP. But really, that's not doing anything more than including a separate CFM template the way we did in our original front-controller. Remember, the existence of objects has very little to do with Object Oriented Programming. Right now, all we have is objects, we don't have OOP.
Want to use code from this post? Check out the license.
Reader Comments
Ben, I would say that the Controller piece is by far the least important thing you can be focusing on. Do it however is easiest. The key here is to be looking at the Model, so get the Controller and View out of the way as quickly as you can.
Also keep in mind that almost no one actually builds their own CFC-based Controller layer by hand. The problems you're talking about regarding passing event and page context data around, etc., were solved by Fusebox, Model-Glue, Mach-II, and ColdBox a long time ago. So my advice here would be to just know "you can get that to work one way or the other" and keep moving ahead.
As Brian indicates, several well-established MVC frameworks have already solved this very hard problem for you. And of course, you now know how hard it is to get this right by hand - and you may have a sense of why most of the folks doing OO use a framework for this piece :)
FWIW, the view variable leakage issue is generally solved in the frameworks by have a "view context" that is instantiated per-request and it is that object into which the actual views are included so, whilst the un-var'd variables do technically "leak", it doesn't matter because the object has "request lifecycle" and goes away afterward.
In fact, because you are creating your controller CFCs on each request, you also don't have to worry about this because your controllers are not application-scope singletons (so most of your hard work was for naught really :)
Anyways, I think this blog post is educational because it shows how much work goes into the frameworks that hide this sort of complexity for us.
Fascinating series - keep it up!
Definitely gotta agree with Sean on this one: makes me appreciate that much more just how much work has gone into Model-Glue, auto-wiring all the event communication and view population under the Controller's sheets!
@Ben, I think it may be helpful to think of the Controllers as application singletons, as Sean mentioned ... not that you have to refactor to that, but just in terms of trying to discern the value of what you just worked through. If all those Controller CFCs already existed in the app scope, then your CFSWITCH would be determining which Controllers to Execute() rather than simply functioning as a pass-through for CFINCLUDE statements.
So, maybe instead of:
cfset strControllerPath = "content.home"
CreateObject( "component", "#strControllerPath#.Controller" ).Execute( REQUEST.Data )
You might see something like this:
cfset strControllerObj = "home"
viewResults = application.controllers[strControllerObj].Execute( REQUEST.Data )
In other words, I think part of the value you're looking for is in what Sean referred to as the "view context". To Brian's point, however, now is probably not the time to delve much more deeply into the hand-coding an entire framework thingy.
@All,
I have been thinking about this all night and something about a CFC-based controller system just doesn't sit right with me. I think I realized what it is:
Once you are inside of the CFC-based framework, you can no longer take advantage of the ColdFusion-based framework.
By that I mean, ColdFusion provides an amazing framework right out of the box. The Application.cfc allows users to hook into all parts of the page processing. The APPLICATION scope allows for global and persisting data. The SESSION scope allows for global, persisting, user-specific data. The REQUEST scope is global and non-persisting. All of this stuff was designed to allow ColdFusion to function as amazing framework.
I think part of where the complexity of frameworks comes from is that they were created before ColdFusion gave such high-touch work flow, perhaps? Before there was an on-session start method, yeah, it was important to find a way to allows users to hook into session-start events.
But now, I just feel like ColdFusion does so much for you, I wonder if (and please understand that I am just speculating here) the obvious need for a separate framework is as clear.
As an example, take a "plug-in" architecture. I have no idea how plug-ins work for other frameworks, so I am speaking only from I own experience. If I want to create a plug-in for one of my applications, I simply go to the Application.cfc and add it to the proper CF-framework event listener; perhaps its the OnRequestStart() method? Perhaps is the OnRequestEnd() method. Perhaps its even the OnSessionStart(). The ability to add plug-ins in my application is easy because ColdFusion as an engine provides a very easy-to-use framework.
But, I digress - where am I going with this? All this is just to say that once we step inside of a custom-CFC (Controller) to handle our framework, all the beauty of the ColdFusion framework is moot. Once inside a CFC, I can't refer to the APPLICATION scope. I can't refer to the SESSION scope. Heck, I can't even refer to the REQUEST scope. Assuming you are following CFC-best practices, so much of the beauty that is the inherent ColdFusion framework is completely canceled by the fact that a CFC cannot / should not know about its calling environment.
I am not saying that this is wrong or right - I am just saying that this has not been sitting well in my stomach, and the above is why I think I have that feeling
I know there are many other reasons to use frameworks, especially if you work in a large team of developers.... I just wanted to get that out on the table.
(and now, I'll address some of the comments)
@Brian,
In retrospect, this step in the development was actually not that bad. The biggest pain-point was having to convert the application to use different variable scopes; however, as I theorized near the end of my post, had I started out writing my application that way, it probably would have seemed much less painful.
But I agree - this felt like the least important step so far. I definitely want to just finish this and move on to the Model. I feel like that is where the real learning and growth will happen. If anything, this step was just a distraction.
I am pretty sure I will roll back to the old CFM-based controller as it did exactly the same thing but with less overhead.
@Sean,
The idea of a View State is very clever. I am curious as to how that would work. It seems like calculating the include paths for the Views would be very difficult as the CFInclude is relative to the compiled template, not the calling code. So, while a view include might be "_view.cfm" relative to the controller, it might be "../../content/circuit/_view.cfm" relative to the compiled View State object. And, this could change from Controller to Controller, so you can't bank on the paths being consistent.
Now you have me ultra curious :) I gotta see if I can wrap my head around that one! I love the concept, I need to know the implementation.
As far as the leaking, I see what you are saying. And, I suppose the view state would negate that. Of course, there are other non-view pieces of code that we have to be careful about. But, can there never be a case where even a non-persisting object can create non-thread safe environments. What if the Controller turned around and something like (pseudo code):
if (action EQ "something"){
. . . . ActionOne();
. . . . ActionTwo();
}
Since the Controller is calling its own methods two times in a row, I could maybe imagine a case where a Do-While loop messes up do an unscoped "intIndex" or "blnContinue" variable.... ok, maybe I am reaching at straws.
I am willing to not worry about it, so long as we agree that this does violate a CFC best practice, but is a violation whose consequence does not nearly outweigh the benefits of its use.
@JFish,
I don't think that whether the Controller is cached or created on every page request makes much of a functional difference. At least not in my code. When it comes down to it, the way my controllers work, I either have:
1. Calling procedural code directly (via CFInclude).
2. Calling procedural code via a NEW CFC Controller.
3. Calling procedural code via a CACHED CFC Controller.
Either way, it's all procedural and makes no difference from a functional standpoint. The only real difference is that #2 and #3 create MUCH more work and MUCH longer variable names.
As such, I will probably roll back to #1 and continue on with my domain modeling.
While I agree with Brian and Sean that there are many benefits to community MVC frameworks, the simple task of getting a request to call a controller method isn't one of them. Steve Nelson gave a presentation at CF United in 2007 on "the CFC's are the framework". Unfortunately he made a few inaccurate statements which ended up in a highly entertaining shouting match, but one which missed the point that some of his core contentions were valid - even though there were flaws in the details of his implementation.
When I get a chance I'll post my latest CFC based controller as another implementation sample to show that this stage can be very easy indeed - even easier than you found it. But that still leaves the question "why bother with controller CFC's"?
There are a few answers to that. One biggie is DI. Just being able to call the service classes required without having to explicitly call a bean factory is nice. Also the ability to wrap things like AOP around your controller methods can be very powerful. I also find sometimes that a few methods within a controller will share a little bit of controller specific logic that I can break out into a private method, and I also find that because when you think of it, most controllers are solving similar problems (processing a form, requesting a list, requesting a detail, requesting a report, stepping through a workflow, etc) by having a base controller that implements some of that you can create very simple controller methods for common classes of functionality just by using parameterized calls to a base controller.
There is no question that the step you've taken so far in itself doesn't appear to provide an ROI, but I'd suggest strongly that you leave the OO controllers in for a while as I'm pretty confident that if you pull them out, down the line you're just gonna have to put them back in again. I'd personally just leave them there for now, implement the rest of your steps and then see whether they're starting to make more sense as you refector your code, but whatever you do it'll be interesting to see how it continues to develop!
Here's a *very* simple example of an OO controller:
http://www.pbell.com/index.cfm/2008/7/24/A-Simple-OO-Controller
Just a small comment. You may not want to do a cfinclude from within a function. It causes any local function variables to be automatically copied into the variables scope, basically the "instance" scope of the CFC. You can see all the problems this would create. That said, if you are using an include outside of a function, for instance to include an entire function, you do not have this problem.
regards,
larry
@Larry,
Actually, I've found using cfincludes within object methods to be quite a useful way of using a cfm template that still has access to method calls within the object. I've used this on quite a lot of projects and not run into any problems to date . . .
@Peter,
I see what you are saying about some of the advantages of a CFC-based controller. And yes, maybe those are good for a large application that has lots of business logic, perhaps; but for something this small, I am having trouble justifying the overhead. Even looking at your other post, between the DI and the all the methods - I can hardly follow along. On the flip side, I don't think anyone has trouble following along between two files and some CFSwitch statements.
I think the complexity-to-benefit ratio just doesn't make sense for me yet. Of course, as I go more OO, that might change. But again, I want to feel that pain before I try to alleviate it. Unfortunately, when learning, I think it is more effective to learn from your own mistakes that to learn from the mistakes of others.
I will roll back to standard includes, maybe get burned, and then learn my lesson then.
So I think for now, I will just move back to the domain model.
@Larry,
I am not sure that I understand what you are saying. Are you saying that all of my LOCAL-scoped variables would then suddently be available in both:
LOCAL.VarName
... and...
VARIABLES.VarName
??
I'm confused about something. I thought the model-view-controller pattern prescribed a *single* controller for an application. The details vary widely, but the controller basically enforces low coupling by translating all requests from the view into method calls in the model.
Lots of business objects and lots of views tied together by one controller. But a lot of folks in this thread are referring to "controllers" in the plural? Can someone explain what I'm missing?
Ben, you said two things that are actually quite different topics:
First:
"All this is just to say that once we step inside of a custom-CFC (Controller) to handle our framework, all the beauty of the ColdFusion framework is moot."
This actually isn't true. All of the major MVC frameworks have hooks into or extend Application.cfc to provide the ability to leverage things like onSessionStart(), onRequestEnd, etc.
And second:
"Once inside a CFC, I can't refer to the APPLICATION scope. I can't refer to the SESSION scope. Heck, I can't even refer to the REQUEST scope. Assuming you are following CFC-best practices, so much of the beauty that is the inherent ColdFusion framework is completely canceled by the fact that a CFC cannot / should not know about its calling environment."
You're right, and this is a good thing not a bad thing. It's something that, as you say, one must feel the pain of, but the loose coupling of components from the various CF scopes leads to far, far more flexible code. As well as code that is easier to test. There are vast numbers of books and web articles that describe the evil of "global data", and the CF scopes you refer to are global data. Once you let it get its tendrils into all aspects of your application, maintaining things becomes a nightmare.
@Ben, I think that makes perfect sense. The only problem with waiting to feel the pain is that the pains that OO apps help with tend to manifest as applications grow. For example, you're not going to see any benefits from an IBO (or business objects in general) if you're just displaying fields from the database. It's only when you have a bunch of complex calculations in your getters that it really starts to work. That said, I agree that if you add a pattern without having a sense of the pain it's designed to solve, you're never going to get a good feel for where to use/not use it. Looking forward to the next cut with a procedural controller and seeing what you choose to OO next!
@David, Controller is a layer within MVC - as are model and view. Doesn't mean you just have one view template and one model cfc. For any non-trivial application you're going to want to logically break down all of the actions your application can handle into groups - each of which would be its own controller - whether procedural or OO. Think of fuses and circuits in fusebox. So you might have a cart controller with addtoCart, viewCart removefromCart and similar actions. You'd probably have separate controllers for your productCatalog, newsletter, checkout and eventDisplay systems.
@Brian,
I had assumed that the MVC frameworks do take advantage of the application events in the Application.cfc. I guess my feeling is that that is like paying retail. Sure, I get the same product, but isn't it less expensive to just go to the manufacturer without paying the middle man?
But, you make a good point about the flexibility of a NOT referring to global scopes. Thanks for putting that in perspective.
@Ben,
Here's what you're doing:
# <cffunction
# name="Execute"
# access="public"
# returntype="void"
# output="false"
# hint="I handle the page flow for this section.">
#
# <!--- Check to see what our next action should be. --->
# <cfswitch expression="#ARGUMENTS.Data.Do[ 2 ]#">
#
# <cfcase value="src">
# <cfinclude template="_src_act.cfm" />
# </cfcase>
Any variables in _src_act.cfm even if they are var'd or within your local scope are copied into the variables scope and exposed to the entire cfc. This can have all sorts of unintended consequences.
However in your case, it may not be all that critical. My own approach would be to create a separate View CFC and call the appropriate functions instead of using different included files.
regards,
larry
but to answer your question, yes those variables would be exposed to the entire CFC.
@Larry,
Inside of my include files, I am scoping all my variables (unless I missed some) into my pseudo-scope, LOCAL. That should keep them all locally scoped to the function.
Was your concern that I was not scoping "local" variables? Or are you saying that my use of LOCAL was not effective?
@Ben,
To give a specific example to Brian's comment about separating your controllers from global scopes, there is also the benefit of encapsulation. I have used frameworks in the past where one of the app-level configs was whether to use Client scope for temp persistence or Session scope. By having a PersistenceFactory and just calling that during instantiation of the application (based on the config), the Controllers never had to know anything more than "get my user from the PersistenceFactory" and didn't have to know whether it would find that structure in Session or Client. This allowed us to re-use that part of the framework across different types of applications without re-inventing each time, and within an app we could change the storage type on the fly if necessary.
Again, not relevant to the problem you're solving with OOPhoto, but something else to consider :-)
@JFish,
That does sound good.
....I'm having second thoughts about rolling my app back. Maybe I should just clean this up a bit. I don't want to dwell on the Controller layer, but I like these arguments I have heard so far.
@Ben:
"I had assumed that the MVC frameworks do take advantage of the application events in the Application.cfc. I guess my feeling is that that is like paying retail. Sure, I get the same product, but isn't it less expensive to just go to the manufacturer without paying the middle man?"
It would be, if the middle man wasn't doing anything but giving you what you could get at retail. But the frameworks give you far more than what Application.cfc offers, as you are no doubt discovering by working through your own implementation of a Controller.
It would be like booking a hotel. Sure you can book with the hotel directly. But if the travel agent is throwing in airfare, rental car, tours, baggage handling, insurance, and meals for free, you'd be pretty crazy not to talk to the travel agent. ;-)
@Brian,
Hmmm, touche. Of course, William Shatner might have a thing or two to say about using travel agents (judo chop!) :)
Joking aside, I do understand what you are saying.
Totally honest? I was only able to absorb a very small percentage of this beautifully thought out hand crafted super insightful blog entry. What I can say is that it caused me to think outside of the daily sludge of doing things the same way over and over without ever questioning my methods or reasoning.
This is top notch stuff, being able to see a high-level developer like yourself go though this thought process is really priceless, i mean that. Thanks so much!
@Jyoseph,
I appreciate the positive feedback. If you have any suggestions on how I might make things more clear, please let me know. The upside and the downside here is that I am thinking as I am writing, so you get actual stream of consciousness which is good, but it can lack clarity and a polished feel. But, if you have some suggestions, I am always looking to improve.
No actually that's pretty much 100% my problem. You're doing a fantastic job. I'm somewhat new to Coldfusion and really programming in general. Most of the time the problem for me is that I don't understand a lot of the terminology/lingo from the start. Meaning the fundamental things you'd learn maybe in the first few weeks of class. So although I could build an app that would upload photos, create galleries, etc. I wouldn't have any idea how to describe it in programming terms to other programmers. Just all part of the learning process.
I should have been more clear. My lack of "absorption" is completely because OOP and some of the things you mention are so foreign to me. Not because of the way you describe them. Sorry! :)
@Jyoseph,
No worries. I hope I can help you out with further explanations.
@Sean,
I've been thinking about this "View Context" you mentioned. I am curious about how it works. I have tried Googling, but I'm not really sure what I am looking for. And then, just now, it occurred to me - it's not only the views that have "local" variables, its also the action files (_act.cfm in my application). Are the actions being execute in the View Context as well? Or just the view templates?
I don't want to dwell on the Service layer too much; but, I think I might leave in the CFC-based controllers before I move on and I'd like to clean them up a bit before returning to the Domain Model.
Could you point me in the right direction - maybe provide some links to View Controller information that I might find useful?
If you get a chance, thanks :)
@Ben,
Hopefully this isn't too obtuse ...
Not the fullest explanation, but Model-Glue's docs have some info (http://docs.model-glue.com/) about how it's used there. In its essentials:
> The switch statement (which is an XML file in M-G, but same function) not only determines which service methods to hit, but it also defines which files to add to the ViewCollection
> There can be more than one view file in a request, and each view file can get a 'name', so that one view can easily refer to another as each is loaded and rendered into the collection (you can have one file loaded as a header, for example, and then another view file includes that header within its own layout, allowing a form to be reused with different titles, for example)
> The view files can access anything that was placed into the Event during the request, so they can access the object results (queries, variables, etc) and so forth
In the case of M-G, the framework actually handles the whole thing, so that the views for a request are rendered and populated and then added to the ViewCollection, and then they're all executed in the order they were declared.
Example: dsp.header.cfm > dsp.body.cfm > dsp.layout.wrapper.cfm
And to answer your question, the View shouldn't be executing, only displaying. The Controller is what should be passing data into/out of the Model and back to the Views. So, your Views expect certain data, but they don't execute or trigger it. Since your Model (your 'act' files) isn't modelled yet, I'm not sure how to make the division which would enable a ViewContext approach, so maybe tackling the Model is the more logical next step.
@JFish,
Hmm, interesting. I don't quite follow, but I get a little bit more insight. When I say "Execute" I just mean that the view file is "used" in some fashion. The way I have it, the view files just store variables into my Data transport struct. The controller then includes a master template in which the view "content variables" are placed. So, the view files are actually just rendered into temporary buffers (variables via CFSaveContent) and then used in a master template).
I am not sure I understand the concept of a view file getting a name? It's just output. How can you (or why would you) refer to parts of one view from another view?
It seems that for each action / view file to "execute" (ie. be included) without variable leakage, I need to execute them each within their own context. Something like (within the Controller - pseudo code):
// Create a unique, encapsulated object.
var Sandbox = new Sandbox( RequestData );
// Set templates to render.
SandBox.AddTemplate( "something_act.cfm" );
SandBox.AddTemplate( "something_dsp.cfm" );
// Render the templates in their own context (this will update
// the passed-in RequestData transport object as needed.
SandBox.Execute();
This way, any unscoped variables used in the Action or Display file will be leaked into their own "SandBox" object. And, since the RequestData is passed around by reference, the action files / view files can still update it and have that propagated to all code that has a reference to that struct.
Am I talking crazy?
No, I think you're on the right track. I think I was thrown off-track by the _act.cfm suffix, thinking that was actual biz logic or DB work, but now I see it's simply the transfer point to populate your data pass-through structure, which totally makes sense.
In fact, looks like you are already doing something similar to M-G here: by referring to a specific view file by 'name', all I mean is essentially what you're doing with CFSaveContent variable="name". The end result is that you can then use the rendered HTML anywhere down the line you want to; exact same thing M-G is adding to the ViewCollection, where the variable="name" simply becomes the structure key (e.g., ViewCollection.getView('name') or whatever).
Sounds like we're pretty much talking about the same thing.
@JFish,
Ah, I think I see. It looks like this "ViewCollection" is similar to my struct:
REQUEST.Data.Web.Content
Which would have things like:
REQUEST.Data.Web.Content.Main
REQUEST.Data.Web.Content.Secondary
REQUEST.Data.Web.Content.RelatedItems
...etc.
I have kept everything very simple - all structs for data transportation. No actual objects. Seems light weight and straightforward to me.
Sounds to me like a great transitional Procedural-to-OOP step to me. Love watching this thing unfold!
@JFish,
Cool. Thanks for the positive feedback. I don't want to spend too much time on this, so I'll what I can maybe whip up over the weekend to put this step to rest.
Hey Ben,
I'm not the expert on OOP, but I have to wonder something. I know that you are really into Interface-Driven Architecture, but do you think that maybe that maybe takes away from OOP a little bit? I have worked on projects that worked both ways, both Interface-Driven Architecture and coded and then Interfaced. Maybe I am misunderstanding Interface-Driven Architecture.
I have had more success with coding first. The reason is that when you code first, you have no idea exactly HOW the program is going to output, just that it does. This forces you to focus not on the specific tasks, but the goal. So I need to think not in specific, but more general terms, which then creates objects.
It seems like nobody can really agree on the purpose or the architecture of OOP, but one thing that is generally agreed on is the fact that OOP provides opportunity for code re-use. When you are forced to think of things in more of general possibilities rather than specific requirements, then you are forced to write code that can be re-used, often very Object Oriented.
Maybe I am wrong about it all. But it works for me.
Personally, I don't think "interface first" (with a small "i" - not Interface first as in CFInterface) takes away from OOP at all. If you have a web application where most of the complexity is in the interactions, starting with the UI makes a bunch of sense. If the goal is to fulfill a contract with users in terms of what input to take and what output to display, starting with that and using it to drive the object model works well. If you start inside the business model you have the risk of building functionality that won't be required by the UI.
The (partial) exception to this is as your domain gets richer. For a rich domain you might want to go through a modeling process to come up with what Eric Evans calls a Ubiquitous Language for the domain, and that may even inform/drive the UI choices you make. But even they you have to be careful to bound your modeling - after all, the goal of an application isn't to fully model the real world. The goal of an application is to provide the right outputs for a given set of inputs. Losing site of that can lead to writing a lot of code that isn't required to solve the immediate business problem.
A simple example of this is where people are looking at object relationships. Let's say a person has-many addresses and they're composed (if I'm deleted from the system, so are my addresses and two people can't share an address - even if the values of the properties of their address are identical). You could argue that an address has-one user, but the question isn't whether an addres has-one user, but whether you will ever need to get a user from a given address. If not, it doesn't matter whether the underlying model could be bi-directional - you don't need to code Address.getUser().
As for the purpose of OOP, I think it's pretty clear. The goals were both improved re-use and maintainability, and as I mentioned some time back (http://www.pbell.com/index.cfm/2006/6/26/Maintenance-and-Reuse-One-Out-of-Two-Aint-Bad) the real success story of OOP is making complex applications easier to maintain - primarily by minimizing global scope and encapsulating implementations and data behind interfaces.
@Peter,
Nicely stated.
@Brandon,
Remember, the primary goal of all applications is to please the client. If you are not pleasing the client, then really, nothing else matters. The goal of the interface is not really to document the functionality to the developer (which is does a decent job at) - the real job of the interface is to make sure that the client and the developers are on the same page as to what the application is going to do (and to come to this agreement before any production code is written).
Ben if you're not working on a book I would seriously consider it if I were you.
I'd shell out $50 to have all of your blog entries structured (in chapter/topic), cleaned up and bound. Hell I might even just get my printer out. I KID!
Please write a book. K thanks.
@Ben
Ok. That makes sense. A lot of the work that I do is not so much for a specific client as it is for any number of clients. I tend to focus more on developing wide-use apps. This kind of forces me to write in a way that can handle any number of possibilities. If I don't write in a modular/objective pattern, then I end up with serious problems.
Since when are clients, designers and developers on the same page? You guys in NY must have some sort of magic going on.
@Brandon,
Ha ha ha :) It is an uphill battle. Even with IDA (interface-driven architecture), there is still miscommunication and the dreaded "Wouldn't it be nice."
I think having to think in a modular way is very good as well. When I started this project, I got into the mindset of having to make it work for both an HTML and FLEX front end. In this same way that you work with many clients, I felt that this mindset would help me think about working for many interfaces. So, in a way, I think we are both agreeing that thinking in objects in general has some serious benefits.
@Jyoseph,
Thank for you the kind words. I feel like I am only part of the way there. Still fighting the good fight, though :)
@Peter
I like what you said. That makes perfect sense. If you are running any queries, how are you passing the datasource around? Are you passing the variable as an argument, or are you using a global var? I guess that to use a global variable takes the reuse out of it.
Well, all of my business objects have a Model bean containing meta-information about them. Those in turn have an ApplicationConfig bean within them (all the dependencies being injected in using LightWire as constructor dependencies).
In the init() method of the BaseDAO it states:
<cfargument name="Model" required="true">
<cfscript>
variables.Model = Model;
variables.DataSource = Model.getDataSource();
return THIS;
</cfscript>
The model beans in turn call ApplicationConfig.getDataSource() to pass it back to the DAOs on instantiation, and the ApplicationConfig gets it by reading in an XML file on application load.
So, I enter the DSN in one place in one XML file and it is available to all of the DAO's so the app is DRY, but because I'm using constructor properties and clearly identifying the API for the beans I'm injecting in, while there are "global dependencies" (such as the DAO), I can mock or stub them out for testing and they're clearly identified so I know if I want to use my DAOs in another system (not a big design goal for me), that I'd have to pass in beans as part of constructing them that implemented the required interface (although I don't actually use cfinterface to document the interface).
Obviously different approaches would be optimized for different use cases, but that seems to be a pretty good balance for my set up.
>.<
So much code more, apparently a lot of overhead, other then what the BaseService.cfc has to offer, I'm still not able to see a benefit to all this OO. (Not talking about Just the controller)
I still don't get it, and I know hundreds more feel the same. Whatever.
@Brandon
Managing things like a DSN get a lot easier when using a DI framework like ColdSpring (I've just started with Transfer and ColdSpring for a project at work. There's a bit of a learning curve and some initial configuration to be done, but I'm already beginning to see the benefit).
@Ben,
I'm curious, after reading this whole discussion, have you decided if frameworks are worth the effort?
I myself have recently started looking into Model-Glue, trying to figure out if it makes any sense to learn and use it. To tell the truth, so far the answer is negative... I am not nearly as sophisticated as most of the writers here, I am but a simple consultant/slave, but from my simple point of view, all these frameworks miss one thing: the good old principle of KISS. I think they are created but the guys who are too smart for their own good, and they just try too hard to improve on the beauty of CF and JS. Now, with advent of JQuery, building a Javascript based controller is easier than ever, and I really don't see the point in making my live a living hell with these over-thought frameworks. I think the death of Fusebox says it all... Meanwhile, the corporate managers just now got the word, and apparently they like the idea of a "system" in a framework, even though they have no clue what this system does, so they keep writing it as a requirement in their job postings. What happened to creativity, and the elegance of design? I've looked at all the major frameworks, and none of them has that.
finally: I love your blog, by far the best out there, you inspired me to start using JQuery and CFC's. Thank you!
@will-i-am
I'd look at something much simple Framework/1 - http://fw1.riaforge.org/. I think you'll find it to be much easier to learn OO in a CF context.
From the site:
--
FW/1 - Framework One - leverages Application.cfc and some simple conventions to provide a 'full' MVC framework in a single file.
Intended to require near-zero configuration, FW/1 lets you build your application without worrying about a framework getting in your way.
Controllers, Services (the gateway to your application's model), Views and Layouts are all 'discovered' using straightforward conventions.
Your controller and service CFCs don't need to extend anything.
Your views and layouts don't need to know about objects and method calls (a simple 'request context' structure - rc - is available containing URL and form scope data as well as data setup and passed by the framework and controllers).
--
Another simple framework you may want to look at is CFWheels which is loosely based on the Ruby on Rails framework. See http://cfwheels.org/docs/1-1/chapter/frameworks-and-wheels
It also gives some very good reasons for using a framework.
hth,
larry
@larry,
I hear you, but my problem is not that I don't understand OO or "how" the frameworks work - it's that whenever I try one, it's like I'm trapped inside someone's vision of how things should be, and sometimes that vision is SO much more complicated than it needs to be, and it's restricting, and often doesn't make any sense. Now that we do everything with AJAX and calling CFC's remotely, why for example would I ever use the "views"? And all the "events" are now happening on the client, so the controller is now JS based. And I find that doing it that way is a lot easier and quicker, in fact learned this from Ben's tutorial.
Of course, I understand that guys that spent all this time writing all these frameworks are now stuck with and have to keep plugging them... But really, I think we should concentrate on how to make the application SIMPLER, not more complicated (fighting with temptation to seem smart and complicated). The complication should be in the perfection of user interaction and business logic, not in the technology. This is why I love ColdFusion - it makes things that don't have to be complicated easy. All these frameworks are trying to take it away from me. Truly great things should always look simple (to the user, which in case of the frameworks is a developer). Great example - JQuery.
but anyway, thanks, I appreciate the advice...
I highly recommend CFWheels.
Here comes the flames... but.. in my opinion, I am nearly 50% faster then anyone using any other framework.
It can give you implicit freedom, or express control.
<cfset user = model('user').findByID
or FindByEmail
or FindByEmailAndPassword... kinda awesome how you can throw just function names together to get a result.
In about 2 weeks I developed a site that might have taken me 5 with spaghetti code.
Take a look.
I'm loving all of the recent comments on this entry. I've recently started building in Rails and I absolutely love it as a framework. I couldn't touch any Coldfusion framework with a ten foot pole because they all seemed so bloated and confusing. Basically, for all the reasons will-i-am mentioned.
I am interested in CFWheels because someone likened it to the Rails framework. Is there a big learning curve? Some frameworks are harder to use than the language itself. :-/
@will-i-am, what blog entry specifically did you use to create controllers from jQuery? I'm really interested.
I haven't worked with RoR, but I will say that CFWheels is my absolute favorite CF Framework to the extent I am a very avid contributer on the google group.
But really? The simpler the application, the faster you can get it up. This framework does everything but code itself. That's not to say it doesn't do complex things well either. You can create a full blown blog such as this in an hour or less once you understand it.
<cfset blogpost = cfset post = model('posts').findByKey(key='1296');
<cfset comments = blogpost.comments()>
loop over comments, if empty do Ben's 'No one commented yet'..
Although I am jealous of Ben's Layout. It takes time to accumulate so many awesome photos that I am jealous. So as a bullet point: Just a blog, not as awesome as this in 1 hour.
But I love the ability to do a 4 table join, get the results and related function calls for 1 to Many in 1 single line of code.
I am currently working on two contributions to the framework... CFWheels can not join a table to itself because it doesn't alias the table in the join... i am working on a small change so that if it detects two of the same table, it'll alias them both.
I'd say it's a framework to contend with.
@jyoseph,
this one: www.bennadel.com/blog/1515-Ask-Ben-Building-An-AJAX-jQuery-And-ColdFusion-Powered-Application.htm - Ben is a freaking genius!
@Brad,
dude, you convinced me, I'll dig in tomorrow on my employer's time :)
@Brad You've convinced me also. Well done!
@will-i-am Thank you sir, going to check that out now. Ben is a freaking genius indeed! My goal in life is to make it into a photo with him in the header. I've been practicing my thumbs up pose!
@jyoseph and others,
I find it interesting that there is a push for a simple framework yet a Rails-like framework is what is being suggested. Rails - and cfWheels - include a lot of code, a lot of documentation and a lot of conventions and machinery.
Certainly, Rails - and cfWheels - get you up and running very quickly. They get you 80% of the way in 20% of the time based on conventions, code generation, generic methods and onMissingMethod() (method_missing in Ruby).
I've been saying for a long time that CFers need to draw inspiration from dynamic languages like Ruby :)
@Sean - More code behind the scenes maybe, but not more code written by me the developer. Is that what you mean?
For instance, I just ran "rake stats" on the app I've been building over the last couple months, there are a total of 776 lines of code. That is about half the size of a single .cfc on the same site built in Coldfusion. The sensible defaults in rails seemed sensible enough that no real learning had to take place, you just had to be aware of the defaults and know if you decided to follow them, development would be easier.
I'm definitely shying away from a Coldfusion vs RoR debate, as my loyalty lies with Coldfusion and is/was my first love. But in terms of frameworks even the most simple frameworks (such as your FW/1, which I looked at earlier) are a bit difficult to grasp. For me anyway.
Which brings me to a question, do you have a sample app for FW/1? I browsed the showcase and there are impressive examples there, but are there any that can be cloned so we can pick them apart? (or are the individual directories in the /examples/ all apps? I haven't cloned it yet, sorry if that was a dumb question)
@jyoseph,
That's kinda my point: you trade off the big, bloated, behind the scenes framework against the small, simple, transparent framework. It's all about trade offs :)
As for FW/1, yes, there's a skeleton app and half a dozen example apps that can be used as the basis for your own applications. But, frankly, I think it's easier to read the three pages of documentation and build an application from scratch :)
@Will-i-am,
First off, I really appreciate all the kind things you have been saying - I am truly flattered. But, getting to the framework conversation, I have yet to really adopt any framework. I will fully admit, however, that part of that is due to the fact that I still very much straddle the Procedural / Object-Oriented worlds. Until you have a truly OO-based business layer, I don't think many of the frameworks are all that accessible (I could just be making that up though).
As a person who still bows down often before the procedural gods, I find that my "framework" consist of a few nested CFSwitch statements for event routing. I do, however, try to heavily leverage the "native" framework that gets shipped with the language (ie. Application.cfc).
This does, occasionally, lead to duplication of my SQL; but, this happens much less often than you might think. And, if I were to wrap my SQL interaction into a CFC-based service layer, I am still not sure that this change would necessitate a framework.
It's quite possible that the applications that I build are simply not large enough to cause pain.
Although, ironically, when I do play with creating a CFC-based service layer, I *do* get pain from the fact that my SQL statements become way more bloated. In the procedural world, SQL statements can be tight as they are custom made for each action. When you more to a CFC-based service layer, however, I think there is a tendency to "Generalize" SQL statements so that they can be reused.
Of course, this pain is probably all mental as I am not sure the generalization of the SQL statements is a concern; and, if they afford greater re-use, then that's awesome.
One place that I trip up often is when I take a SQL statement that performs 3 joins, each of which requires an input value for validation. Essentially, I am validating three aspects in a single query. When I move to an OO-version of that, I get lost. Do I now have to make 3 different queries and then cross-compare? Or, do I keep the same query? And, if I keep the same query, can it every truly be re-used?
Arrrrg... head hurts, where's my Fresca :)
All said, I have heard nothing but excellent things about Sean's FW/1 framework. Everyone who touches it seems to come away with a good experience.
I wouldn't begin to hold an argument against RoR, because I haven't worked with it. I am well versed in PHP though, and PHP has extremely powerful frameworks... but none quite as elegant as Wheels.
And Wheels can be as simple as you want it, really. It implicitly reads table definitions and can handle nearly every CRUD operation without configuration. Especially when you follow what most would consider conventional naming schemes.
Take fusebox, you need XML files in order to begin. Take wheels, drag - drop - make a single view... make that path the default path... bang you got a one page site. No controller needed, no database needed.
Need a database? Fill out the config file and call tables like you had built an ORM for it. In php, most frameworks need to generate classes as you build the database. Not CFWheels.
Anyway, I am glad that CF is clawing back ground from php.. the last few years before weren't looking so hot. I'd say Railo has had a great part in this too.
@Brad,
Just a note that Fusebox 5.5 does not require XML so the single page site scenario you mention is possible (and it still allows fully procedural code - or a mix of OO and procedural).
FW/1 is also built so that a single page site is trivial and you can grow into a full application over time. FW/1 requires that your controller be CFCs but if you prefer a procedural style, each CFC can be just a glorified switch statement that includes .cfm pages (I don't recommend that approach - just saying it's possible).
I hear good things about cfWheels but I do worry about the zealotry surrounding it (much as there seems to be quite a bit of zealotry around ColdBox, such that any criticisms tend to draw a lot of flak). I suspect CFers would exhibit less zealotry if they had a broader experience to draw from, able to see more of the good and bad in a wider variety of approaches...
I personally don't like full stack frameworks (like cfWheels and, to a lesser extent, ColdBox). I prefer assembling applications from a set of micro-frameworks, picking exactly what I need, when I need it. Frameworks are, after all, all about personal preferences. There's no wrong or right framework.
Well,
I like OOP in terms of Cold Fusion, because we all repeat this in the 'my language is better then yours' conversations. Coldfusion compiles CFCs and to various extents (as noted on this blog as well) various methods contained within... meaning it should be pretty snappy in production... unless all your logic is in cfincludes.
Also, anyone in the community will stand by the point of 'do what makes sense.' I do no personally advocate bending yourself to fit some one else's style of coding. A while back, when I started coldFusion, my first interaction with another framework was based upon action=x&subaction=y and it was routed through two different indexes to do so. They called this fusebox 1. It has since evolved.
And it's no disrespect to Ben or Ben's coding style. We all come to this site because we respect his methods, otherwise we wouldn't be reading it. We're all people with our own culmination of knowledge and skill sets. I just happen to be in love and near (self appointed) evangelist for CFWheels because (in my opinion) it's the new kid on the block. It addresses everything I could ever need from a framework and it clicks with me.
If you write your own frameworks -- great. I did before as well... but every time I thought I did something new... it exists in another framework. I actually made one that is based off of functional OOP, like Drupal in PHP.
But yeah, there are clearly wrong choices in software architecture... but right is a very large grey area :]. Sorry, I just thought I would give my two cents on CFWheels in my previous posts. For any people who are 'still on the fence' but looking for a framework.
Just arrived here to chime in on Brad's love for CFWheels.
I've been using it a few weeks now and while I'm not great at it yet, it's super easy to get going!
This I might add is my first MVC experience, but it's so simple and elegant that I can't really think right now of needing anything else.
To anyone reading this, try it out!
http://www.cfwheels.org/