OOPhoto - Thoughts On Integrating The New Domain Model
The latest OOPhoto application can be experienced here.
The latest OOPhoto code can be seen here.
This morning, I created the BaseModel.cfc for OOPhoto, my latest attempt at learning object oriented programming in ColdFusion. Then, just now at lunch, I created my three primary domain objects - the PhotoGallery.cfc, the Photo.cfc, and the Comment.cfc. That seemed pretty easy. But then, I went to start integrating my domain model into my service objects and I got very stuck. I have a lot of repeated queries in my service objects for the various methods; but, I shouldn't be repeating all my object "load" style functionality in each of those methods, should I?
At first, I started in the PhotoService.cfc and created a GetPhotoObjects() method. The idea here was that this method would take a query of photo data (already being done by each service method) and return an array of Photo.cfc objects. I thought that this would be a good way to keep things DRY. But then I remembered that each Photo object has a reference to its PhotoGallery object. A simple concept, but what happens if the record set I passed to GetPhotoObjects() have a common gallery (such as the query created in GetPhotosForGallery())? In that case, I don't want to create a new PhotoGallery object for each record! I would rather create one and then pass that reference into each Photo instance.
To make matters even more complicated, some photo data queries have a mixture of galleries. The GetRecentlyUploadedPhotos() method returns a whole mess of photos regardless of their photo gallery. In that case, I might have to create a new PhotoGallery.cfc instance for each photo data record.
This is really frustrating. Every step seems a bit harder than the previous one.
Maybe not all the service methods should return an array of objects. Afterall, let's not forget how awesome the ColdFusion query object is. But, if we go down that road, how do we decide which service methods should return queries and which should return arrays of objects? A tough question and one reason why I think that I should totally abandon the idea of returning queries while learning object oriented programming in ColdFusion. On one hand, they are lightweight and very powerful; but, on the other hand, I think they complicate my understanding of where to do what, when.
I think what I need to do is completely strip out the implementation of my Service objects and rebuild them. Right now, I am getting confused because there are a lot of queries floating around. If I rewrite the service objects while thinking about objects rather than queiries, perhaps it will make more sense.
Reader Comments
i feel the flaming coming, but i can take it: why not provide facilities for both query and array-of-object return in your service? You could:
1) have DAO functions that take a "mode" argument telling them whether to return a query or array of objects
2) or have a DAO function that takes a query and converts it to an array of objects
3) have Service methods like getPhotoResultsAsQuery or getPhotoResultsAsArray
4) those service methods would be extremely thin:
cffunction name=getPhotoResultsAsQuery
cfreturn photodao.getPhotoResults(args...,mode="query")
/cffunction
and your array returner in your service could just call the query returner from the service but perform the additional step of converting it to the array.
cffunction name=getPhotoResultsAsArray
cfreturn photodao.asArray(getPhotoResultsAsQuery())
/cffunction
or if that seems dumb, it could just call the dao function directly:
cffunction name=getPhotoResultsAsArray
cfreturn photodao.getPhotoResults(args...,mode="array")
/cffunction
This way, your html views call call whatever they want from the service. and if there's a flex app that calls your service functions, it gets the strongly typed return and has a choice of query or array of objects. Since your queries are encapsulated in a single place (the DAO), you're doing very little extra work to make all this happen. And the functionality for converting a query to an object (for the QueryToArray stuff) should be boilerplate enough that it would go into your BaseDAO object quite nicely... so you'd conceivably only write that function one time and from there on out, getting arrays of objects out of your queries is just a matter of calling a single extra function.
My former colleague mike rankin introduced this to me some time ago and it's been working very nicely.
good luck, Ben. Great series!
Ben,
You've reached the CF school of OO pragmatism. Sometimes a query should just be a query. This is a controversial view by some who believe OO is the only way to go, but ColdFusion is not a pure OO language, and there is a heavy tax in trying to make it act like an OO language. Array of object performance is a dog, at least in CF8, and it was worse in 6 and 7.
In general, if you aren't using set()'s on your output, you don't need an array of objects. If you need something more complex than a query, try an array of structures. If a straight query will do, use a query. I have found from experience that if you're doing lists (returning a large recordset), use a query. If you are manipulating a database, such as in CRUD, use objects.
Marc,
I was typing your comment while you were typing yours, but this is exactly how I build my service layers. Provide the getWidgetsAsQuery() and getWidgetsAsArray() methods.
one other thing... rereading it again and better appreciating your specific problem, i wonder if, for now, you should just "punt" on the whole "photos have a gallery... now what?" problem. i'm not saying that the relationship shouldn't exist. i'm saying that, at least for now, maybe don't solve that problem at all with your returns!
for example, if you have a screen with photos, you're probably only looping over those photos, showing the photo and then maybe providing links to them, right? and maybe a link to their gallery? Well, you probably only need like 2 or 3 bits of info from the gallery i'd say: an ID, maybe a name.
for your html page, with 50 pics a page, i'd say it's nuts to load up 50 photo objects AND 50 gallery objects. in the real world, it'd never scale. in this case, I personally in that html page would be calling like service.getRecentlyUploadedPhotos() or whatever and that would be a straight up query that joined on all the tables it needed to join on. it wouldn't be asking photoDAO to call junk from photogalleryDAO. i'd have directly in photodao a query that talked to every table it needed data from. So maybe that wouldn't directly port into the QueryToObjects call... . but i wouldn't care. because for my html page, it needs to be fast and it needs the data.
now, if you were to take that same concept to flex-ville, i think it applies in the same manner. it could call the exact same query. Or, if you were really anal about flex only getting objects, then it could get your array of photo objects, and your links would have the GalleryID because that would probably be a part of your photo object anyway (no danger in putting it there if it isn't already.... that tiny bit of duplication would go a long way). so if on your flex front end you wanted a "show all pictures from this gallery" on your "recently uploaded photos" screen, you'd have the galleryID there, without having incurred the additional overhead of creating 50 gallery objects just so you could pass back the ID. and with that gallery ID you'd have what you needed.
good stuff Ben. I look forward to hearing what others have to say on this.
one last thing and i'll quit hogging up the airwaves here. i was thinking to myself, "how would i do this in java?", and the answer is really simple: it would almost be a non-problem, in fact, because your photo relationships would be modeled very simply with hibernate.
so in cf9, when hibernate integration supposedly will happen, that means we're all gonna be wanting to just create models like this and magically fetch them all and it'll be glorious. But what if this object-creation performance hit still exists which, presumably, it will? creating 50 photo and 50 gallery objects in java is nuthin. but in CF, it's sumthin. Or, rather, it's a heck of a lot slower than just running the 15ms query you'd need to run to get the same data.
i imagine if the performance hit problem didn't exist, your problem would be a moo point (http://snurl.com/37aig)
Hey guys, thanks for the feedback. I guess my concern here is that if I try to decide when is better to use a query, I am gonna look at the application and feel that most of it is better as a queries :)
I like Brian's comment that unless I am using a Set() method, I might as well just use a query.
I need to step back and think about this a lot.
@Marc,
As far as Hibernate goes, I don't want to fall into the trap of solving problems that I don't understand yet. I need to figure out how to do this manually so that I have *some* idea what the heck is going on behind the scenes for something that becomes so core to an application.
What do you think about the "Gallery Detail Page" scenario. Here, I would be loading the gallery data and showing all of the photo thumbnails. In a situation like that, do you think it would be good to just load Gallery object that has an array of photo objects? Or should I still go with the query of photos and the query for the gallery?
I was also thinking about why you'd need a specific get() method.... sometimes you'd want to do like photo.getSomeCalculatedColumn(). and that's been an argument for why you'd want to return objects instead of queries.
In that case, at least old school, a lot of times you'd want that done on the DB side anyway and not in CF (stringing together names, doing simple math, etc). So you'd just do
select blah, firstname + ' ' + lastname as fullname
(or whatever your calculated column is)
in the query to begin with and thus mitigate the need for using the object... so you're still in query land.
Looking back on my recent apps that have moved toward OO, I find that I rarely need to return objects, particularly for simple "listing" pages. I try to keep those display pages as fast and "light" as possible so I can spend my time working on the hard stuff. cfquery/cfoutput/pound sign column name Done!
the other thing I was thinking about too was that if you really needed to pull back a ton of "extraneous" information, like all the gallery data, then that might mean the interface would get awfully crowded. so in that case, you could potentially solve that by going back to your design-first strategy... instead of pulling back all the gallery data for each picture, you just pull back the ID, and when you hover over the image for, say, 200 milliseconds, it makes an ajax call and pops up more info... sort of like a "tooltip" kind of thing but done on-demand.
i'm curious: what did hal say about this one?
marc
@Marc,
So do you basically use objects mainly for add/editing screens?
ugggg, this answer's gonna draw slings and arrows:
what do you *like* to do? Like, when you're cranking out pages and making interfaces, how do you like to do this kind of coding? what's enjoyable?
here's something i'm wondering: so you create a gallery object, and you fetch 50 photo objects. and what you've done is you've looped over your 50-row query and done a cfset obj.setSomeColumn() for 5 columns on 50 objects. and all that did was transfer data from a query into an array of objects which were in themselves just wrappers around structures anyway. so you've created 50 arrays of structs, essentially, albeit heavy structs.
50 arrays of structs sounds like a query to me.
I guess for me, I'm going to go back to how you started this, which is with "starting with the interface". I wonder: is there any behavior in your gallery pages that requires you to have an "object" for each of those photos? and if your interface doesn't require behavior, well.....
oh, and one more thought about your gallery detail screen. I wouldn't even see a problem here with running TWO queries... one for the gallery data and one for the photos data:
galleryDAO.getGalleryDetails(galleryID)
photoDAO.getPhotosForGallery(galleryID)
or whatever it is.
otherwise, you have those queries that are like <cfoutput group="galleryID" query="GalleryAndPhotos" maxrows="1"> ... show gallery data </cfoutput>
then down the screen....
<cfoutput query="GalleryandPhotos"> .... show the photos </cfoutput>
which some people find confusing, hving that top query like that instead of just a cfoutput over a "Gallery" query.
preference, i suppose. that feels more stylistic to me than anything i guess though.
@Marc,
Really, there is very little that I have seen in this application that feels like it requires an object. The application is so simple; as such, I guess using my "gut" to make these sorts of decisions is not really what I should be using. What I have to keep remembering is that the goal here is not just to get the application running well - it already does that quite nicely with procedural code.
I have to remember that the primary goal is to learn how to use objects.
So, maybe that is just what I have to remember. Keep moving forward with the objects even when my gut tells me that it is not necessary.
Or maybe, I should go the Iterating Business Object route which is the half-way point between queries and object. But then again, that can always come later.
That being said, I might just go for an all-OO solution. Then, when I am done with that, pull back and work more queries back into it.
ben, to your comment about add/edit screens. Yes, that's where I've been using them, mainly because it saves me from typing a bunch of cfqueryparams like i used to do back in the day when loading a query to drive an add/edit screen.
to be honest with you though, i still struggle with this concept of "bean as transport mechanism". So what you do is, you have a form, and you submit it, and you create a bean via setters, and then you pass the bean to some dao, and that dao saves it, and it calls getters.
i understand that it's clearer. and i understand that it encapsulates. and i understand that it provides the opportunity for "business logic" in the setters. and when that stuff is appropriate, that's cool. but for a lot of simple apps, it seems like it'd be complete overkill.
That said, I think that's why I like the kinds of services that frameworks like coldbox provide, where they'll populate your bean objects for you from the form struct so that it doesn't feel so whorish and dirty to be doing such things.
i guess basically this data transport object pattern, while i understand it, seems like a pattern that, applied blindly, just makes more work. I know some other commenters on the previous posts have voiced the same concerns, and I think it'd do them a disservice to dis them as blockheaded luddites who don't know sh*t about sh*t. it's a valid concern I think. I'm not advocating a return to the stoneage by any means. But I guess what I am saying is that it really sounds to me like your gut is telling you something, and I think we all know that you're a hell of a lot smarter than you give yourself credit for, and i think you should listen to your gut on this.
To me, views are just views. nothing special. so if it's queries and cfoutputs, fine. But when you submit that photo page, lots of stuff's going on. you're uploading the picture. you're generating thumbnails. you're checking file sizes and file types. you're potentially converting from tiff or png to jpeg. you're even potentially analyzing metadata for data tags and stuff. that's a lot! and that's the benefit of stuffing that initial few formfields into an object and not just a struct, because then you can say "photo.populate(FormStruct)", and then "photo.doYourBadassStuffDawg()" and it's all encapsulated, right there inside your photo object.
and at the end of that request, you're either gonna have a perfectly uploaded photo or a photo that failed your verification process. either way, it's gonna either redirect to your beautifully simple photo list page or your "you suck at uploading photos" page which will be a simple veiw with a call to show the error message you want to show. In either case, you did your OO duty... you encapsulated the "real" behavior of a photo into the processing of the upload/edit.
Again, great string of posts. I think you've got another CFEmmy wrapped up with this excellent series!
Ok noob question here. Can you put in laymens terms what the big advantage of OO would be in this particular coldfusion app?
Because I'm a noob I'll accept the fact that I may be short-sighted on this issue; But it seems like I could build this ten times faster by posting a form to an action page (do some validation), invoke a method / pass the form variables to the method in my cfc as arguments and return the result with returnvariable.
Same with getting. A photo id is requested, invoke the getPhoto method by passing the id as an argument and return the result.
This is all really cool but, can someone put in very simple terms what one would achieve by all of this. If it helps your app scale I'm in, if it's speed that's awesome, ease of updating code would be nice. But as is it looks like it adds a lot of complexity to what really should be a simple straight forward task.
I'm anxiously awaiting! Be nice now! ;-)
I think there's a perception that buying into the awesomeness of object-oriented programming implies a "death to queries!" attitude.
But the fact is, the query is a marvelous thing if you're retrieving and iterating over data. And if you still feel bad, remember that a query is itself an object. It's a recordset, just like the recordsets in .NET and Java. It even has (undocumented) methods like isLast() and next() thanks to the underlying Java class it extends.
It's a shame that the discussion is complicated by the fact that it takes sooooo long to instantiate a CFC. In Java, methods commonly return a collection of objects. No one talks about using a recordset to speed things up! Oh, well...
I think the gurus warn against queries in OO because of the obvious issues with tight coupling to a database. And thinking too much about the database makes it easy to lapse back into procedural programming. You get a lot more mileage out of OO when your objects express through their behavior and relationships the realities of the thing you're modelling. In OO, it's not just the nouns that are important, but the verbs!
Ben, I think part of your problem is that this app in particular involves a whole lot of moving data around, so your model still seems very procedural. I would take a good look at your photos and galleries in the abstract, and try to figure out what they actually *do*. What are the verbs in this story, and which nouns should be responsible for handling them?
If it's all about saving and displaying data, then finding the value in OO is going to be much harder!
@Joe, you granola-eatin hippie, if you think that way, the terrorists have won!
For real though, it's a completely relevant question. I think that's the great thing about Ben's public learning of all of this stuff... it's like it gives a forum for asking that question without fear of recrimination.
I think Dave's spot-on with his notion of thinking about the verbs. To his point about returning collections of objects in java as opposed to recordsets, I'd add that in java, that's pretty much how it has to be. If you've ever programmed in java and had to deal with ResultSet objects, they're a complete pain in the ass. And to get the same functionality with resultsets in java that you get with CF, you have to jump through a whole lot of hurdles. For example, in CF, you can reuse recordsets. You can get the record count. You can access a specific row, then some other row before or after it. You can iterate back and forth. You don't have to worry about closing the statement, resultset, and connection objects. In java, all of those things are much more difficult and prone to connection leaks (abandoned, unclosed connections). Returning a resultset from your model to your view, and iterating fore and aft, requires you to send params to the resultset (scrollable, etc) that make it a VERY slow object. I'm not talking 15 ms vs. 30 ms. I'm talking ms. versus seconds. The very nature of the java resultset leads to collections of objects. But in java that's completely natural because in java, collections of objects are the right way to do things. It's fast, it's easy... it's how you WANT to program in java. It *feels* right. Now, granted, queries in CF aren't updateable like resultsets in java are, but that's a different story, and it doesn't matter anyway because of the request/response nature of your typical web form-->action movement.
I remember way back, on an app, before I got into Hibernate, I got so frickin tired of resultsets that I tried to figure out how to actually use the CF query object, standalone, inside a java app. I was a n00b, desperate, not thinking of licensing and whatnot (never got it working anyway). But I just was so used to the ease of cf queries and I wanted that ease in java.
I still remember that lesson. I think that's in part why, for views, I favor the fast-and-easy approach of just looping over queries. For me, there's extremely little bang for the buck in converting stuff into arrays of objects just to loop over them in a list of things.
A lot of the bang, though, comes in the action processing. I continually reap benefits in that area. But here's the thing: a whole lot of this has been available in CF since the early days, largely via CFModule and even custom tags.
I work at a company that was fortunate enough to, in the early 2000s, employ a really, really smart guy. He built most of the infrastructure, taught the rest of the other smart people working there at the time how to program in a modular fashion, and a ton of that guy's code still lives in some form to this day. But that was back in Pre-MX days, so he used the best tool he had at the time: CFModule.
He encapsulated behavior like a mofo. In fact, we just converted a few of our most important modules into CFCs within the past year, and his code didn't undergo that much change at all when we ported it.
The biggest improvement is largely in the area of codifying parameters. With modules and custom tags, you have to look into the files to find out what attributes are expected/optional and such. With components, you have an API exposed. To me, that's gold.
The modules I'm thinking of are absolutely critical chunks of code in our system. They're "action" modules that do more than just process a single form submission. We use them to process form submissions. We use them in back-end processes that have no form submissions... the modules are just plain and simple smart. And when we moved them to CFCs, we retained that flexibility. When we need to save a certain critical chunk of data in our system (which can happen in a half dozen different places/ways), we just call that component's primary method and all works beautifully.
Now, contrast that to how it would've been had he not written it so modularly. Say he had that gigantic ass chunk of logic in some "act_SaveData" file that just looked at the form scope for its data and then redirected to some dsp page. that would've been 100% not reusable.
I think the real benefit of OO comes from this line of thinking: "you can call this here thing, from anywhere, pass in these expected other things, and it'll just work". You don't have to hunt down form field names. You don't have to worry about redirects. You just call it. He did it with CFModule. We do it with CFCs. Same thing *almost*.
I say almost because there's one thing doing it with cfmodule didn't afford us, and that's the kind of additional flexibility you get with components, i.e. the ability to really break down things into more granular functions.
His module worked for a long time, thankfully. But what about the case where, in that 500 line module, you have 10 lines that need to change because of a specific use case for some other client? What do you do then? Do you add client-specific crap directly into that module, and add another if statement? And what about Client 2? And what about when another 10-line chunk has slightly variant behavior for some other reason? What then? Do you require all client code to pass in a "ClientID" parameter? Do you look in the application scope?
That's when you get cruft and increasingly unmaintainable code. And that's the way it is when you're living in single-module world. But when you're doing it with components, you can then start to abstract stuff out into separate concerns with potentially no changes to the public API. Maybe in that module there's a really specific chunk of data that needs different behavior at the client level. or even at the user level, based on permissions or whatever. Sort of like, you need a different strategy, just for those few lines.
And that's right there, in my experience, is the power of OO. Now, you might say "just extend that main CFC and add client-specific functionality". And that's one way. But another way is to say "Hey, for this here chunk, and that there chunk, we need a way of doing things that is different. It's variant." And that there is the source of change, when we start digging into this module. It's a source of risk, because as soon as you open that file and start modifying stuff, you're introducing risk.
So you say "I need a strategy here". And then you start writing strategy objects. So our big monster super critical data saver object, which does all kinds of magic, says "hey, programmer, if you want variant behavior for this chunk, pass me a Strategy object that tells me how to deal with this weirdo chunk of data". And you have a "Situation1Strategy" that is just a little 10-line object that handles that variation. But for all other clients of that object, the world remains the same because it's using the DefaultStrategy object for that little punk bastard chunk that's been causing problems for some other client system. So for all the old stuff using it, nothing changes. And new "LookAtMeImSpecial" client just calls one additional line of code... obj.setPunkAssChunkStrategyObject( strategy1Object)
If we were in the way old way of doing things, where there's just a form submitting to an "act" file, we'd be totally screwed. Now we're in copy/paste land, and this is where maintenance headaches were born.
But with OO, it's simple. You abstract that variant behavior into a set of strategy objects, and for client code that needs different behavior, they write a small object that does exactly what they need to do, pass it in, and the monster object will use it to do what it needs to do for that one little piece. The risk of change is greatly mitigated for existing client code, and the new client code gets its custom behavior.
Taken in the context of MVC, this literally means that, for clientX that needs the special behavior, I just add that single line of code into the relevant "handler/controller" functions and all is well.
In coldbox, for Client1 through 5, I might just have an event called "SaveThisHereDataThingie" and it just does a createObject on my badass monster object and then calls save(). But on ClientX, it looks like:
cffunction name=SaveThisHereDataThingie
obj = createObject("MyBadassDAtaSaver)
obj.obj.setPunkAssChunkStrategyObject( strategy1Object)
obj.save()
/cffunction
And then you say "but what about the 6 other places where you use that Saver object? Don't you have to hunt all those down and add that same line of code? And isn't that going to be a source of bugs?"
The answer is, "you're damn right is is!"
And that's why coldspring and lightwire were born, so you can declare in a single place a MyBadassSaverObject. and in your clientX coldspring file, you put a setter injection on it that takes an instance of Client1Strategy. And everywhere in your app when you fetch that Saver object from the bean factory, or it's injected into your app or whatever, it's already configured! But that, for me anyway, is the kind of thing you don't fully appreciate until you go through that pain. In fact, in the app I'm currently working on, we're not starting with declarative bean factories at all, because you don't appreciate them until you live the pain of not having them. That pain is gold, in my opinion. When I was a teacher, I used to say "you have to earn your answers". This is one of those cases.
I think in Ben's photo app, you're likely to not see those kinds of conditions. But in more real world apps, at least in my experience in the corporate world where you have large codebases that try to service multiple client applications, you really see it. And I'm not talking about that old saw "but we have to change the database from mssql to oracle and we launch in 3 days" crap. I'm talking about the more realistic changes we see day to day.
I had a really neat experience a few months back when adding some functionality to the mxunit plugin for eclipse. I use apache axis to communicate with CF, and by default axis uses apache commons http to do the http communication. However, apache commons http doesn't support a particular type of authentication, and i needed to provide some support for that authentication type. Now, I'd have been up sh*t's creek if the axis designers hadn't been so forward thinking. Thankfully, they were. They abstracted http communication in such a way that you could provide alternative http connection objects, and those objects could perform the communication work. Turns out, the apache HttpClient project did exactly what i needed to do. So I simply had to add a few lines of code to say "hey, axis, use httpclient instead of your built in communications object". and i provided the httpclient object with the authentication details. And that was that. You can check out the mxunit plugin source from subversion to see what i'm talking about. Now, it took me a while to learn all that, naturally. But imagine how sweet it was, trying to knock that stuff out, once I finally learned that about 10 lines of code or so could do what I needed to do, just by swapping out http clients!
That lesson sticks with me all the time when programming CF. When things get hairy (i.e., i'm not saving simple forms), my "spidey sense" starts tingling... "what here seems brittle? what seems like there's a high likelihood that it'll need to change for some reason or another? do the requirements give me any whiffs of potential change down the road?" Or, as in the case of my boss, who's super smart and much more experienced in the realm of dealing with the people in our company who create the (often vague) requirements, "does my experience lead me to believe that this kind of thing is likely to change even though everyone says it's not going to?" It's never cut and dry, to be sure. And you can get into the trap of thinking too far ahead, too. But at a pragmatic level, I think you get to a point where you get a feel for things that will maybe change. And when you do, you start to realize the benefits of OO.
At least, that's been the case for me.
It's hard to think about how OO helps in the context of a reasonably simple app that has a low likelihood for change because you get stuck in "But i could crank it out in 5 minutes!" mode. That said, I had another neat experience a while back when trying to add some functionality into a personal app I wrote back in 2001. In a nutshell, the app has a small chunk of functionality for uploading pictures. Nothing fancy. Just a cffile upload and a cfquery or so in an action page and then some display code underneath it.
All in a single file. Upload. Insert. Display picture.
So a few months ago, after probably 4 years of not writing a line of code for this app (much used by my friends, just not updated b/c it didn't need it), i wanted to add the ability to send pictures to an email address via email and have the app pick up the emails and add the pictures in the same way that a user would do via the front end. Now, you can imagine how hard it was for me to do that with the current code.
I couldn't reuse any of it! It was completely procedural, etc.
I had a choice... rewrite the existing code to be encapsulated such that I could easily reuse the parts I wanted to reuse, or just copy/paste the stuff I needed into yet another procedural piece of crap.
When functionality isn't encapsulated, it's harder to reuse it. And back in 2001, I never thought I'd need to reuse a single line of code.
When I opened the code, I said to myself "you have got to be shitting me! I wrote this?". Then I copied and pasted, ftp'd, and reminded myself to do a better job next time.
hey, has anyone seen marc esher lately?
:)
@marc
Wow, dude that was awesome.
As my first coldfusion project, I built a simple cms for my personal site/blog, nothing fancy. It was fine when it was just for myself but now my wife has her site running the same codebase, oh and now we have a photography business that uses the same code base. The part I didn't mention is it's not the same code base, well the same base but very different. What happened is I created the new site from a copy of the old site and made customizations to meed the needs of that site. Problem is before you know each of the 3 sites are vastly different. So, as you can imaging, making security updates, adding features, cleaning and maintaining the code has turned from a fun little side project to a pain in the you know what.
If I understood correctly, having the perfect OO implementation would allow me to have a single more simplified code base to perform the basic core functions of the cms. Other features that would deviate from that core base would be done by calling an object that was written for that specific task?
Does anyone have any recommended reading for someone just getting into this? I mean sort of baseline stuff? ;-) Is cfOOP.org a good/bad place to start? Should I first really nail down exactly what an object is?
*takes bite of granola bar, weaves basket*
@Marc,
Holy cow :) Most excellent input.
I had the same concerns as Ben when I started to write more OO code in coldfusion.
What to do with the recordset?
At the time initiating objects in had a significant performance hit, so I didn't want to make arrays of objects, especially for large datasets, but I didn't want to expose my code to the field names and structure of my database by working directly with the query.
What I went with is based on a combination the iterator pattern, and the flywheel pattern.
Basic idea:
Create a container object for your query results, I call mine DTOs for data table objects.
I then wrote my DAO functions so that they only accept and return DTOs
Implementation
Create an abstractDTO
PUBLIC functions
moveToFirst(), moveToNext(), moveToPrevious(), moveToLast(), isLast(), isFirst() - move between records
moveToNew() - creates a new record
getCount() - number of records in the set
hasValues() - true/false there are values in the record set (useful for quick checks before looping etc.)
setID(value), getID(), moveToID(value) - all my tables have a id field to unquely number each record
setUUID(value), getUUID(), moveToUUID(value)- all my tables have a uuid field to unquely number each record
getIDs(), getUUIDs - returns a list of IDs/UUIDs of all records available in the set
setIsSaved(), getIsSaved() - have any changes/additions to the recordset been saved?
hasChanged() - the current record has been modified (true/false)
filterChanged() - new/update records
filterUnChanged() - unmodified records
filterNew() - only new records
filterExisting() - only previously saved records
filterClear() - turns off filters
PRIVATE functions
init(recordset)
getFieldValue(fieldname)
setFieldValue(fieldname,value)
getRecord() - returns the current record as a structure using with the key being the fieldname
sortBy(sort options) - applys sort options to query (taking filters in to account) and moves to first record
Create a DTO for each table, example userDTO
Inherit the abstractDTO.cfc (I use inheritance so I can make use of interfaces for typing, I will probably refactor to use composition for some functions)
PUBLIC functions
init(recordset) - calls SUPER.init(recordset)
setFirstName(value) - returns SUPER.setFieldValue("firstname",value)
getFirstName() - returns SUPER.getFieldValue("firstname")
Etc for the remaining fields
sortBy...() - calls SUPER.sortBy()
Create an abstractDAO,
PUBLIC functions
save(DTO) - accepts any component of type="interfaceDTO", or component, depending if you use interfaces etc. Iterates through the DTO, determining if the record is new - calling add(getRecord()) or changed - calling update(getRecord()). Returns the DTO. The function DTO.getIsSaved() will return true if the recordset was saved.
selectByID(IDs) - returns a DTO containing the indicated records
create() - returns an empty DTO
delete(IDs) - deletes the specified records, used IDs
exists(DTO) - compares all the records in the DTO to the database, getIsSaved() returns false
PRIVATE functions
Init(serviceObject,"DTOname") - service: contains factory functions, DTOname: the name of the DTO to use with this DAO (passed to the factory function whenever I need a new DTO)
getDSN() - calls the sericeObject to get the DSN for the database
Create a DAO for each table, example userDAO
PUBLIC functions
init(serviceObject) - calls SUPER.init and returns an instance of this object
Add(record) - returns true/false
Update(record) - returns true/false
Any other getBy functions I want to add
My approach does not exactly fit the GoF patterns, but I've always taken the approach that patterns were created to facilitate the "communication" of design ideas and concepts, so a group of people discussing a design could use the same words for the same concepts. I didn't get the impression that they were ever intended to be definitive design principles.
I'm hoping the above makes some sense. Someday I'll get my design docs and updated site finished and online.
Hey Ben,
Perhaps no surprises, but I'm gonna advocate the IBO. Why? Because it IS (for me) the OO solution for CF. In CF you either need to use IBO's or you have to do some kind of half assed pre analysis of "yeah, I think this needs to be an object, but no, I don't think that list of objects will ever need to have calculated properties or methods for getting associated objects, so I'll use a query to solve the underlying problem that object creation is a dog in CF.
With an IBO (or any similar approach designed to give you objects without the cost of one object instantiation per record in a query), you can just always pass objects so if you want to Gallery.getPhotos(), you just need to drop in a getPhotos() method into Gallery that calls the PhotoService.returnPhotosforGallery() method which returns an IBO.
Other stuff I've found that works for me:
DAO always returns a query, service class always wraps that in an IBO
so UserService.getAllUsers() is
User = new(); // Calls userservice.new() - a method that wraps BeanFactory.getBean("User"); and any other lagic for setting defaults, etc.
User.load ( DAO.getAll() );
Simple, elegant, consistent and powerful.
The problem with arrays of objects is you're always going to be trading off performance for consistency of approach and your refactors when you find one of your queries does need to be an object are going to be more of a PITA - especially if the query is used in ten different screens. In my experience, whether a query is simple or may need more complex methods (i.e. to be wrapped in an object) is something that often varies, so you want to encapsulate that. I do so using an IBO, but anything that allows you to consistently use objects without having to worry about object creation performance would do.
However, the great thing about this is that it is a learning app. Do something - anything. Consistently use queries and then note the problems you run into when you add calculated properties and trying to figure out where to put the logic for getting associated objects. Try a pure array of objects for everything and see how that works out. Play with different IBO implementations to see if you can come up with something you like. Try different approaches that come to you in a moment of inspiration, start to get a feel for each one and how they work and then you'll be more comfortable in chosing an appropriate approach for a given problem down the line.
When it comes down to it, learning OO is like martial arts or doing weight training. It helps to understand the principles and know what you're doing, but nothing beats time actually "doing", and once you know what you're doing you'll find that a lot of the rules are actually guidelines and will have a better sense (through trial and error) of the implications of breaking the rules in specific cases and when that makes sense.
@Marc, Hey buddy - if you don't have enough work to do, maybe I should post some enhancement requeste for MX Unit :-)
Definitely agree with Marc about code re-use and encapulation.
For me though the biggest benifit is Unit Tests.
I don't code in my day job anymore, so weeks can go by where I don't have a chance to even look at code. With objects I know that once I have an object working, it will keep working, and I don't have to worry about it.
By using unit and intergation tests I can quickly test a set of objects to make sure they are each working the way they should, and if I make any changes I can quickly identify what is breaking.
I find I use the tests a lot to help refesh my memory on where I left of in my code.
Ben, one of the best techniques I've found for getting my head wrapped around objects was to use test driven devlopment. If I can't figure out how to write a unit test for the object, I probably haven't designed it right.
Peter, I was going to bring up the IBO approach but i didn't think it would work with a flex front end, and if ben's trying to keep this open to use with flex, i wasn't sure how that'd fit in.
do IBOs work with flex?
@Marc, Sure they do - with a fairly simple tweak. Pass the IBO to your remote proxy cfc which acts as the controller for your flex requests. Add a method that iterates over the properties for each record and returns a collection of structs using the __type__ notation so that passing to back to Flex will give you typed objects. I haven't needed to write it yet, but the imple implementation would just be the remote proxy method saying something like:
return ProductService.getAll().forFlex();
so unpacking that, we have:
var ProductIBO = ProductService.getAll();
return ProductIBO.forFlex()
I don't love the naming, I would also accept the argument that the forFlex() method being within the IBO is a violation of the Single Responsibility Principle (although it wouldn't be a bad starting point).
If you wanted to be a little cleverer you'd have a decorator for the forFlex() method so you'd do something like:
var ProductIBO = ProductService.getAll();
var ProductFlexDecorator = BeanFactory.getBean("ProductFlexDecorator");
ProductFlexDecorator.loadIBO( ProductIBO );
return ProductFlexDecorator.forFlex();
I don't love the naming, but I think you get the idea?
@Peter,
I really do like the IBO strategy. Here's one thing that I can't quite follow: how does an IBO pattern interact with the idea of composed objects. If I can do something like this:
objPhoto.GetGallery().GetName()
... to get the name of the containing gallery, what takes care of setting the gallery object to begin with.
The only thing I can think of is that if you call the GetGallery() method, the IBO, internally, makes a call to the GalleryService.GetGalleryByID() and passes in the gallery_id of the current photo record.
But, that implied that I am storing the "gallery_id" with the photo, which is not correct as that is a data-centric view of the world. Really, my photo doesn't have a "gallery_id", but rather a gallery reference????
Can you give my some direction of the use of composed objects and IBOs?
Or, am I thinking too hard?
And just to get this out on the table, someone might ask, "Do you ever need to know the gallery details when looking at a photo"? Maybe. Maybe, when I output a list of recent photos I wan the ALT tag of the IMG to be in the form of "GALLERY Name > Photo File Name".
Thanks for your guidance.
Hi Ben
When it comes down to it, the implementation of your gallery reference is probably some kind of ID. Entity objects (as opposed to value objects where only the values matter) have an identity. If those entity objects are persisted to a relational database, somewhere a query will be used to return related records and that query will use whatever uniquely identifies the persistent identity of a given object - often a simple numeric ID field, although it could be a GUID or a real world identity such as a SSN (not usually a great idea).
Given that, my implementation is more or less what you described. Beans have access to the service classes of associated objects and make an appropriate call on those service methods to get the associated object(s) back which will return an IBO also.
So typically I'd call:
<cfset Gallery = objPhoto.GetGallery()>
Then #Gallery.GetName()# would be used in the display.
The getGallery() method on objPhoto would call GalleryService.getByID( getGalleryID() ), so it's pretty much as you described.
The other way round I'd call:
PhotoList = Gallery.getPhotos()
and then iterate over the PhotoList to display them.
I have found in practice that this has worked really well for the kind of apps I build. I guess the question I'd ask is what (if any) problems does this approach create in terms of maintainability? I haven't really come across any in my use case, and you've also got to bear in mind that once you get beyond a certain point you're probably gonna switch out your custom relational code for Transfer or Hibernate anyway, so I don't feel it's something you want to overthink too much.
Of course, my actual implementation uses a generic getAssociated() method which uses metadata stored in XML to describe the relationships between objects and to generate the appropriate joining SQL, but I don't think you need to start out there :-)
@Peter,
Thank for the explanation. However, here's the hitch:
GalleryService.getByID( getGalleryID() )
Maybe I am totally over thinking this, but my photo IBO shouldn't have a GetGalleryID() method. Doing that would imply that the photo "object" contains a "GalleryID".
In practicality, when the data is persisted, this is true. The database record for each Photo has a gallery_id column which is the foreign key to the gallery.
But! When we go back to Helmsian thinking and we ask "What does it mean to be an object.... What does it mean to be a photo"? Does it make sense to store a gallery ID in the photo? Storing referential IDs seems like it is missing the whole point of object oriented programming to begin with? That is why my Photo domain object stored a reference to the Gallery and no gallery_id directly within it. It seems that if we are storing the Gallery ID, we are really just wrapping our database implementation in objects.
So here is the question: if this is simply a trade-off, I am fine with that. If you can say that storing the Gallery ID is not "strictly" OO, but makes life much easier, then cool, I am down with that - I am not interested in making life harder for principles only. But, if is not strictly OO, I'd also like to know that as I am forming my understanding of the basics. I want to know which rules are being bent and which are being broken.
Hi Ben,
I think we're coming at this from two different perspectives and I think that is what is causing the disconnect. I have never had a non-functional requirement for a project that it be "strictly object oriented". I can't think of a business case for making an object oriented app (unless you're in the business of selling OO training maybe :-) ).
Rather I build apps that are designed to be maintainable. I have found that for a large class of non-trivial apps, using some of the ideas from object oriented programming allows me to make the app more maintainable and testable. As such I use those patterns - not to make the app OO, but to make the app more maintainable and testable. I would go as far to say that trying to make an app OO is not a good idea.
I would say that from a learning perspective it does make sense to implement patterns to solve maintainability problems you don't yet have, but I don't see any point in making an app "strictly OO" as OO'ness in itself isn't a benefit.
Given that, I don't see a trade off in something somewhere admitting that there is a GalleryID stored in the Photo table (or however the relational model works) and allowing that to work. I would say there are real benefits to hiding such implementation details by using an ORM like Hibernate, but if you're writing code to persist your objects, something, somewhere needs to know about the ID's.
@Peter,
Great answer. Thanks. You have given me lots to think about. I am trying to figure out my next step.
@Ben, I'm not a big fan of creating arrays of objects, I prefer to work with queries instead, I'm sure my opinion won't be shared by many. Let's be realistic, when we return records from a db, we don't return thousands of records to display on a single screen, we may return at the most 500 records. If you're handling pagination right and querying the db right, you should use the db to control that rather than returning an entire recordset and having CF display "x" number of records with cfoutput/cfloop query.
All that said, have you ever tried looping the query results returned and using querysetcell to modify the data? I did something similar to decrypt some sensitive user data to display on the screen to allow them to modify it. It was fast and the best thing was i had a query to loop for my page.
I do use objects, but for single records only, so i'm not against them. I just can't see why anyone would take a query and convert it over to an array and have all that overhead.
@Ben,
You asked if objects should have IDs. I personally dig the "domain-driven" school's answer: an ID should only be included in your model if it has a meaning in the domain you are modelling. Employees, accounts, and orders frequently have unique IDs in the real world, so modelling those IDs is not crazy talk.
@Peter,
You sure do love to plug your Iterating Business Object (IBO)! :) I am definitely going to download that from RIAForge and take it for a test drive on Monday!
@Hatim, Creating service class methods to modify recordsets is one possible way to go - along with putting more of the logic into the db and using views and/or calculated columns in the SQL to return the data you want.
In my experience, as apps get more complex, this becomes hard to maintain if you have a fairly large number of custom getters on the same object for two reasons. Firstly, if you're using objects for a single record, you're gonna have to repeat yourself in your UserService.mungemyData() method repeating the code in your getWhatever() method in the object. For example, in mungeMyData() you'd gonna have a routine that calculates the User.Age property based on the User.DoB column and then you're going to replicate the same business logic in your User.getAge() method. Secondly, as you have 5 or 8 or 10 of these transformations, it becomes pretty hard to both manage the code and to handle the dependencies (if getTotal() depends on getShipping() and getSubtotal() that is easy with objects and properties, making sure your munger in the service class generates the columns in the right order to preserve calculation dependencies can be a bit of a pain for larger apps.
Also, with something like an IBO (yeah, I'm sick of me saying it too), there is no substantial performance penalty, so why not?!
Of course, if the service class query transformation works for your use case, go for it, but I'd argue that putting the getter logic into the object itself will be more maintainable as complexity increases. Obviously not important if you don't have that level of complexity . . .
@David,
Just remember, the Riaforge project is just a sample implementation. A bunch of people have written their own implementations, so you might want to start with that code, but if you don't like it, change it until you do - dont' give up on the patterns if you don't happen to like the implementation of the iterator or the generic getters or whatever!
I'm a huge fan of the pattern. The exact implementation is definitely something you should "season to taste" :-)
@Peter,
Do you have any blog posts (yours or others) that you would recommend checking out to learn a little more about IBO? I feel relatively comfortable with OOP now, but I've never really had the formal training on it (a class here an there, but mostly just learning from other's code, some books, etc). Facades, decorators, factories, still give me some grief when trying to wrap my head around all of it.
Also, I think it was here that someone posted someone's blog that had something like "a laymen's OOP terminology" page. If anyone has a link to that, I'd appreciate it (computer crashed before I bookmarked it).
@Gareth,
This is probably a good starting point:
http://www.pbell.com/index.cfm/2007/8/13/IBO-Sample-Code
although there are quite a few links worth looking at:
http://www.google.com/search?q=%22iterating+business+object%22&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a