Closed urbandroid closed 1 week ago
Domain Driven Design and Hexagonal architecture
These are not real things; they have no basis in math, nor in empirical science.
When hand-wavy "patterns" or "best practices" come into conflict with writing clear, DRY, redundancy-free, readable code, it's the patterns and best practices which must yield. Because code is actually real stuff which can be evaluated via relatively objective metrics.
Wouldn't be great if we annotated our model classes with custom
@Model
annotation then tell JPA work with this annotation in single line?
No, it would not. We now have literally decades of practical experience telling us that the right place for object/relational mappings is on the entity classes.
When JPA 1.0 was first proposed, there was indeed an active debate in the community around the appropriateness of the use of annotations on the entity class to indicate persistence semantics and object/relational mappings. Full disclosure: I was strongly on the side of "annotations on the entity class". This debate is long, long over. The success of the annotation-based model is now entirely clear.
There's no way we should be messing with such a successful aspect of the most successful persistence API which has ever existed.
Sorry.
Hello @gavinking , how about giving people another choice so that they can make their own decisions. Right now working with said architectures and using JPA is a pain point. Is it something hard to implement or we are categorically against it?
- Create duplicate entity classes in infra module like this:
Ideally, your domain would be different from your entity, so there is no "duplicate" class, but surely a sort of class-per-entity pattern which may or may not be an issue for you. I personally think it's totally fine to have a bunch of DTOs per entity.
Look at those ugly toDomain and toEntity methods.
It's your interpretation of the architectural constraints that force you to write these methods. If you're willing to model your domain as entities, you don't need this. You also have to ask yourself, what is the purpose of these constraints if all you're doing is to essentially duplicate code and map things 1:1. One of the main ideas of having a separate domain model that abstracts over the entity model is to gain freedom in both representations i.e. you can change one without necessarily having to change the other (unless necessary for new use cases ofc). But if you're not taking advantage of this freedom, maybe because you're building a simple CRUD app, then the architectural constraints will be a burden.
There are libraries out there which can help you to build a domain abstraction on top of an entity model, but that is IMO out of scope for JPA. I actually created a library which can be used for domain model abstractions that is called Blaze-Persistence Entity-Views. There are talks in the Jakarta Data community about adding "projections" which is the read aspect of the abstraction story. Blaze-Persistence Entity-Views goes one step further and also allows "writable projections". Maybe if there is enough interest in the community, this could emerge as separate specification. There is definitely interest in specifying the read aspect and potentially support this also on top of other non-JPA technologies.
I mean, we already said we're going to investigate the question of projections/DTOs/Entity Views in the context of the Jakarta Data spec. But I wouldn't say that this is a thing that JPA should address. JPA is already very big!
@beikov
Ideally, your domain would be different from your entity, so there is no "duplicate" class, but surely a sort of class-per-entity pattern which may or may not be an issue for you. I personally think it's totally fine to have a bunch of DTOs per entity.
you are right i shouldn't be bothered by this. And for simple CRUD apps xml does the job but having ability to tell JPA to pickup @Model
like annotations would still be great for this kind of apps.
We have no idea what a "@Model
-like" annotation is, nor what it does, because you haven't defined it.
It is a custom annotation provided by the client in model/domain module and in some way we tell JPA to scan this annotation and JPA treats these annotated classes as entities and if client wants extra configuration either implements 1-1 @EntityConfiguration annotated configuration classes or orm.xml files for the parts that derails from conventions.
Convention is simple and things already part of JPA like every field is column, named with field name, and conventions that doesn't exist like every Collection field is relationship if there is counterpart relationship in another class then its a Many to Many relationship, first field is always ID so on.
This way if client wants extra configuration that derails from convention can provide mapped @EntityConfiguration
class like @EntityConfiguration(model=MappedModel.class)
or something like that.
For example :
public class User {
private Long id;
private String name;
private String email;
// getters and setters
}
And then configuration like this
@EntityConfiguration(model=User.class)
public class UserEntityConfiguration {
@Column(unique = true)
private String email;
@Version
private Long version;
// getters and setters
}
And if there is no configuration class then JPA treats @Model
annotated class as if like this class exists:
@Entity
public class User {
@Id
private Long id;
private String name;
private String email;
// getters and setters
}
It is just a thought. There may be better ways to do it. This is what i can think of, i bet you guys do something lot better than this.
So:
@Model
is exactly like @Entity
, butI don't see how this improves my program. It violates the very most basic and most fundamental principle of software engineering, which is DRY. It makes the code harder to navigate, harder to understand, and harder to refactor.
Now, OK, sure, you're arguing that you can make case 2 somewhat less common via additional:
conventions that doesn't exist like every Collection field is relationship if there is counterpart relationship in another class then its a Many to Many relationship, first field is always ID so on.
The problem with this is that if we thought that defaulting such stuff was a good idea, then we would have already built such defaulting into JPA. But we don't think that. Instead, we decided, after much reflection, that some things should be explicit, and that defaulting them would cause problems.
Right now Domain Driven Design and Hexagonal architecture getting some traction they deserve and using JPA with these approaches is simply ugly here is the example to show it.
To comply with the architectural constraints in model module or domain module we shouldn't depend on infrastructure code that means bye, bye
@Entity
on model classes.We have 3 choices to proceed.
1. Create duplicate entity classes in infra module like this:
For User class in model module:
Create UserEntity class in infra module
and then user repository looks like this
Look at those ugly toDomain and toEntity methods.
2. Orm.xml files in infrastructure module
For the same user model class we have
An then add those 1990 style xml files to another xml file persistence.xml file like this:
An then for this option repository class looks cool and with it.
3. Custom annotation
First we create custom annotations in our model module for JPA to pick up later.
Then we annotate our model classes with these annotations.
And then we use JPA metamodel to pick up our annotated classes:
Look at this code eye bleeding.
In my opinion DDD and hexagonal architecture, onion architecture all have something in common no anemic domain models, keep your domain classes POJOs. This is the future and JPA needs to be with it.
So JPA needs to make a way to pickup simple POJO classes to stay cool.
My suggestion would be make a elegant way to pick up custom annotated classes a.k.a. option 3 improved. If it is possible I would prefer a JPA which works when i tell it which annotations to work with it in java code.
Wouldn't be great if we annotated our model classes with custom
@Model
annotation then tell JPA work with this annotation in single line?