You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cayenne.apache.org by Andrus Adamchik <an...@objectstyle.org> on 2010/11/02 00:27:20 UTC

Exploring DataObject mixins

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




Re: Exploring DataObject mixins

Posted by Mike Kienenberger <mk...@gmail.com>.
Just as a generic comment on the concept since we're doing something
similar in an in-house JPA metadata modeling language.

We have a couple of "hooks".

One is "mods" which when specified as an attribute of a table
(DBEntity), automatically inserts all of the columns defined in the
mod.   Ie, mods="record_level_logging" inserts columns for
last_created, last_modified, etc, into the table.

Another is "options" which when specified as an attribute of any
entity (table, column, etc) associates this user-defined value to that
object so that the code generator can check for it.  For example,
certain tables are marked to generate a certain pattern of JSF page
layout while other tables are marked to generate an alternate pattern
of JSF page layout.   And the "record_level_logging" columns above are
marked as "record_level_logging" so they can be treated differently on
page layouts than other columns (read-only, displayed in a separate
section, for example).

Both of these are at the model level rather than at the code level,
and don't explicitly exist past the code generation stage.

On Mon, Nov 1, 2010 at 7:27 PM, Andrus Adamchik <an...@objectstyle.org> 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
>
>
>
>

Re: Exploring DataObject mixins

Posted by Andrus Adamchik <an...@objectstyle.org>.
I sort of want to keep mixins out of the Modeler until the use patterns become more clear, so I kind of got stuck on how we can cgen the extra properties outside of it. I like the idea of annotated annotation, just wish we could use it in cgen somehow...

Another thought (a bit unrelated) ... per repeating discussions on the user list, many people (me too actually) would want to have a simple way to access an object diff when processing a lifecycle callback for this object. Cayenne has this information of course, and its a shame it is not easily accessible. Moreover sometimes the diff is needed AFTER the commit, when ObjectContext already disposed of it. So we may provide a mixin handler "context" containing such diff data. This is not even mixin-specific, I just feel reluctant to add another API layer to the core, and a mixin module may provide a sandbox for experimentation.

Andrus 


On Nov 1, 2010, at 7:49 PM, Robert Zeigler wrote:

> 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
>> 
>> 
>> 
> 
> 


Re: Exploring DataObject mixins

Posted by Robert Zeigler <ro...@roxanemy.com>.
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
> 
> 
>