3

My Java project uses JPA as a persistence solution. As I read more and more JPA tutorials and guides I find (for the sake of simplicity, maybe) that every writer uses the entity classes as their model classes. That's obviously a poor design choice with regards to code extensibility and maintainability: a decision to change the persistence solution would imply a complete refactoring (or rewriting) of the whole model.

Thing is, rewriting model classes corresponding to each entity class feels out of place and inelegant, since the would all just ditto the corresponding entity class (in my personal case), were it not for the JPA annotations. Writing them off as interfaces implemented by the entity classes feels wrong as well, be it for the potential inconsistency arising when I might need to expand the model with a class that does not correspond to an entity, or for the fact that in Java the generics used to represent @OneToMany and @ManyToMany relationships are not (and should not be) covariant with the abstracting interfaces.

I shall now post some code to exemplify my concern:

package com.depvin.pps.orm;

import javax.persistence.*;


@Entity
@DiscriminatorValue("E")
public class EmployeeEntity extends UserEntity {
    @ManyToMany
    @JoinTable(name = "EmployeeProject",
            joinColumns = @JoinColumn(name = "employee"),
            inverseJoinColumns = @JoinColumn(name = "project"))
    List<ProjectEntity> projects;

    public EmployeeEntity(String username) {
        super(username);
    }

    public List<ProjectEntity> getProjects() {
        return projects;
    }
}

How should the corresponding model class/interface be realized, taking into account the matters I brought up above?

Michele De Pascalis
  • 932
  • 2
  • 9
  • 26
  • 2
    Why do you think that changing the persistence solution would require a "complete refactoring or rewrite" of the model class? I think one could use this class unchanged with another persistence technology, and removing the annotations hardly qualifies as complete refactoring or rewrite, does it? In particular because most of these annotations are optional, and only required if you want to override the default names of columns? – meriton Nov 28 '15 at 20:51
  • One proplem comes with flow control annotations like PrePersist PostPersist. A new persistence technology has to have at least the same assertions. I think this will be the case, but you clearly see, that code execution is bound to JPA. This shouldn't be a big deal but it shows a control flow dependency to JPA. So annotations are more than metadata to the class. They are also data to the JPA environment when to execute what. – oopexpert Dec 09 '15 at 18:42

2 Answers2

3

From my point of view it is obvious that it is a good design. An entity is a model class. It is (or should be) the manifestation of the business object which is part of the domain model.

You are right, you should not build your model for the persistence API. But I don't see in the example above where you do that. Except for the annotations, but those don't make up your model, its just metadata. You could even put that into an extra mapping file and would have straight model classes.

If you mean that you create your model classes from an existing database and the resulting classes do not express the business model, then you should think about your database model.

And you can at anytime change the persistence solution, I can't see why you need to stick to JPA here. It would be very easy, for example, to create a JSON from any entity class with just some annotations.

Last thing: JPA is only using the generic type of a collection, if you don't define the targetEntity attribute in the @OneToMany.

Tobias Liefke
  • 8,637
  • 2
  • 41
  • 58
1

The annotations of JPA-Objects are object-relational mapping, so JPA-Objects are part of the object-relational mapping.

You are right, that you often see JPA objects as model objects in project architecture. I don't say that this solution is sufficient bad in every situation. But if you want to be clear you should separate the two aspects of object relational mapping layer and business layer. If you have done this, you maybe have technical redundancy but no SEMANTICAL redundancy according to SRP. SRP says that one code fragment should only be change for one purpose. If the mapping changes, then the ORM layer is the right place. If the business rules change go to the model layer.

Of course you may have code to transfer data between the layers. A "String name" may appear in the JPA-Object and in the Model-Object. But its not a mapping at all, its caching. Caching makes the redundancy that makes you uncomfortable. If you don't want to cache, delegate from the model layer to the JPA-Layer. But keep Business logic separated from the JPA-Layer.

After all: if you have a small project you won't face any real problems with a JPAObject-only solution. It all comes with the size. And then I recommend the BusinessObject -> DAO -> JPAObject structure.

oopexpert
  • 767
  • 7
  • 12
  • I disagree with your analysis: The mapping is _not_ any object, it is the annotation (or the mapping file). If the business logic changes, you will change the model object. If the mapping changes, you will change the annotation. But usually one thing results in the other. If I've modeled a person with _one_ address and now I need more than one, I will change the property to a `Collection` and annotate it with `@OneToMany`. Even if you give a good example for your approach, I would call it _the exception from the rule_, as such are really rare. And I've seen many projects that had their _size_. – Tobias Liefke Dec 09 '15 at 09:03
  • The mapping is the annotation, I will give you that. But you cannot go away. If you create object-relational mapping, your objects are bound to restrictions from the used ORM-Framework. It will have a certain structure which may not be the same as the target domain object (but often is). You have to look at this: To every JPA mapping you are able to create a corresponding Class. But you are NOT able create a JPA mapping for every Class. Therefore for every Class you want to map that doesn't meet you domain model, you have to write extra code that should be in the DAO Layer, not in the domain. – oopexpert Dec 09 '15 at 16:31
  • I'm not going away ;-) And all I ask is an example where the domain model is reasonable and where we can't create a mapping? I don't need to create a mapping for _every_ class, I just need to create it for the business objects. And for nearly every relationship between these objects you'll find a corresponding mapping. And please add an example how you would load / store the "model objects" in your approach as well, as I've got the feeling that this makes things more complicated. I have to admit that the service layer binds us to JPA somehow, but that was not the question. – Tobias Liefke Dec 09 '15 at 16:53
  • Btw. I've just seen that the OP asked for a solution _while avoiding code duplication_, which conflicts with your answer. I'm wondering why he accepted it nevertheless. – Tobias Liefke Dec 09 '15 at 16:55
  • I answered the question: There is no code duplication according to SRP. It may be sufficient design in some cases but not an overall "good" design. I also mentioned the efford left away in most projects. And where they left it away they have to ask themselves where to put the extra code to straighten the JPA-Objects to the domain layer. "I don't need to create a mapping for every class." --> The question is: Is there at least one class, that cannot be mapped? The answer is yes: Technically you may have at least a chance. But if semantic comes along it becomes hard to impossible. – oopexpert Dec 09 '15 at 18:06
  • Just because of _one_ hypothetical class that can not be mapped, you want to introduce duplicate objects for _all_ model objects? I've got model objects in many of my project that are not mapped, but these are either transient or just a different representation of other model objects for a special use case (like model objects for wizard steps). And as I already said: If you change the model, you will have to change the mapping as well. So your approach is a violation of SRP, as you will always have to change two classes. You can still prove me wrong with proper examples.... – Tobias Liefke Dec 09 '15 at 20:38
  • Domain Model --> technology free representation of your problem domain with OO constraints and functionality, JPAObject --> simple Data Structure with ORM and maybe DB-Constraints, DAO --> Transforms JPAObjects into OO Objects of the problem domain and vice versa. Clear responsibilities. No violation of SRP. – oopexpert Dec 09 '15 at 21:44