[TransWarp] Tips on using model, storage, config, and
naming (was Re: First attempt to use the storage package in PEAK)
Phillip J. Eby
pje at telecommunity.com
Sun Dec 29 11:00:09 EST 2002
At 10:31 AM 12/29/02 +0200, Roché Compaan wrote:
>Jeez, you think of everything ;-)
I try. Sometimes I drive Ty nuts with my, "but what about X?" questions,
though. :)
>But I'm glad you put so much effort
>into a proper validation framework - I found that without it you quickly
>end up with spaghetti code especially when you have to validate changes
>accross fields and accross objects. I wont buy into a framework that
>validates fields atomically.
Yeah, that's pretty useless for meaningful domain models. The work that
I'm doing on the MOF will be an interesting test case in itself for
validation: MOF has a lot of meaningful semantic constraints, like that
something can't subclass itself or contain itself, directly or indirectly.
But the MOF definition itself includes the notion of constraints that are
either "immediate" (checked in real-time) or "deferred" (checked manually
or perhaps at a transaction boundary), and I think it will be best to
implement these capabilities directly in peak.model, since such constraints
are part of the MOF model, and thus part of the UML and CWM metamodels.
> > Ideally, if one could specify constraints in "relatively declarative"
> form,
> > it would make validating constraints that much easier. Actually, I
> kind of
> > wonder if maybe I shouldn't be looking into OCL, because then one could
> put
> > that into the UML model for an application from the start.
>
>OCL would be great, but expressing constraints in OCL shouldn't be a
>requirement. I think one should be able to express constraints in python
>as well, not free from, but in a form that can be mapped back to OCL.
Right. The simple way to do that would be to create object classes
representing OCL constructs, that can be created straightforwardly in a
Python expression. Then, if that works out well enough, it should be
possible to create an OCL translator that creates the same objects. That
overall approach worked pretty decently for the design and implementation
of SkinScript in ZPatterns.
But first, I need to learn OCL. :)
>Thanks for all the great tips on the config and naming packages. I love
>the fact that configuration variables can be aliased, are in one place
>and that components can easily discover them.
Eh? I think I know what you mean by "aliased", although technically
they're not aliased; it's just that you can define one set of properties in
terms of another.
But "in one place" is something I'd disagree with; you can put them in one
place if you want, but every object can define its own value for a
property, e.g.:
class MyComponent(binding.Base):
someVar = binding.bindTo("otherVar",
provides=PropertyName('some.property'))
Instances of MyComponent will supply the value of 'someVar' in response to
a request for 'some.property', so any child component of a MyComponent
instance will use that value as well. You will see some instances of this
pattern's usage in PEAK source code. You'll also see some things like this:
someVar = binding.Acquire(PropertyName('other.property'))
What this does, is that if 'someVar' isn't manually set, it will look up
'other.property' in its parent components. But the component and its child
components will use the value of the 'someVar' attribute as their own value
of 'other.property'. This means that if you were to do this:
aComponent = MyComponent(someVar="some value")
Then you are effectively setting the value of 'other.property' to "some
value" for 'aComponent' and its children.
As for whether components can "easily discover them", that's an interesting
comment. There isn't any discovery capability for properties; you can look
one up if you know its name, but there's no way to find out what properties
do or don't exist. So, for example, you can't iterate through all possible
URL scheme names. The property namespace is potentially infinite. You
could do this, for example:
[peak.naming.schemes]
foo.* = # some code here to create a new scheme handler based on the scheme
name
This would let you use URL schemes of the form "foo.bar:", "foo.baz:",
etc. This kind of capability is incompatible with true "discovery",
although Ty and I have batted around the idea of making it possible to at
least discover that a rule for 'foo.*' exists. This would really just be
*rule* discovery, not *property* discovery.
> I still have a hell of a
>time maintaining instances of Zope apps where mailhost and database
>connections store configuration variables in themselves.
Yes, that was definitely a "misuse case" for PEAK, in the sense of
something we definitely didn't want. Storing that kind of configuration in
a persistent object as part of an application is a twisty entanglement of
problem domain and solution domain.
>At the moment PEAK addresses almost all my concerns as an application
>developer and I can really see that it will make maintaining
>applications orders of magnitude easier.
That was certainly the "big idea" behind PEAK, as distinct from TransWarp
(whose primary mission was application generation). I'm glad that you're
already finding it useful.
>There is one big concern that
>is not necessarily the responsibility of a framework like PEAK and has
>more bearing on the implementation of the problem domain namely, 3rd
>party customisation. I mention it in the hope that you can already see a
>pattern that will work well with PEAK.
The config package gives you all the hooks you need to deal with this sort
of thing, but of course you have to implement your basic application or
framework in a suitable way. This could be as simple as using:
config.setupModule()
at the end of a module's code. This allows people to create "patch
modules" which can "advise" your module's code, wrapping functions around
your functions, redefining class hierarchies, etc. It also allows people
to "subclass" your module, creating their own instance of it that is
similarly modified.
You can also define plenty of hook points using configuration variables, e.g.:
class Something(binding.Base):
OtherClass = binding.bindToProperty('myapp.Something.CollaboratorClass')
[myapp]
Something.CollaboratorClass = importString('some.package:SomeClass')
And of course this is then overrideable in an end-user's application
configuration file.
> The best solutions I've seen so
>far is CMF Skins and by far the best one is the hyperdb used by Roundup
>(a python based issue tracker to which I contributed some code a while
>back - http://roudnup.sf.net/). You most probably know CMF Skins so I'll
Actually, no, I'm not that familiar with it. I read something a long time
ago but I've never really used it so it has already dropped out of my brain
cache. :)
>When you install Roundup the core is installed in
>lib/python/site-packages. Creating an instance of an issue tracker is a
>two stage process. First you add an instance with a command-line script
>to anywhere on the file system. This installs a default class schema and
>html template in the directory you specified.
One way we expect this sort of thing to work with PEAK is that an
application "instance" would look like this:
#!/bin/env basicApp
...configuration file of arbitrary format goes here...
And the 'basicApp' interpreter would look like this:
#!/bin/env PEAK_App
[some.properties]
#... ".ini" format configuration goes here
[peak.running]
app_config.format = # code to load the schema needed to parse the instance
config file
Finally, there would be a 'PEAK_App' script, written in Python, provided as
part of PEAK, which would basically set PEAK_CONFIG to point to argv[1]
(i.e., the 'basicApp' script), and then invoke the
'peak.running.app_config.format' schema on argv[2] (the application
instance config) to create a root component for the application, and
execute it (probably via a 'run()' method or some such).
The idea here is that you'll use a more user-oriented configuration format
(such as Zope 3's ZConfig syntax) with an application-specific schema for
its content. The configuration file itself becomes the executable for the
application instance. Applications themselves are a PEAK .ini-format
configuration file that specifies the "meta configuration", if you will.
You'll see that this concept allows you to "inherit" from an application
definition, as well. For example, you could create this "extendedApp" script:
#!/bin/env PEAK_App
[Load Settings From]
file="/path/to/basicApp"
#... various settings to override those in basicApp
And then you could use "extendedApp" as the interpreter for instances of
this new extended application definition.
In principle, there is no limit to how deep the inclusions or meta-levels
can be. In practice, however, I think that two metalevels of configuration
(instance and application), with "n" levels of inclusion and the use of a
PEAK_CONFIG-specified environment configuration, should be plenty of
flexibility. :)
>Now I only have to modify the html template for the user, initialise the
>instance and start using my customised instance.
This would of course be possible with the above techniques as well.
>Although the hyperdb is quite flexible wrt to custom schemas and user
>interfaces the downside is that it has relatively few hooks where custom
>methods can be plugged in.
>
>Whereas CMF Skins allows for generous customisation of the user
>interface and provides you with a perfect place to add custom logic
>through scripts and external methods it only solves part of the problem.
>One still needs something that can accommodate 3rd party modifications
>to class schemas that won't be heavily disrupted by upgrades of the app.
I think that simplest way to do this sort of thing in PEAK would probably
be to have a property that specifies where to import the "model.Model" that
describes the application's problem-domain object model. The
solution-domain components would have 'bindToProperty()' attributes to
retrieve the model. Something like this:
class someDM(storage.EntityDM):
model = binding.bindToProperty('myapp.Model')
defaultClass = binding.bindTo('model/TheClassIWant')
[myapp]
Model = importString('some.package:MyDomainModel')
Of course, it may be simpler to do it using a wildcard rule, and something
like:
class someDM(storage.EntityDM):
defaultClass = binding.bindToProperty('myapp.Model.TheClassIWant')
[myapp]
Model.* = # code to load the class named after the 'myapp.Model.' prefix
>I am still thinking of ways that will work best with PEAK but haven't
>come up with something that satisfies me yet. I think the way you
>illustrated how to include the MySQLConnection without modifying code in
>the storage package might be the answer but I'll have to play with my
>Contact example some more to see how well the patterns works with domain
>entities.
Perhaps the examples I've laid out above will help.
>I've attached the mysql driver if you want to include it - it was
>fairly simple to write since MySQLdb implements the DB API.
I'm afraid MySQL gives Ty and I the creeps, and we don't feel comfortable
including a driver for it with PEAK for a number of reasons. Because we do
try to "think of everything", we are concerned about its locking and
transactional semantics and how they would interact with PEAK's caching,
locking, and transactional patterns. We couldn't say whether or not it
will work correctly, and so are totally uncomfortable including it in an
"enterprise application kit".
In the environment where I work, *PostgreSQL* is considered a lightweight
database for prototyping purposes, and Sybase or Oracle are what you use
for production applications. One of our apps has a 30GB database on a RAID
array, and its dedicated database server hardware is a 64-bit,
four-processor machine, with an identical warm standby server continuously
replicated from the first. PEAK really is intended to work at this sort of
"enterprise" scale, and using MySQL for production in such an environment
would be quite insane, IMO.
But Gadfly and SQLite don't pretend to do or be more than what they are,
and their limitations are both more obvious and more clearly documented, so
I don't have a problem with including them so that people can do database
prototyping. I worry that too many people use MySQL in production
situations where they *really* shouldn't be, and I don't want to encourage
that trend. Most people, however, wouldn't be tempted to use Gadfly or
SQLite for production unless they really could live with the
limitations. But many are tempted by MySQL's claims that speed is all that
matters and transactions aren't important. That's why it gives people
whose companies spend hundreds of thousands of dollars on RAID,
replication, and hardware redundancy for mission-critical, 24/7 worldwide
apps, a really creepy feeling about having *anything* to do with it. :)
So, while I'm sure that you would never use it for an application
environment it wasn't suited for, I'm not comfortable with making any
implication that PEAK supports or endorses its use in any way, because
PEAK's intended audience *does* include people who would abuse MySQL, and
in the process would give other people an excuse to trash PEAK, Python, and
Open Source in general for their lack of attention to "enterprise-level"
concerns.
More information about the PEAK
mailing list