|
From: Nathan D. <na...@ch...> - 2002-10-31 22:51:22
|
We are very much on the same track. I agree that "join" is the wrong word
in the OO context. Collection is much better. In fact, they probably
should even be arrays, they should be structs (in CF terms). I extended the
XML config I put together after my last post to toy with what it might look
like. I came up with:
<contentObjectType label="Press Release">
<fields>
<field
name="title"
label="Title"
type="org.bacfug.modus.fields.text">
<rules>
<rule
name="org.bacfug.modus.validation.full" />
<rule
name="org.bacfug.modus.validation.maxlength"
maxlength="50"/>
</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"/>
<field name="type"
label="type"
type="org.bacfug.modus.fields.picklist">
<options>
<option value="normal" label="Normal"/>
<option value="special" label="Special"/>
</options>
</field>
<field name="related"
label="Related Press Releases"
type="org.bacfug.modus.fields.contentObject"
multiple="yes">
<objectType name="modustest.contentObjects.pressRelease"/>
</field>
</fields>
<contentObjectType>
I had originally called it contentObjectCollection, but then I was toying
with the idea of "multiple=yes" as a different way. The difference is that
rather than having a baseCollection I wanted to be able to have any field
have multiple values. That may be too ambitious given the difficulty it
creates for the "out-of-the-box" API. But, it would be great to be able to
just say that any field hold multiple values without having to write a
textCollection, longTextCollection, etc. Of course, it may be that the only
time a collection is needed would be for other objects, rather than for
"simple" values.
I rather like the "key" notion you introduce. I was also thinking that
there might be a reason to have a contentObjectType have a way to define
what it's "handle" should be -- then, for instance, in renderQuickForm() I
would be able to just make a SELECT field with press releases to choose
from, using the "key" field as the value of the OPTION tags and the "handle"
of the pressRelease object as the label. Though, I guess it might be nice
if the key and "handle" label were both defined in the descriptor that
actually defines the collection -- that would probably be cleaner. Hmm.
With many-to-many it might be OK to just define the relationship on both
sides. Take a basic example: Say, I have articles that have one or more
authors. I want to see which authors wrote any given article, but I also
want to see the articles written by any given author. In that case, I might
define the contentObject collection in both the articles descriptor and the
author descriptor to point at each other. But, I'm not sure how pretty that
is when saving. Hmm.
-----Original Message-----
From: mod...@li...
[mailto:mod...@li...]On Behalf Of Jeremy
Firsenbaum
Sent: Thursday, October 31, 2002 2:26 PM
To: modus devs
Subject: Re: [Modus-devs] Collections of content objects and performance
Nathan -
As for the XML descriptor format I totally agree. In fact, after we
discussed this last week I put together an example that is almost identical
to what you've constructed. This seems to be a no-brainer and the parser
just needs to be built.
In terms of the array issue I keep harping on, your suggestion of a
generic 'array' field type got me thinking. What we're really talking about
here are collections (to use a java term). The "join" terminology seems to
come from the relational database world, and, please correct me if I'm
wrong, doesn't really fit into an OO context.
What if we extend basefield with a basecollection component. A few
accessor methods to basecollection are added, or maybe override basefield,
to enable access to multiple items, be they simple or complex values. Then
any type of collection can be constructed: the contentObject array, an array
of string values, maybe even structures. Most of the basefield methods work
'out of the box' and problematic methods, such as renderSimpleForm(), are
overriden or nullified (made to throw an exception perhaps). With the new
XML descriptor format a 'key' property to the collection type allows for
simple identification:
<contentType label="Thread">
<fields>
<field name="thread"
label="Thread"
type="org.bacfug.modus.fields.text">
<rules>
<rule type="org.bacfug.modus.validation.full"/>
<rule type="org.bacfug.modus.validation.maxlength" value="100"/>
</rules>
</field>
<field name="postid"
type="org.bacfug.modus.fields.hidden"/>
<field name="posts"
label="Posts"
type="org.bacfug.modus.fields.objectcollection"
key="postid"/>
</fields>
</contentType>
It seems that this should work without having to create a baseFieldValue
component, as you've suggested - although if you're still thinking about
that, and it seems to offer more flexibility, let us know.
I think a few more concrete examples might help flesh this thing out.
We've got the simple press release and I've offered a few examples of
'parent is composed of child' type relationships. Maybe someone has a
scenario in which the relationship is not one of composition, such as the
thread containing posts. I'd be curious to see what a many-to-many
relationship would look like in object terms. Are there situations in which
this collection field type won't work?
-Jeremy
----- Original Message -----
From: Nathan Dintenfass
To: mod...@li...
Sent: Tuesday, October 29, 2002 8:45 PM
Subject: RE: [Modus-devs] Collections of content objects and performance
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
-------------------------------------------------------
This sf.net email is sponsored by:ThinkGeek
Welcome to geek heaven.
http://thinkgeek.com/sf
_______________________________________________
Modus-devs mailing list
Mod...@li...
https://lists.sourceforge.net/lists/listinfo/modus-devs
|