From: Nathan D. <na...@ch...> - 2002-10-30 01:44:08
|
So, the question before us is whether "child" objects or "joined" objects should be a separate kind of attribute of a contentObject from a field. Hmm. One question I've been thinking about is whether this is about a parent-child relationship, in which case it makes more sense to use the way you've been working on (and I'd consider calling it getChildren("threads") or something like that. Or, is it more that a contentObject can have some "joins" -- and really we need a way to define a join (as being either one-to-many or many-to-many, for instance). Seems the first issue is whether a field should be able to be of a type that is actually just another contentObject. An interesting question. I think the answer is yes, but I can also see how it causes a lot of problems for the API and the cleanliness of the code base. One idea I floated was that there should be a way for a field to be an array of values, independent of whether it's contentObjects or not. Then, have a way for the value of the field to be another contentObject. A field would then, perhaps, have methods like isArray() and isContentObject() -- which would help a user who might want to build some abstracted interfaces. Both arrays of values and contentObjects as values creates some nastiness in the interface, though, potentially. For instance, renderSimpleForm() on a contentObject -- could get ugly. Right now I'm thinking that moving towards the XML descriptor is the first step to any of this. I am increasingly convinced that the CFPROPERTY route is too limiting from an programming standpoint and apparently is also potentially a big problem when it comes to performance (per an off-line discussion with Sean Corfield). Once we're using XML I think a lot of the other issues will be easier to tackle. So, for instance, the pressRelease.cfc in the modustest app might look like: <contentObjectType label="Press Release"> <fields> <field name="title" label="Title" type="org.bacfug.modus.fields.text"> <rules> <rule name="org.bacfug.modus.validation.full" /> </rules> </field> <field name="body" label="Main Body" type="org.bacfug.modus.fields.longText"/> <field name="image" label="Photo" type="org.bacfug.modus.fields.webImage"/> <field name="featured" label="Featured?" type="org.bacfug.modus.fields.yesno" default="no"> <rules> <rule name="org.bacfug.modus.validation.boolean"/> </rules> </field> <field name="category" label="Category" type="modustest.contentObjects.categoryPicker"/> </fields> <contentObjectType> First question: does that make sense? So, now the issue is if I were building an app with child components (or joined components) it might look like: <contentObject label="Forum"> <field name="threads" isArray="yes" type="myApp.threadComponent"> </contentObject> Or, it might look like: <contentObject label="Forum"> <childObjects> <objectType type="myApp.threadComponent"> </childObjects> </contentObject> Or, perhaps: <contentObject label="Forum"> <joins> <join type="one2many" objectType="myApp.threadComponent"> </joins> </contentObject> Any thoughts from the rest of you about which would be best? In that last one, would we think that the thread than also has a join in its definition? Would we need a separate place where a join is defined outside of this config file? Tangent: if we have fields that have arrays of values, do we want to expose the CF array, or would we want to create methods to traverse it (using some kind of iterator) to maintain the object-based API? Hope that's enough to continue the conversation . . . - Nathan -----Original Message----- From: mod...@li... [mailto:mod...@li...]On Behalf Of Jeremy Firsenbaum Sent: Tuesday, October 29, 2002 2:51 PM To: modus devs Subject: [Modus-devs] Collections of content objects and performance Thought this thread might be of interest to others on the list. Nathan and I have been talking about a couple of issues I've raised, namely, contentObjects containing collections of other contentObjects and the performance impact of instantiating so many objects. Note that this reads from the bottom up: Nathan - certainly not wed to the interface. But unless, as you suggest, the definition of what a field is changes I don't see a way of using basefield for content objects. Back to the forum example I've been using - retrieving a collection of threads: The way I suggested: <cfset forum= createObject("component","forum").init(forumID)> <cfset threads= category.getObjectArray('threads')> <cfloop from="1" to="#arrayLen(thread)#" index="i"> <!--- doing stuff with each thread, like: ---> #thread[i].getField('thread').getValue()# </cfloop> One possible way of doing what you suggest: <cfset forum= createObject("component","forum").init(forumID)> <cfset threads= category.getField('threads').getValue()> <cfloop from="1" to="#arrayLen(thread)#" index="i"> #thread[i].getField('thread').getValue()# </cfloop> As you can see the only difference is the method call for the threads array. And of course this gets back to the descriptor for the content object. In the first case thread would have a property type of 'objectArray' or 'subcomponent' or something like that, and in the latter it has the type 'field' with a particular fieldType. If the latter seems cleaner to you, fine. I just did it this way to avoid reworking basefield - sort of a proof of concept. The developer using the API already needs to know the 'type' for various fields. Most are simple strings, but anything that extends basefile will certainly be handled differently, or at least make several new methods available. So making 'contentObject' be a possible fieldType should be doable - the basefieldValue idea is probably the way to go. Let me think about that a bit. As for the performance issue, it comes in precisely when you want to instantiate a whole array of contentObjects. If I call load() on a 40 page book object, a new instance has to be created for each of these pages. The only way to avoid that is to reference the cached object, which opens up a whole other can of worms - locking the server scope, concurrent access to writable data... This may be possible, but is this a route worth going down. Maybe others have thoughts. - Jeremy ----- Original Message ----- From: Nathan Dintenfass To: Jeremy Firsenbaum Sent: Tuesday, October 29, 2002 12:27 PM Subject: RE: Ah, I see what you mean. Yes, get() does that, but only if you wish to populate from another instance -- which you pass in -- that is, if you have an existing contentObject that you want to "load()", then you need to do this. But, in this case you still never need to recreate a whole new instance as you already have the one you want to load and the one you retrieve from the cache (except on the first hit) -- but, what you are saying is that is a performance bottleneck for you, right? Hmm. On the "interface" I was talking about -- yes, the API exposed to the end user. I figured you were not wed to it. My instinct says that it would be better to do an overhaul of what a "field" is. First, I'd make it possible to have an array of values, even if not other contentObject. Then, I'd work on making it possible to have the value of a field be a contentObject. Perhaps baseFieldValue.cfc would be one way to get towards that?? I totally agree that one way or another there needs to be a way to have a "book" and its "pages" (not to mention many-to-many relationships) and have "book" and "page" each be instances of a contentObject. - Nathan -----Original Message----- From: Jeremy Firsenbaum [mailto:jfi...@ma...] Sent: Monday, October 28, 2002 5:33 PM To: na...@ch... Subject: Re: "By the way, the only place I am using the makeClone() is when I put objects into the cache, not when I take them out of the cache" I'm sorry if I was misleading. "Cloning" takes place going in as well as coming out, although the makeClone method is only called one way. Going in, makeclone calls contentObjectPopulateFromInstance, which does all of the actually duplicating. Coming out, get() calls contentObjectPopulateFromInstance as well: line 41 from simplefilesystempersister.cfc: if(instance.cache.isObjectCached(arguments.id)){ objectRetrieved = instance.cache.getObject(arguments.id); if(structCount(arguments) GT 1) return contentObjectPopulateFromInstance(arguments[2],objectRetrieved); else return objectRetrieved; } I found the duplicating from a cached instance in contentObjectPopulateFromInstance to be the bottleneck. "I'm not sure I'm personally satisfied with the interface you are using in your forums app." I'm all up for criticism - but not sure what you're referring to by interface. Do you mean the contentObjectArray within basecontentobject, or the way in which the API is used externally to construct the forum. Obviously this was just an example I was using to work with the idea of content objects within content objects. "Are you waiting for a signal from me as to what my alternative might be?" Well - kinda, yeah. I mean I would like to know if this approach to "composition" even makes sense, or whether there's another way of doing "parts of" relationships within modus. Many systems, content management or otherwise, have this kind of structure. An artifact, say a book, has many pages, each one of which has been scanned in. At one level, you want to deal with the book as a whole (when presenting a library of books), on another level, each one of the pages is a content object unto itself (so that the book is browsable). I would like to be able to load the book, for a single request or in session scope, and have the pages come along for the ride, just as each thread is available within the forum content object. Does this even make sense within the context of what modus was and is being designed to do? ----- Original Message ----- From: Nathan Dintenfass To: Jeremy Firsenbaum Sent: Monday, October 28, 2002 7:56 PM Subject: RE: Ah, yes. Well, right now you are at the cutting edge of that question. I'm not sure I'm personally satisfied with the interface you are using in your forums app. Doesn't feel clean to me, but I have a hard time justifying that feeling. Are you waiting for a signal from me as to what my alternative might be? By the way, the only place I am using the makeClone() is when I put objects into the cache, not when I take them out of the cache -- are you experiencing serious performance problems with that? My main issue with performance before the caching was with the getAll(). Load() is still a bit slower than I'd like, but I figure that will be rarely called in volume since getAll would be generally be faster. That is an area to work on, though. - n -----Original Message----- From: Jeremy Firsenbaum [mailto:jfi...@ma...] Sent: Monday, October 28, 2002 4:45 PM To: na...@ch... Subject: Re: Sorry for being obtuse - I mean being able to declare other content objects as being contained within another content object: forums are composed of threads, which are composed of posts... ----- Original Message ----- From: Nathan Dintenfass To: Jeremy Firsenbaum Sent: Monday, October 28, 2002 7:37 PM Subject: RE: When you say "composition of content object" what do you mean? - n -----Original Message----- From: Jeremy Firsenbaum [mailto:jfi...@ma...] Sent: Monday, October 28, 2002 4:33 PM To: na...@ch... Subject: Re: Fair enough. Maybe it's all of the nested subcomponents that accounts for the slowness in cloning from the cache. I'd love to do some more work on modus, maybe sink my teeth in the XML descriptor thing, but I feel like I can't do much with it until I know whether composition of content objects is feasible. This seems to be the most important feature needed to model more complex systems. Looking forward to seeing what you think. -Jeremy ----- Original Message ----- From: Nathan Dintenfass To: Jeremy Firsenbaum Sent: Monday, October 28, 2002 7:13 PM Subject: RE: Jeremy: I hadn't yet gotten to look in depth at your changes. I agree the clone() method is far from optimal (as I mentioned in that posting). My comment was based on the dramatic performance enhancement I witnessed on the modustest app after implementing the caching. My calls to getAll() went from over 400ms with a handful of "press releases" to 20ms. - n -----Original Message----- From: Jeremy Firsenbaum [mailto:jfi...@ma...] Sent: Monday, October 28, 2002 3:52 PM To: Nathan Dintenfass Subject: Hey Nathan I saw your post to CFCDev today: "I have taken pages that were slow because they had to instantiate dozens of components and made them very fast by just grabbing from the cache in the server scope." This got me wondering if you have looked into the performance issue with clone() that I mentioned last week. It seems like it wouldn't make a difference whether the subcomponents were instantiated within the contentObject, as I had tried to do, or if this were left to the developer to do externally: forums = createObject (forum.cfc), loop for threads = createObject(thread.cfc). If you need "dozens of components" the clone technique doesn't seem to be optimal. Were my performance numbers similar to yours (if you've had time to look into this) or am I totally off base here? Jeremy |