Now that I have created my domain model, it's time to take these ColdFusion objects and start integrating them into my procedural code. As a first integration step on my journey into learning object oriented programming in ColdFusion, I am basically taking my domain objects and replacing them into the code were the equivalent queries used to be. The translation, for the most part was pretty easy; there were some functions that I needed to add to the domain models as I went and there was some tweaking, of course, in other areas, but for the most part, it was a fairly basic code replacement.
To start off, I updated my Application.cfc's initialization (OnApplicationStart()) event method to create and cache an instance of the ServiceFactory.cfc. Since the ServiceFactory.cfc creates and stores all of the service objects in the application, this will easily allow me to ensure that only singletons of my service objects exist. This is not a mission critical concept, as there is no data held within the service objects that necessitates being a singleton, but on the other side of that coin, as they have no instance data, there is no need to ever create more than one copy of each of them. Once the ServiceFactory.cfc was instantiated and stored, it would be referred to globally via APPLICATION.ServiceFactory.
Once the Service Factory was in place, I could then continue to replace all of my queries and update / insert logic with object creation and Service object usage. As I was doing this, I came to realize that all of my Services had a flaw (potentially) in their Read() method. The Read() method took a record ID and then populated a domain object instance. The Read() method gathered the record data by turning around and calling its own Search() method and passing in the ID that was passed to it. The problem was that the Search() methods were doing conditional searches based on the ID that was passed in; meaning, it only included the ID into the search logic IF the ID was non-zero. Part of my search logic is that I would never have to search for a zero ID because it wouldn't make any sense based on the database structure and its one-based auto-incrementing values. So, what was happening was that when I called Read( ID = 0 ), I was getting instance data based on the first record in the database since the Search() method was ignoring the zero-ID and was returning all records in the given table.
To solve this problem, I added some logic to my Read() methods that checks the passed-in ID; if the ID is zero, I simply return the results of a GetNewInstance() call; if the ID is non-zero, I proceed with the regular Read() method. This doesn't sit too well with me. I feel like I am doing something wrong. However, I suppose that using the Search() method from within the Read() method is also a bit of a short-cut. Had I been using my own query call in the Read() method, this additional logic would not have been required. So, I guess, the Read() logic is just a bit of a tradeoff for the ability to leverage the Search() functionality.
Other than the minor tweaks, the substitution process was pretty straightforward. Some of the code became much shorter. I mean, you simply can't beat something like this for brevity when compared to full-on query:
<cfset REQUEST.Exercise = APPLICATION.ServiceFactory.GetService( "ExerciseService" ).Read( REQUEST.Attributes.id ) />
It looks better all on one line, but I am breaking it up here so you don't have to scroll. This is much shorter than the equivalent query, but at the same time, it's a really long single statement. I found that a lot of my statements became very long and a bit unwieldy. Take a look at this snippet of code where I am populating my JointAspect.cfc ColdFusion component:
<!--- Get the joint actions for this joint. This will be a list of three IDs for Action, Plane, and Symmetry. NOTE: We know that these will all be numeric based on our RegularExpression CFParam. ---> <cfset REQUEST.JointAction = REQUEST.Attributes[ "joint_action_#REQUEST.JointID#" ] /> <!--- Set the properties for the various aspects. ---> <cfset REQUEST.JointAspect.SetJoint( APPLICATION.ServiceFactory.GetService( "JointService" ).Read( REQUEST.JointID ) ) /> <cfset REQUEST.JointAspect.SetJointAction( APPLICATION.ServiceFactory.GetService( "JointActionService" ).Read( ListGetAt( REQUEST.JointAction, 1 ) ) ) /> <cfset REQUEST.JointAspect.SetMovementPlane( APPLICATION.ServiceFactory.GetService( "MovementPlaneService" ).Read( ListGetAt( REQUEST.JointAction, 2 ) ) ) /> <cfset REQUEST.JointAspect.SetMovementSymmetry( APPLICATION.ServiceFactory.GetService( "MovementSymmetryService" ).Read( ListGetAt( REQUEST.JointAction, 3 ) ) ) />
Pretty freakin' wordy! I could alleviate a bit of this by creating short-hand references to the Service Objects rather than constantly going back to the Service Factory and its GetService() method, but this would only have condensed it a bit. In general, I found that many of my lines of code, while less in number, were much greater in length.
Also, little conveniences were taken away. For instance I used to be able to just call ValueList() on the joint_id column of my join query, but now that that query has been replaced with an array of JointAspect.cfc object instances, I have to iterate over the array to build up the ID list. I guess, there's just a bunch of little tradeoffs to dealing with object oriented programming that balance out a lot of the great benefits that it provides.
Overall though, it was a smooth process. The next step is to figure out what to do with data validation. All of the data validation is currently living in the controller / view code; this needs to somehow be encapsulated so that we can use the same logic in multiple places without having to duplicate our efforts... but that is another post altogether.
Want to use code from this post? Check out the license.
Are you using CFCExplorer for the Kinky File Explorer?
No, just an AJAX call and PRE tag to display the content. I hope to put in some color coding at some point. Just been short on time.
Thanks for putting this stuff out there for the public to go through. It's handy to see someone else's thought processes when trying to learn.
I did some quick searches here but didn't see it. Maybe I missed but if not I think it would be good for some folks if you touched on why you were using the request scope instead of variables or something else similar. Grax!
No problem. I am still learning all of this OOP stuff, so this is just my experimentation.