To learn all about Skin Spider, click here.
To view the most updated application, click here.
To view the current code base, click here.
This is a very minor update. I prepared the ColdFusion user defined library structure. The library itself, UDFLib.cfc, is broken up into smaller, more cohesive libraries:
I have done this to make the library more organized. I find that this leads to better naming, more code reuse, and generally easier to maintain code. It also cuts down on naming conflicts. Each library is stored in the public THIS scope of the UDFLib.cfc and is references by its short name:
Right now there is no functionality built into any of the libraries (with the exception of the AJAXLib.cfc which has some very basic JSON code to be used later in the project). As part of my next step, I will start going through the code and moving functionality into the user defined library. I will also be applying changes that are required for the Config.cfc ColdFusion component. I felt I wanted to set both of these objects up (Config and UDFLib) before I fully integrated them into the project. I thought this would make it easier to understand.
If you look at each of the user defined libraries, you will see that each library requires a reference to the parent UDFLib.cfc instance upon initialization. This allows any of the child libraries to use methods from any of its sibling libraries. All it would have to do is access them through the parent library reference. For example, the AJAXLib.cfc might need some string functionality built into the StringLib.cfc. To use this, it could simply call VARIABLES.Library.String.SomeMethodName(). This will allow for maximum code reuse within the set of libraries.
I have put the UDFLib.cfc in the main components directory but put all the child libraries into a sub directory named "UDFLib". This is purely for higher maintainability. The UDF library objects are functionally grouped into their own directory; they don't need to be with the other ColdFusion components as they are not related to them in any way. No need to have large directories when I can break them up into easier to understand, smaller ones.
Application.cfc ColdFusion Component
If you look in the Application.cfc ColdFusion component, you will see that in the OnApplicationStart() method, in which I am doing application initialization, I am creating an instance of the user defined library (UDFLib.cfc) and caching it in the APPLICATION scope. This means that I only have to do this processing once (per application run). It also means that I can get a reference to the UDFLib.cfc instance through the APPLICATION scope: APPLICATION.UDFLib. Notice that I am only creating an instance of the UDFLib.cfc and none of the child libraries. This is because the UDFLib takes care of creating all of its own libraries during its own initialization.
CustomLib.cfc ColdFusion Component
The user defined library is supposed to be composed of generic functions. They are meant to be application-agnostic. However, there are going to be functions that are designed for a very specific, customized feature of a given application. I am placing these in a library called CustomLib.cfc. This will contain ONLY application-specific functions. The idea is that as I build new applications I should be able, in theory, to copy over all the user defined library components to the new application without having to modify anything... except of course, for the CustomLib.cfc, which will not be applicable to the new application.
Looking For A New Job?
- Wanted: Full-Time ColdFusion Developer at Intoria Internet Architects
- Cold Fusion Senior Developer at Edge Information Management
- Back-End Web Developer-Information Technologist at Michigan State University
I like the code viewer - cool! Not sure about the whole idea of a bunch of UDFs as the whole point of OO is to wrap functions with the data it operates on into cohesive objects, but I'll be interested to see where you're going with this!
Two things. One, the application has not quite been OOP'icized yet. Remember this is a journey towards a final product. Part of the way I am doing things is so that I can then go back, explain why another way would be better, and the refactor/rework.
Two. While functions should be grouped with the data they operate on, remember we are aiming for the DRYest (Do not Repeat Yourself) code base. There are going to be functions that are global "utility" functions that are code-independent.
As an example, the ListLib.cfc might have a method called ListLib::RemoveNonInteger( list[ , delimiter] ) which would remove all non-integers from a list.
This is a utility function that might be used in more than one place (for example, any form that might submit a list of IDs). There's is no need to repeat that functionality in all the places that require it.
The most non-OOP mentality might be the CustomLib.cfc. I agree with you on this one (that it should be grouped with the stuff that uses it). We shall see where that goes (I am just as interested ;)).
Also, glad you like the Code Viewer (and happy you finally used it!). I am trying to put ALL the cards on the table. That is why you should be able to see the unique code base for EVERY update I make to the application.
Agree with DRY which is why I tend to use base classes. All other things being equal, only DAOs should know about db, so Query functions go in there. Similarly I have a controller helper tied into custom data types that handles all of the form processing.
I do agree, though that sometimes it is hard to find an OO place to put stuff - I'm still struggling with a few functions that I'm not sure where to put.
I find a lot of those are due to the fact that in CF you don't have the "everything is an object", so there are a number of functions that should really be associated to properties (firstName.Captitalized() or Product.get('Price').asInteger() would be a couple of made up examples) and because you can't ask properties to do things (because they aren't objects) you are stuck having to manipulate the properties. While it is just a fact of life in CF, I'm starting to feel that the lack of everything is an object is the biggest conceptual weakness in CF.
Right now I have a set of DataType cfcs which allow me to fake this functionality using a service layer approach to processing custom data types, but if I EVER left CF for Ruby or Python, it would be for this one thing!
I hear you on the data type stuff. It would be nice for the data types of have a bit more functionality, but so far, it's been pretty easy to work with. I like your custom data type object work-around, but the only part I don't like is you can't override the "=" operator (ex. CustomString = "Ben") so you have to set them or pass them into a constructor or something.
Hopefully when I start putting my form processing into more objecty-type stuff, you will be able to give some great advice. At that point I am less the teacher on the application, but more learning as I go.
Uggg, sorry, i have someone else's info saved in my form fields... it keeps sending out a bad email.
Are you sayaing that ALL DB calls go through the DAOs? You're DAO objects are doing more than just CRUD... what kind of methods do they have? Is part of that whole GetByUniqueAttribute() base component stuff?
For me, the DAO abstracts data access. Period. You pass it a list of the object attributes you want (whether or not they are persisted in the db) and it returns a recordset with all of the fields required (you ask for Age, it is smart enough to go get the DateofBirth field and so on).
Right now, my DAOs are small enough that I don't need a seperate Gateway and if you look in the Java world you'll see my approach is not unique.
I'm still working up the API for BaseDAO, but it includes:
and of course init()
It has private methods for insert(), update(), getbyUniqueAttributeList(), deletebyUniqueAttributeList(), getbyFilterPaged().
I'm still working on adding reporting methods to support havings and group bys.
Will blog much more this weekend (just got flattened with some stuff this week).
OK I give up. Why is it the your init method for all your libs are like so:
<!--- Define arguments. --->
<cfargument name="Library" type="any" required="true" />
// The library variable is a reference to the main UDF library. This is
// going to be used to reference other parts of the library that are not
// in this specific component.
VARIABLES.Library = ARGUMENTS.Library;
// Return This reference.
return( THIS );
Each child library (ex. AJAXLib, StringLib) get's passed in a reference to the parent library (UDFLib). It then saves this reference into its own private scope. That is the whole:
VARIABLES.Library = ARGUMENTS.Library
In this case, ARGUMENTS.Library is a reference to the parent library (UDFLib). This allows each child library to call methods for sibling libraries (by first crawling up the parent-child chain to the parent (UDFLib), then accessing a sibling object (ex. StringLib) from the parent (UDFLib)'s THIS scope.
Then, the INIT method returns the THIS reference which is a reference to the child-library object itself. Since all the child libraries are being created from the parent UDFLib.cfc, the THIS reference is being passed back to the parent, who is, in return, storing that THIS reference of the child library into its own THIS reference using a shorter name (THIS of AJAXLib becomes THIS.AJAX in the parent UDFLib object).
Hope that helps and does not confuse further.
So Pete, your DAO are not taking in BEAN objects to get data from?? You don't have to answer this... I look forwarding to reading anything you blog about this.
That is a little bit scary about accessing sibling libraries via parent. I'd really expose these as singeltons and just inject them into each other using CS or LightWire if necessary. Otherwise you have a bunch of undocumented dependencies where removing any given function from a UDF could break any or all of the other UDFs as the dependencies are explicit.
But as you said, go for it, play with it and find out what works for you. I enjoy watching the journey as I'm going through the same thing myself. Don't follow an approach unless it is solving a problem you have or see you'll have in the future otherwise you're just copying - not designing!
For now, my DAOs return queries but accept IBOs. A little unbalanced, but my API is still really fluid - same as SkinSpider! Will keep you posted.
I am not sure I agree with you regarding the DI for the UDFLib objects. There is documentation. Each child library requires the parental reference. In fact, is this not what Constructor Injection is? Then, the UDFLib is built to have all its children accessed from the outside world (aka. From any code block that has a reference to the UDFLib singleton). Since the child libraries include (nay, required) a reference to the parent, they should be able to act in the same way.
I think for the UDFLib / Child, part of the "contract" of the child library is that it exists only as part of the UDFLib object.
But again, even regardless of this, again, the very fact that each child library has a parent reference means that it can use the library the same way that any other code does. And yes, if one child library changes, the sibling code might have to change, but this is no different than any other code template that makes use of the UDFLib.
My gut feeling is that DI for this type of collection of features is going to be overkill. Of course, again, the whole point of this is to learn, so it might be a mistake that I can learn from very soon. Perhaps you had a particular scenario in mind that would cause issues?? I always appreciate all your advices.
I guess my issue is accessing siblings via parent. Does every udf need access to every other one? If not, you're not documenting anywhere WHICH udfs depend on which others whereas DI would make it clear the list of UDFs that each other UDF depends on.
But I'd just keep going as you are. Want to mention some things to consider, but don't use them unless they add sufficient value to your use case!!!
That's cool. I understand the concern about the documentation. We shall see where this goes.
>> Uggg, sorry, i have someone else's info saved in my form fields... it keeps sending out a bad email.
That would be me?
Doubly confusing because all the e-mails I randomly looked at were starting "Pete,"...I was beginning to think I'd been sleep-commenting. :S
Regarding your library thing... it seems a bit odd to group AJAX and Validation stuff with data type/structure stuff, although now I look inside, I'd just say the AJAX one is badly named; wouldn't JsonLib be a better name, since you're just converting to JS with it, rather than actually handling HTTP requests?
I think that is an excellent suggestion (re: JSON library naming). You are correct, I will not be using anything else but JSON (which I love, but haven't used all that much).
Thanks! And sorry about the bad email postings :(
I think I need to read your Tony answer about your parent-child chain concept not less than ten times to understand it. No need to explain it again, but if any question I'll ask you. thanks.
Think about it like police cars and a police dispatcher. Each police car knows about the dispatcher (the lade at the police station who is always making calls over the radio). Each police car can get in touch with another police car by telling the dispatcher who in return then calls the appropriate cars.
Let's say a cop car (ListLib.cfc) is in a high speed chase. It calls the dispatcher (UDFLib.cfc) and asks for help (needs a method in StringLib.cfc). The dispatcher knows where all the other cop cars are (AJAXLib.cfc, StringLib.cfc, ArrayLib.cfc) and can call the appropriate cars (StringLib.cfc) and send it to help the original caller (ListLib.cfc).
I hope that analogy might help a bit.
I know it is too late. But I neet to write you a big THANKS and tell you that finally I understood it perfectly. I read this post again and again when a question came in my head.
The question is, What if I'm in component PageData.cfc and I want to call a function from other component like DatabaseService.cfc? should I use application.DScfc, which is a reference to DatabaseService.cfc object, to call that function? Or better to use request scope variable refer to the object that's application.DScfc refers.
All that both ways are possible. And after I understood your wonder parent-child chain concept, I got that it solves this issues by having a cfc local variable refers to the whole UDF libraries to call any function from out side this cfc. But assuem -just assume- that I'm not using your way and I'll be using one of my two suggested, dose any one more better that other considering performance issue?
Another different question, The part of code which is inside the component and out side any function, such as:
// I mean this part
// Inside component and out side the function
In whcih case of next four this code will be executed?
- Creating object with/without initialize (using init()).
- Calling function from this component using already created object from this component.
- Calling function from component directly without using the object, like this:
<cfinvoke method="addOne" component="AnyCom">
Finally if this component is the Application.cfc file, will that part of code be executed for each page request, and even the component object will be created also?
NOTE: When I rote this comments I didn't find your anti-spam equation.
Instead of putting the objects in application scope directly, check out ColdSpring. That is a much better way for objects to access each other. www.coldspringframework.org
Thanks I'm downloading it now to check it. I think I have and old vertion.
The space in a component that is located outside of function definitions is run when the ColdFusion component is created.