OOPhoto - Thoughts On The Massive Shift To Object-Based Controllers
The latest OOPhoto application can be experienced here.
The OOPhoto code for this post can be seen here.
As a next step in my OOPhoto application, Peter Bell and I thought it would be a good next step to convert from a standard CFM controller scheme to CFC-based controllers in which each controller is represented by a ColdFusion component (CFC) with some sort of execution function (ex. Execute(), Call(), etc.). On first thought, this seems like a relatively small shift; just like creating the Service Objects, as a first step we can simply move our procedural code into ColdFusion components, right?
Wrong! Moving our page-flow from ColdFusion pages to ColdFusion components is actually a massive shift in functionality and best practices. I say "best practices" because there are liberties we could take to make this step easier, but those would violate some of the most fundamental rules of object usage. Specifically, I am talking about the idea of low-coupling. When it comes to object usage, we want the object to have low coupling to the environment in which it is used. This makes it more portable (not an issue in our case) and much more resistant to change in the application (that's what we want).
In order to ensure that this low coupling is achieved, an object should not assume anything about the way it is being used; an object should know what is going on inside of it and nothing more. The concept is simple, but when it comes to object-based controllers, things get very sticky very fast. If you look at what our controllers are doing you will see references to the following objects (off the top of my head):
This is all well and fine when you are dealing with CFM pages, but when you have the above wrapped in a ColdFusion component, suddenly each reference above represents a high degree of coupling to the application environment; each reference to APPLICATION, REQUEST, CGI, FORM, URL, etc. requires the Controller object to know about the world greater than itself. This is a huge no-no and one of the most often-seen violations of object usage.
Could we leave the code as-is? Yes. All of the above scopes are globally accessible scopes and can be referenced inside of any ColdFusion component. But the point here is not just to get it to work (the existing code already does that!), the point it to get it to work better.
So how do we proceed? We need to come up with a way to pass these values into the Controllers such that we can lower the coupling. But at the same time, we don't want to have to explicitly pass in every object - this would mean that every time we needed a new variable, we would need to update the signature of our execution methods. The easiest way to accomplish this is wrap all of the above data into a single object and pass that object through to the controllers. I think, although I could be mistaken, that this pattern is often seen in other MVC frameworks as the Event object or the View state or PageRequest or whatever other "bag" is created to store variables.
Once we have our variables bag, we can easily pass through variable data with a low(er) amount of coupling; the object is still highly coupled to the structure of the variables "bag", but at least it is no longer coupled to the greater application. But this is only the first step of the transformation - now we have to worry about the rest of the code. Every place that references the REQUEST scope or the FORM scope or the URL scope (etc.) needs to be updated to refer to this variables bag or to objects explicitly pulled out of it.
As you can see, this seemingly simple step is actually a rather large undertaking. In an application of this size, I am almost 100% convinced that this will not be seen as a useful step after the fact. However, since I do see the benefits of it in larger applications and because I think other MVC frameworks use this methodology as well, I will implement it to get a taste of how it is done.
Great stuff Ben. I finally took the time to read through all of your posts and all of the comments in this series, and it's a fantastic resource. It took me a few hours, but was definitely worth it. I think your description and example of DI in the last article are probably the most accessible I've ever seen.
I'm looking forward to the rest of the series. Keep up the good work!
Thanks for the fantastic feedback. I am really excited to be learning this stuff, but it always makes it twice as nice to know that my explorations are helping other people as well.
I think this next step is gonna be a big one :) I'll keep you all updated.
You know . . . you really should write a book. You've got a lot of good, usable material right from this blog.
Just to confirm your suspicions, you are correct about other frameworks encapsulating other object references into a standard package to be passed into each controller instance. Model-Glue uses the Event object. Also as you deduced, this loosens coupling with the application but at the same time creates a new coupling to the framework. In other words, in a Model-Glue application, every method in the controllers expects an Event argument and it knows that information comes out of arguments.event and goes back into arguments.event. The controller absolutely reflects the framework, but in the process it needs to know nothing about the application ... how the Event got populated prior to the controller call is unimportant to the controller object.
All of which is just to say: I think you've boiled down the essence of where you're trying to go with the Controller now, and it seems like it's all moving in the right direction. It's very cool to see some of this stuff actually worked out piece by piece, rather than just seeing the finished product, like a Model-Glue or Mach ii framework. The 'why' of it all is coming much clearer to me in the process.
I have been reading through this series over the past few days and I must say this is fantastic work. The way you write your thoughts out is what makes it an extraordinary series of posts. Good work man!
Thanks for the encouragement. We'll see where this series ends up :)
Good to know that I am heading in the right direction. Now, I have to come up with a good name for my variables bag. This is actually harder than it seems because it will contain such a huge mix of data that will be used for so many different purposes.
Just finished my conversion to very basic CFC-based controllers. The code is not yet posted. Hopefully I can post at lunch. That was stressful :)
The updated code with CFC-based controllers have been posted:
I am exhausted.