[ZPatterns] Re: Demeter, Normal Forms, Caching and kickTriggers (was Re: New ZPatterns release)

Steve Alexander steve@cat-box.net
Sun, 27 May 2001 17:36:13 +0100


Phillip J. Eby wrote:

> 
> It may be that you are dealing with some other kind of model than I'm
> thinking - for example, something like a gym with personal trainers.  That
> is, there really is no notion of "course", and you have:
> 
> Person - Student role - Instructor role - Person
> 
> In which case only Students and Instructors are specialists, and it is
> proper for an Instructor to list its students, or a student to display its
> Instructor.  


This is what I have in this application.


> What isn't proper (IMHO) is for the student to store data for
> its instructor.


Sure. The only persistent attribute to do with instructors that a 
student has is an instructor_id.


> It seems to me that there is a straightforward way to solve your problem,
> then.  Add a trigger to your instructor object, which upon a change of
> instructor name, calls a method on the Students specialist to recatalog the
> students.  Voila.  Now the individual triggering disappears.  


This is almost exactly the way the application works. The only 
difference is that the method notify_instructor_changed in the Student 
class (actually in a class extender) calls kickTriggers on itself. The 
rationale for this is that the only place that needs to know about 
cataloging Students in the ZCatalog the SkinScript trigger for Students.
If I don't use kickTriggers(), I need a method for the cataloging that 
gets called by both the SkinScript and the notify_instructor_changed method.


> (Note that
> you can do this recataloging several other ways as well: you can flag the
> instructor as "dirty", you can add it to a list of instructors to be
> recataloged, you can walk through the instructors' students and recatalog
> them as part of transaction commit, etc.)


Actually, looking again at the application, it does the latter. An 
Instructor has a method notify_students_of_change() (or something like 
that), which calls notify_instructor_changed(instructor) on each of the 
instructor's students.
The notify_students_of_change() method gets called by a trigger in 
Instructors whenever the instructor's name changes.

So, actually, it is no big deal to get rid of kickTriggers in this 
application. It is used in a couple of other places too, but for the 
same pattern of updating data in a ZCatalog.



> By the way, I'm assuming that the "caching" you refer to is non-persistent.
>  That is, you're not actually storing an object's data in another object
> (ZCatalog index aside).


Sure. There doesn't seem any point, given all the caching that goes on 
in Dataskins/Racks.




> Hm.  I guess what I'm trying to say is that typically attribute values
> based on those of other objects, typically are things that "want to be"
> domain-level methods, rather than attributes at all.  Sort of like Coad's
> "rankFor"/"rateMe" pattern.  Instructor name is really *not* a student
> attribute - per LOD it should be a method if it needs to be exposed by
> students, e.g. getInstructorName().  It's okay for instructor to be an
> attribute, but not the name.  This is something that's clear-cut at the
> domain-level design stage - implementation is utterly irrelevant.


Yep. In design docs, these do appear as methods. I chose to implement 
them as computed attributes in SkinScript, for convenience, and for the 
caching of values within a transaction's boundary.

So, I end up with one SkinScript statement that gets a student's 
instructor, and makes available various attributes of the instructor. 
This is an implementation thing. The high-level design just has an 
association between Student and Instructor. The low-level design has 
instructor_id as an attribute of Student. There is no attribute 
instructor_name at this level.

WITH Instructors.getItem(self.instructor_id) or NOT_FOUND COMPUTE
   instructor=RESULT,
   instructor_name=instructor.name,
   (similarly for other attributes of instructor)
OTHERWISE LET
   instructor=None,
   instructor_name='no instructor',
   (similarly for other attributes of instructor)

This is very useful for letting someone else do the user interface in 
DTML, as they can just get a list of the "attributes" of a student that 
work. They never need to use <dtml-with ...>, <dtml-let ...> or 
<dtml-var expr="...">.



> Now, if you get down to cataloging something and you have to have this
> data, it's certainly an acceptable hack to throw it in as sort of a
> "private attribute" as you have done.  That's an implementation detail, and
> hideable.  But if you need the instructor name from application code which
> is not part of a method of "student", there should be a
> getInstructorName()-type method on student.  (If you need it within methods
> of student, it's within LoD to do self.instructor.name.)


There is no reason to want a student's instructor's name outside of 
Student. All that gets passed around is either an instructor_id or an 
Instructor object.



So, in summary, kickTriggers() is saving me a method call and method 
definition here and there, but I can quite easily do without it. Who 
knows... the design might even improve as I remove it :)

--
Steve Alexander
Software Engineer
Cat-Box limited