Here my current solution I came up with after some research. But please also post your solutions.
I went with my suggestion in the above edit. However only the affected methods in the DAO require these additional parameters (in my case 2 java.lang.Class Objects). DAO and Service Classes remain generic.
The trick however is to add a Constructor to your Service Class which takes those 2 Class Object Parameters as arguments. Then in the Spring configuration you need to create a service bean per class in the hierarchy you want to use. Example:
<bean id="myService1" autowire="byName"
class="MyServiceImpl">
<constructor-arg index="0">
<ref bean="CompoundClass"/>
</constructor-arg>
<constructor-arg index="1">
<ref bean="CompoundQueryClass"/>
</constructor-arg>
</bean>
<bean id="myService2" autowire="byName"
class="MyServiceImpl">
<constructor-arg index="0">
<ref bean="CompoundClass2"/>
</constructor-arg>
<constructor-arg index="1">
<ref bean="CompoundQueryClass2"/>
</constructor-arg>
</bean>
where the referenced beans are Class objects created like this:
<bean id="CompoundClass" class="java.lang.Class" factory-method="forName">
<constructor-arg value="full.qualified.class.name"/>
</bean>
While the affected methods are now a bit cluttered (many arguments) and passing around Class objects seems non-ideal to me (probably I'm wrong) a developer using the framework will not have to deal with the issue. He can create his extended entities and apply the correct configuration. I've created tests for his and the solution those seem to work as expected.
If CompoundClass2 extends CompoundClass (which uses @DiscriminatorColumn) and I call a query method from MyServiceImpl only CompoundClass2 objects are in the result even if a CompoundClass object would match the query too + the right type is returned. No casting needed.
Best Answer
Update: Rewrote code in Java since this question is tagged
java
but this applies to any object oriented language.If using inheritance is a means of reducing code duplication, I'm a little reluctant to have DTOs inherit from anything at all. Inheritance implies an is-a relationship between child class and the parent.
Using audit fields as an example, would you say the following statement is true?
It sounds kind of funny to me when I read it out loud. Inheritance in DTOs becomes problematic, because you introduce coupling between classes where each class usually represents a concrete idea or use case. DTOs sharing a common parent class implies the class hierarchy will evolve at the same time, and for the same reasons. This makes these classes harder to change without having a ripple effect cascading to classes where the new fields might not be applicable.
You'll notice this happen more often for DTOs that serialize and deserialize data from sources you have no control over, like a web service owned by another company or team.
That being said, I've found cases where processing a DTO can be abstracted away. Audit fields are a good example. In these cases I've found interfaces to be a much more robust solution than a concrete parent class, for the simple fact a DTO can inherit from multiple interfaces. Even then I keep the interface as tightly focused as I can and only define the minimal methods to retrieve or modify a small subset of fields:
Now if we go back to our is-a test:
The statement makes more sense. With the use of interfaces it becomes easier to utilize polymorphism in the classes that process DTOs. Now you are free to add and remove interfaces as you see fit to aid in processing the DTOs without affecting the serialization and deserialization to and from their source format. Because interfaces are being used you can modify the classes to reflect structural changes in the data, and have additional methods that make the DTO more palatable to the classes processing them.
Going back to audit fields, you have one web service call a property
creatorId
and another one calls itcreateUserId
. If both DTOs inherit from the same interface, then processing both of them becomes easier.Let's say you have a sales staff web service. By coincidence the property names in the XML response match the Auditable interface, so nothing extra is needed in this class:
The customer web service returns a JSON response and two fields are named differently than what is defined in the Auditable interface, so we add a couple of getters and setters to this DTO in order to make it compatible:
Sure there might be duplicated getters and setters, but behind the scenes they are working with the same fields so everyone is happy.