Interesting. Except for the bit with having to implement interfaces + cover
methods; you've now "polluted" your code with mixin details. All-in-all, a
nice idea, though. I wonder if we could incorporate mixins (custom or
otherwise?) directly into the modeler + class generation so that the interface
+ cover methods are auto-generated into the DO superclass? Not sure about this,
just "thinking out loud". Maybe if the DO mixins could, themselves, be
annotated, like:
@CayenneMixin(interfaceClass=Referenceable.class,
template="SomeFile.txt")//both interfaceClass and template would be optional...
template would contain some blurb that the superclass generator "mixes in" to
superclass templates.
public @interface ReferenceableMixin {
}
Now you potentially have a way to scan for mixins/gain additional information
about them... anyway, just considering ways to write less duplicate code. :)
Robert
On Nov 1, 2010, at 11/16:27 PM , Andrus Adamchik wrote:
> Been thinking about work-related design issues, and came up with an idea of
> DataObject "mixins". So what are the problems:
>
> 1. Often you'd like to associate a certain set of common properties and/or
> behavior with a group of persistent objects that are unrelated and not a part
> of the same inheritance hierarchy. Here is an abstracted real-life example:
>
> * entities A, B, C are "referenceable" (they have a public UUID)
> * entities A, C, D are "auditable" (when a user changes their data in some
> way, a system must record the change history)
> * entities A, C and E are "access-controlled" (a certain permission level is
> required for a user to view or edit them).
>
> 2. In a layered architecture, DataObject itself may not be the right place to
> implement complex behavior. All non-data methods should ideally be pluggable
> (normally meaning must be IoC-driven), but still attached to a specific
> DataObject.
>
> In other words there's no multiple inheritance in Java, and anyways we want
> to push most of the logic to the higher app layers. So... I am experimenting
> with something I called "mixins", based on DataObject dynamic nature +
> listener capabilities. I checked in to github a simple mixin extension for
> Cayenne:
>
> http://github.com/andrus/cayenne-mixin/tree/master/other/cayenne-mixin/
>
> and a proto-CMS using it:
>
> http://github.com/andrus/cayenne-mixin/tree/master/oc/
>
> I may actually keep developing the CMS piece for my own needs, but here a
> very basic version of it is used for a mixin demo. A mixin in the simplest
> form is just a custom annotation placed on a DataObject:
>
> @ReferenceableMixin
> @AuditableMixin
> public class Article extends _Article { }
>
> It is up to the application to attach lifecycle handlers for a given type of
> mixin. MixinHandlerManager class from cayenne-mixin module provides API to
> bind such handlers:
>
> MixinHandlerManager handlerManager = new MixinHandlerManager(entityResolver);
> handlerManager.addMixinHandler(handler);
>
> A handler implements MixinHandler interface and does whatever is needed to
> the object during its lifecycle. Below is a mixin handler that calculates and
> injects a UUID property in a referenceable object:
>
> class ReferenceableMixinHandler implements MixinHandler<ReferenceableMixin> {
>
> @Override
> public Class<ReferenceableMixin> getMixinType() {
> return ReferenceableMixin.class;
> }
>
> @Override
> public void setupListeners(LifecycleCallbackRegistry registry,
> Class<? extends DataObject> entityType) {
>
> registry.addListener(LifecycleEvent.POST_PERSIST, entityType,
> this,
> "initUuidCallback");
> registry.addListener(LifecycleEvent.POST_LOAD, entityType, this,
> "initUuidCallback");
> }
>
> void initUuidCallback(DataObject object) {
> int id = DataObjectUtils.intPKForObject(object);
> String uuid = object.getObjectId().getEntityName() + ":" + id;
> object.writePropertyDirectly(Referenceable.UUID_PROPERTY, uuid);
> }
> }
>
> Of course any DataObject can store any transient property, so we are taking
> advantage of that. Similarly AuditableMixinHandler is used to create
> ContentVersion records when an object changes. I am still exploring various
> uses of mixins and ways to extract common handler patterns in cayenne-mixin.
> A few early observations:
>
> * Mixins provide a better way to organize and understand listeners. From
> experience, ad-hoc mapping of listeners quickly results in a mess - each
> listener maps to more than 1 entity and more than 1 type of events. Very hard
> to remember and manage that stuff. I'd rather not think about event types at
> all, and just think that e.g. a listener manages 'uuid' property for a set of
> referenceable objects.
>
> * Mixins may be a better way for inheritance mapping. I am still to explore
> this idea, but I suspect in many cases mixins may be more scaleable than e.g.
> vertical inheritance. Besides of course they allow for many "superclasses" at
> once.
>
> * Mixins may provide a facility for custom relationship faulting. E.g. an
> injected property can be a lazy collection backed by a custom query (maybe
> try overriding standard Cayenne relationships this way?)
>
> * There are some limitations, most obviously all mixin properties have to be
> accessed via DataObject.readProperty(..) which may not fly well with
> scripting (e.g. "article.uuid"). This can be solved by an optional mixin
> interface and manual cover methods. Not ideal, but works:
>
> public class Article extends _Article implements Referenceable {
>
> @Override
> public String getUuid() {
> return (String) readProperty(Referenceable.UUID_PROPERTY);
> }
> }
>
>
> Anyways, this is something to explore. Mixins solve a whole class of design
> problems for me, and I am sure we can find other uses.
>
> Cheers,
> Andrus
>
>
>