Annotations in domain objects with JPA violates Database is a detail.

huangapple go评论68阅读模式
英文:

Annotations in domain objects with JPA violates Database is a detail

问题

The discussion of whether to split Persistence Models from Domain Models is a common one in software development. In most cases, it's advisable to separate them to adhere to clean architecture principles, maintain a clear separation of concerns, and avoid anemic domain models. However, the decision to split or not should consider your specific project requirements and constraints.

在软件开发中,讨论是否将持久性模型与领域模型分离是一个常见的话题。在大多数情况下,建议将它们分开,以遵循清晰的架构原则,保持关注点的清晰分离,并避免贫血领域模型。然而,是否要分离或不分离应考虑您具体项目的需求和约束条件。

If you choose to split them, you can map between Domain and Persistence Models using methods like mapToEntity(DomainModel DM) and mapToDomain(PersistenceModel PM) as you've shown in your code. This approach allows you to maintain a rich Domain Model with business behavior while interacting with the database through the Persistence Model.

如果您选择分开它们,您可以使用像您在代码中所示的mapToEntity(DomainModel DM)mapToDomain(PersistenceModel PM)等方法在领域模型和持久模型之间进行映射。这种方法允许您在通过持久模型与数据库交互时保持丰富的领域模型和业务行为。

Ultimately, the decision to split or not should align with your project's goals, complexity, and team's expertise. There's no one-size-fits-all answer, and it's essential to make the choice that best suits your specific context.

英文:

What do you think about have persistence models and domain models separated? I've read that you don't have to mix persistence concerns with your business concerns (DDD, Clean Architecture,MartinFowler, Eric Evans and a lot and a lot of much more). Even so, I still see in all projects domain models annotated directly with ORM annotations like that, being the domain model coupled to the persistence mechanism reaching an anemic model and violating other principles.

//PersistenceJPA entity
@Entity
@Table(name="training_cycle")
class TrainingCycle {
    @Id
    private Long id;
    private String name;

    @Column(name="discipline_id")
    @JoinColumn(name="discipline_id")
    private Long disciplineId; //Referencing by Id because Discipline is another Aggregate ROOT

    //EmptyConstructor, getters and setters avoided
}

//PersistenceJPA entity
@Entity
@Table(name="training_cycle")
class Discipline {
    @Id
    private Long disciplineId;
    private String name;
    //EmptyConstructor, getters and setters avoided
} 

So if you want to follow clean principles you need to split Domain and Persistence Model(as shown below)to have Domain model with business behavior(this avoid anemic model and follows the SRP) and because of that you need to map the domain model to the persistence model (with typical methods like mapToEntity(DomainModel DM) and mapToDomain(PersistenceModel PM) maybe in a mapper/tranformer maybe in the repository class) when you want to interact with Datastore and viceversa when you want to retrieve data from database.

class Discipline {
    private DisciplineId disciplineId;
    private String name;

    public Discipline(DisciplineId disciplineId, String name) {
        this.disciplineId = disciplineId;
        this.name = name
    }
}

public class TrainingCycle{
    private TrainingCycleId trainingCycleId;
    private String name;
    private DisciplineId disciplineId;

    public TrainingCycle(TrainingCyleId trainingCycleId, String name, DisciplineId disciplineId) {
        this.trainingCycleId = trainingCycleId;
        this.name = name;
        assignDiscipline(disciplineId);
    }

    public void assignDiscipline(DisciplineId aDisicplineId) {
        if(aDisicplineId == null) {
            throw new IllegalArgumenException("Discipline cannot be null")
        }
        this.disciplineId = aDisicplineId;
    }
}


@Entity
@Table(name="training_cycle")
class TrainingCycleJpa {
    @Id
    private Long id;
    private String name;

    @Column(name="discipline_id")
    @JoinColumn(name="discipline_id")
    private Long disciplineId; //Referencing by Id because Discipline is another Aggregate ROOT

    //EmptyConstructor, getters and setters avoided
}


@Entity
@Table(name="training_cycle")
class DisciplineJpa {

    @Id
    private Long disciplineId;
    private String name;
   //EmptyConstructor, getters and setters avoided
}

class TrainingCyleJpaRepository implements TrainigCycleRepository {

    public void create(TrainingCycle trainingCycle) {
        entityManager.persist(this.mapToEntity(trainingCycle)
    }

    public TrainingCycle create(TrainingCycleId trainingCycleId) {
        return this.mapToDomain(entityManager.find(TrainingCycleId));
    }
}

So the discussion/question is Split or not Split Persistence Model from Domain Model? When to split or when not? In the most projects, not to say in all the ones I've seen as an example, I've seen they couple annotations of persistence model in Domain Model when "gurus are allways hawking" DataStore is a detail.

Thanks a lot.

答案1

得分: 3

请查看这个非常相似的问题持久性注释在域对象中是一种不好的做法吗?

我认为作为工程师,我们应该是实用主义者。任何最佳实践、原则或“大师建议”都应该有所帮助,而不应该使事情变得更糟。所以我建议把它们当作指导而不是严格的规则。例如,我一般同意“数据库是一个细节”。但我们很少改变这个细节。

另一方面,注释不执行任何代码。耦合性也不那么糟糕。你的域对象可以同时是JPA实体,这将非常清晰和有用。顺便说一句,这不违反单一责任原则(SPR)。如果你认为它违反了,可以查看Uncle Bob撰写的SOLID解释


编辑

> @RenéLink 我不同意:注释是被解释的,执行它们的代码可以用不同的解释器进行交换,即使使用一个空操作解释器。注释保持不变,不关心它们。它是一个声明性的元素,仅此而已。

注释是源代码依赖项,这意味着如果编译类路径上没有注释,源代码将无法编译。

没错,注释不是硬运行时依赖关系。如果类路径上不可用注释类,注释也不可用。因此,即使注释在类路径上不可用,你仍然可以使用带有注释编译的字节码。

因此,注释是比普通类、接口等依赖关系更少限制性的依赖关系。

我总是尝试最小化我的代码中的依赖关系。让我们以Spring为例。

在Spring中,你可以要么实现InitializingBean,要么定义一个带有@PostConstruct注释的方法,在这种情况下,我会使用后置构造。通常情况下,我不需要@PostConstruct,因为我进行构造函数注入。但是,如果我将初始化移动到Java配置中,我可以在返回bean实例之前调用任意的“后置构造”方法,而根本不需要@PostConstruct注释。

我同意注释依赖关系比其他类或接口问题要少。但要记住,OP谈论的注释也有另一个问题。如果你将域对象与JPA注释混合在一起,就会违反单一责任原则。你的域对象现在有了多个改变的原因(领域变化和持久性变化)。

当你添加@Transient注释或因为你改变了领域逻辑而与同事持久性的事情发生合并冲突时,你将会意识到这个问题。

英文:

Please check out this very similar question: Are persistence annotations in domain objects a bad practice?

I think that as engineers we should be pragmatic. Any best practices, principles, or "guru suggestions" should help. They should not make things worse. So I suggest to treat them as guidance, not strict rules. For instance, I generally agree that "database is a detail". But we rarely change that detail.

On the other hand, annotations do not execute any code. And the coupling is not so bad. Your domain object can be a JPA entity at the same time, and it will be very clean and useful. By the way, this does not violate the Single Responsibility Principle (SPR). If you thought it does, check out SOLID explanation by its author Uncle Bob


EDIT

> @RenéLink I don't agree: Annotations are interpreted and the code that executes them can be exchanged with a different interpreter even with a no-op interpreter. The annotation stays untouched and doesn't care. It's a declarative element, nothing more.

Annotations are source code dependencies, which means that the source code can not be compiled if the annotation is not present on the compile classpath.

It's right that annotations are not hard runtime dependencies. If the annotation class is not available on the classpath the annotation is also not available. Thus you can use bytecode that was compiled with annotations even if the annotations are not present on the classpath.

So annotations are a less restrictive dependency then normal classes, interfaces and so on.

I always try to minimize the dependencies in my code. Let's take a spring example.

In spring you can either implement InitializingBean or you can define a method annotated with @PostConstruct in this case I would use post construct. Often I don't need a @PostConstruct, because I do constructor injection. But if I move the initialization to a java config I can just call an arbitrary 'post construct' method befor returning the bean instance and I don't need a @PostConstruct annotation at all.

I agree that annotation dependencies are less problematic than other classes or interfaces. But keep in mind that the annotations the OP talks about also have another problem. If you mix domain objects with JPA annotations you violate the single responsibility principle. Your domain objects now have more then one reason to change (domain changes and persistence changes).

You will recognize the problem as soon as you add @Transient annotations or you might get merge conflicts because you changed domain logic and a colleague persistence things.

答案2

得分: 2

是的,这些注释是细节,应该与干净架构的实体分开。

不要混淆干净架构中的名称实体和持久性框架中的@Entity注释。它们是不同的东西。

Uncle Bob有一个关于干净架构的视频,在视频末尾,他明确指出:

实体不是数据库表的实例。它们通常是从许多数据库表构建而来的。

另一个视频中,他谈到了依赖注入和这些框架使用的注释。不过,依赖注入与您所询问的持久性无关,但这个视频明确了Uncle Bob对干净架构的用例或实体层中的框架注释的看法。

而在这个视频中,他明确指出实体不应该包含持久性细节。无论是Hibernate还是JPA,都是如此。

英文:

Yes, these annotations are details and should be kept away from the entities of the clean architecture.

Don't be confused about the name entity in the clean architecture and the @Entity annotation from your persistence framework. They are different things.

There is a video of Uncle Bob about clean architecture and at the end he makes it really clear, because he says:

> the entities are not instances of database tables. They are usually constructions from many database tables.

In another video he talks about dependency injection and the annotations that these frameworks use. Well, dependency injection has nothing to do with persistence that you asked for, but this video makes clear what Uncle Bob thinks about framework annotations in the use case or entities layer of the clean architechture.

And in this video he makes it really clear that the entities should not have persistence details. It doesn't matter if it is hibernate or JPA.

huangapple
  • 本文由 发表于 2020年8月13日 18:42:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/63393388.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定