The official documentation is well written and full of examples on how to build relations, compose DCs, whether to persist a certain field or not, and so on.
What I would like to cover are some particular cases. I'm trying to write it as complete as possible, so if you got any direct experience on this remember that any feedback is welcome.
I want to point out the attention on how to deal with inheritance problems in DCs: being 100% Groovy/Java classes they are eligible to use the same design pattern solutions and best practices, but they have constraints connected to hibernate/GORM philosophy as well.
As a small recap please read the following snippet of code, it’s full of hints and considerations taken from Grails Documentation:
class FlyingObject { Integer flyingTimeMin = 0 Integer dummyProperty //all property names listed in transients will not be persisted static transients = ['dummyProperty'] //when you extend an other class with FlyingObject this constraints //will be shared static constraints = { flyingTimeMin nullable: false, min: 0 } static mapping = { // if false 2 different table will be created // if true only one table will be created with a 'class' column //used by hibernate to cast the record to the correct // domain class tablePerHierarchy false } def doFly(){ log.info "I'm flying!" } } class Concorde extends FlyingObject{ Integer currentPassengerNb //static properties are not persisted static int maxSpeed = 2172 //if you need a field editable at runtime but not interested in persisting it //declare it as private and create getter+setter Object getPublicId() { return id + "-public" } void setPublicId(Object id) { this.id = id } private Object publicId static constraints = { currentPassengerNb nullable: false, min: 0, max: 200 } }
I've stored both classes in the grails-app/domain folder: in this way Grails will create 2 different tables in the DB (depending on what value you choose for tablePerHierarchy value in static mapping closure).
If you store the FlyingObject class in the src/groovy folder Grails will create only the table for Concorde object with all fields inherited by FlyingObject class.
Till now, nothing new.
In Grails Documentation you can read these considerations (all valid):
At the database level Grails by default uses table-per-hierarchy mapping with A discriminator column called class so the parent class (Content) and its subclasses (BlogEntry, Book etc.), share the same table. Table-per-hierarchy mapping has a down side in that you cannot have non-nullable properties with inheritance mapping. An alternative is to use table-per-subclass which can be enabled with the ORM DSL However, excessive use of inheritance and table-per-subclass can result in poor query performance due to the use of outer join queries. In general our advice is if you're going to use inheritance, don't abuse it and don't make your inheritance hierarchy too deep.
Beside the performance problem, I think there is another thing that deserves some consideration. It is connected to how Java approaches inheritance concept. In Java multiple inheritance doesn't exist; but when you need it (or when you wrongly think you need it) all attempts to obtain it will lead to low flexibility and to a lot of well known implementation errors that have to be solved with the right design pattern.
This kind of problems are well documented and discussed around the web and they are known as "Diamond problem of multiple inheritance".
Here you can find some materials on this:
- http://berniesumption.com/software/inheritance-is-evil-and-must-be-destroyed/
- http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- http://javapapers.com/core-java/why-multiple-inheritance-is-not-supported-in-java/
Let's try to be more clear, imagine now that you must add in your model also a class for Eagle:
class Eagle extends FlyingObject { static constraints = { } def doFly(){ return super.doFly() + " like an Eagle!" } }
BUT an eagle has no seats! This is kind of rigidity in using inheritance to model a class that I was talking about. Surely you could refactor the FlyingObject class and add a Vehicle class to make sense. Let's give a try:
class Concorde extends Vehicle{ Integer currentPassengerNb ... def doFly(){ return super.doFly() + " like an Airplane!" } } public class Vehicle extends FlyingObject { Integer seatsNb //when you extends an other class with FlyingObject this constraints //will be shared static constraints = { seatsNb nullable: false, min: 1 } } class FlyingObject { Integer flyingTimeMin = 0 //when you extends an other class with FlyingObject this constraints // will be shared static constraints = { flyingTimeMin nullable: false, min: 0 } static mapping = { ... } def doFly(){ return "I'm flying " } } class Eagle extends FlyingObject { ... def doFly(){ return super.doFly() + " like an Eagle!" } }
It might work this way, but imagine that now you have to model a Car: it is a vehicle so by logic we should extend Vehicle class and inherit its behavior and properties; but right now Vehicle extends FlyingObject class and at the same time you must agree that a car cannot fly! You can keep refactoring all classes when your data model evolves, but at a certain point, when complexity is medium/high, be sure that by using only inheritance you will end up in a dead-end and you will not find any sensible way of using it to model your domain classes. It is not even guarantied that you will be able to refactor your classes: what if you are extending DCs from a plugin (inline or not) that you cannot refactor? This is a typical problem going on with inheritance: it is extremely easy to use in the wrong way.
As a golden rule we can say that inheritance must be used in all those cases where a class like Concorde wants to expose the complete interface (at least all public methods) of FlyingObject so that Concorde could be used where FlyingObject is expected.
In our example above in the Eagle class you only want a part of the behavior exposed by FlyingObject as it was in the beginning, and in the Car class you want only a part of the behavior of Vehicle class, not everything inherited by the FlyingObject class as it is in the last refactoring.
In the final situation you need to use composition: split the aspects or abilities that distinguish a behavior in different classes and use them as properties in your desired class.
Grails allows you to use composition quite smoothly in domain classes: just fill the static embedded property with the name of properties that concur in composition.
Let's see how our model evolves with composition:
class Car extends Vehicle { String brand ... } class Concorde extends Vehicle{ FlyingObject flyingObject static embedded = ['flyingObject'] .. def doFly(){ return flyingObject.doFly() + " like an Airplane!" } } class Eagle { FlyingObject flyingObject static embedded = ['flyingObject'] def doFly(){ return flyingObject.doFly() + " like an eagle!" } } public class Vehicle { Integer seatsNb ... static mapping = { tablePerHierarchy false } }
Now everything looks much more elegant, doesn’t it? We are using composition we needed and inheritance when it make sense.
Cool. Now it is time to remind ourselves that Groovy is Java on steroids, so let's spice everything up :)
With Grovvy we can use an annotation (used to annotate a single property) that is what we need to make everything to work better, like:
class Eagle { @Delegate FlyingObject flyingObject static embedded = ['flyingObject'] def doFlyLikeAnEagle(){ return doFly() + " like an eagle!" } }
It literally it means: "when someone asks an Eagle instance for methods or properties, check if the flying property has them before throwing an exception"
As you can see I'm using the doFly() method inside doFlyLikeAnEagle() without any reference to the flyingObject instance. It would be the same with an eagle istance, e.g.:
eagle.flyingTimeMin = 100
Bingo: this way we inherit all properties and methods from FlyingObject as if we were extending it. BUT we are using composition, so we can have the freedom to choose how to compose our Eagle or Car or whatever without going mad piling the extension in a sensible way.
In the above examples I used mostly methods in order to explain inheritance and composition, but it's time to remember that we are talking about DCs, so now our main concern should be about the properties that at the end are going to be persisted.
So as I did in the initial recap, here you find a final recap on how to use @Delegate
class Car { String brand // importing a DC as delegate allows you to use its properties and methods // in the current one. just remember to instantiate them with new key @Delegate Vehicle vehicle = new Vehicle() // we must point out that we are not interested in saving the vehicle // instance itself static transients = ['vehicle'] // we must indicate to GORM to use the properties list from vehicle // to create/manipulate Car instances static embedded = ['vehicle'] // the current class must be aware of the constraint coming from Vehicle DC static constraints = { importFrom(Vehicle, include: ['seatsNb', 'vehicleName']) brand nullable: false, maxSize: 100 } } class Concorde extends Vehicle{ Integer currentPassengerNb // importing a DC as delegate allow you to use its properties and method in // the current one. just remember to instantiate them with new key @Delegate FlyingObject flyingObject = new FlyingObject() // we must point out that we are not interested in saving the vehicle instance // itself. here all properties imported from @Delegate // classes that we don't want to persist must be listed static transients = ['flyingObject', 'dummyProperty'] static embedded = ['flyingObject'] //static properties are not persisted static int maxSpeed = 2172 // the current class must be aware of the constraint coming from FlyingObject DC. // in this DC vehicle is normal object that can be nullable or not static constraints = { importFrom(FlyingObject, include: ['flyingTimeMin']) currentPassengerNb nullable: false, min: 0, max: 200 } // mixed usage of both native properties and imported with @Delegate and as usual def printProperties() { vehicleName + " now flying from " + flyingTimeMin + "minutes, with " + currentPassengerNb + " occupied seats on " + seatsNb } def doFlyLikeAnAirplane() { return doFly() + " like an Airplane!" } } class Eagle { // importing a DC as delegate allow you to use its properties and method in // the current one. just remember to instantiate them with new key @Delegate FlyingObject flyingObject = new FlyingObject() static embedded = ['flyingObject'] static transients = ['flyingObject', 'dummyProperty'] //when you extends an other class with FlyingObject this constraints will be shared static constraints = { importFrom(FlyingObject, include: ['flyingTimeMin']) } def doFlyLikeAnEagle() { return doFly() + " like an eagle!" } } public class Vehicle { // in this example app Vehicle is used both to define a regular DC, a composition // (in Car DC) and inheritance (in Concorde DC). // don't mix the meaning of this different usage! Integer seatsNb String vehicleName //when you extends an other class with FlyingObject this constraints will be shared static constraints = { seatsNb nullable: false, min: 1 vehicleName nullable: false, maxSize: 100 } static mapping = { // if false 2 different table will be created // if true only one table will be created with a 'class' column //used by hibernate to cast the record to the right // domain class tablePerHierarchy false } } class FlyingObject { Integer flyingTimeMin = 0 Integer dummyProperty //when you extends an other class with FlyingObject this constraints will be shared static constraints = { flyingTimeMin nullable: false, min: 0 } def doFly() { return "I'm flying " } }
Comments should be enough to understand what is going on, if not please feel free to ask in the blog's comments.
Note 1:
As in pure Groovy class we could also use @Mixing annotations to compose classes, at the moment (grails 2.3.4 at January 2014) there are a couple of bugs in Groovy and Grails that are preventing to use this annotation in the correct way. The most elegant way for now is use @Delegate annotation.
Note 2:
I admit that when you use @Delegate in DCs you have to insert the instance in the transient list, as well the properties from delegated class that are transient there and going to be transient in the DC that uses it. Is not really elegant. Also in the mapping you have to specify which properties to import and from where.
This is due to the fact that @Delegate annotation is from Groovy, not from Grails. So it cannot be aware about all GORM-related issues.
I really hope that guys from Grails team come up early with a Grails version of this annotation that takes into consideration all this facts.
Note 3:
here you can find a github project with all the final code and the various steps:
https://github.com/tamershahin/Grails-Inheritance-tests