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



Reply via email to