SpringBoot:如何保持DDD实体不受JPA/Hibernate注释的影响?

SpringBoot:如何保持DDD实体不受JPA/Hibernate注释的影响?,spring,hibernate,spring-boot,orm,domain-driven-design,Spring,Hibernate,Spring Boot,Orm,Domain Driven Design,我正在编写一个我希望遵循DDD模式的应用程序,一个典型的实体类如下所示: @Entity @Table(name = "mydomain_persons") class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(name="fullname") private String fullName; @OneToMany

我正在编写一个我希望遵循DDD模式的应用程序,一个典型的实体类如下所示:

@Entity
@Table(name = "mydomain_persons")
class Person { 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name="fullname") 
    private String fullName;

    @OneToMany(cascade=ALL, mappedBy="item")
    private Set<Item> items;
}
@实体
@表(name=“mydomain\u人员”)
类人{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私有int-id;
@列(name=“fullname”)
私有字符串全名;
@OneToMany(cascade=ALL,mappedBy=“item”)
私人设置项目;
}
如您所见,由于JPA/Hibernate严重依赖实体类上的注释,我的域实体类现在被持久性感知注释所污染。这违反了DDD原则以及层的分离。它也给我带来了与ORM无关的属性问题,比如事件。如果我使用@Transient,它将不会初始化事件列表,我必须手动执行此操作,否则会出现奇怪的错误

我希望域实体是POJO(或我使用Kotlin时的POKO),所以我不希望在实体类上有这样的注释。然而,我绝对不希望使用XML配置,这是一种恐怖,也是为什么Spring开发人员首先转向注释的原因

我有哪些选择?我应该定义一个包含此类注释的DTO类和一个将每个DTO转换为相应域实体的映射器类吗?这是一种好的做法吗

编辑: 我知道在C#中,实体框架允许在实体类之外创建带有配置类的映射类,这是一种比XMLHell更好的替代方法。我不确定这种技术在JVM世界中是否可用,有人知道下面的代码是否可以用Spring完成吗

public class PersonDbContext: DbContext 
{
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    //Write Fluent API configurations here

    //Property Configurations
    modelBuilder.Entity<Person>().Property(p => p.id).HasColumnName("id").IsRequired();
    modelBuilder.Entity<Person>().Property(p => p.name).hasColumnName("fullname").IsRequired();
    modelBuilder.Entity<Person>().HasMany<Item>(p => p.items).WithOne(i => i.owner).HasForeignKey(i => i.ownerid)
}
public类PersonDbContext:DbContext
{
公共数据库集人物{get;set;}
模型创建时受保护的覆盖无效(ModelBuilder ModelBuilder)
{
//在这里编写流畅的API配置
//属性配置
modelBuilder.Entity().Property(p=>p.id).HasColumnName(“id”).IsRequired();
modelBuilder.Entity().Property(p=>p.name).hasColumnName(“全名”).IsRequired();
modelBuilder.Entity().HasMany(p=>p.items)。with one(i=>i.owner)。HasForeignKey(i=>i.ownerid)
}

由于几个原因,缺少解决方案可能是件好事。 通常,在我看来,域结构和持久性策略是解耦的,这是非常明智的。 您可能希望以独立的方式应用somme persistence模式来设计域模型。 在自上而下进行设计时,您不关心如何处理遗留表,而且您可能拥有与域实体截然不同的jpa实体。这有什么问题?
因此,这不是一个问题,因为您一直在使用类似FP的方法在repo中实现域/jpa实体映射,减少了bolerplate,并将DAO调用的副作用放在一边。

我发现解决这个问题的方法是使用抽象域实体,由我的类在持久层实现(可能是也可能不是Hibernate实体本身)。这样,我的域类对持久性机制一无所知,我的持久性类对业务逻辑一无所知,我基本上避免映射代码。让我进一步说明:

想象一个这样布置的项目(这几乎就是我组织项目的方式):

那么您的Person类可能会如下所示:

@Entity
@Table(name = "mydomain_persons")
class Person { 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name="fullname") 
    private String fullName;

    @OneToMany(cascade=ALL, mappedBy="item")
    private Set<Item> items;
}
公共抽象类人物{
公共抽象int getId();
公共抽象全名getName();
受保护的抽象无效集合名(全名);
公共抽象ImmutableSet getItems();//假设您正在使用番石榴
受保护的抽象void addItem(字符串itemName,int-qtd);
受保护的抽象无效删除项(项);
void doBusinessStuff(字符串businessArgs){
//运行复杂的域逻辑来处理业务。
//使用自己的getter和setter。
}
}
您的FullName类可能如下所示:

@Entity
@Table(name = "mydomain_persons")
class Person { 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name="fullname") 
    private String fullName;

    @OneToMany(cascade=ALL, mappedBy="item")
    private Set<Item> items;
}
公共最终类全名{
私有最终字符串名;
私有最终字符串lastName;
//建设者,工厂,获取者。。。
}
最后,您的JpaPerson类应该如下所示:

@实体
@表(name=“mydomain\u人员”)
公共类JpaPerson扩展了Person{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私有int-id;
@列(name=“firstName”)
私有字符串名;
@列(name=“lastName”)
私有字符串lastName;
@OneToMany(cascade=ALL,mappedBy=“item”)
私人设置项目;
@凌驾
public int getId(){return id;}
@凌驾
public FullName getName(){return FullName.of(firstName,lastName);}
@凌驾
受保护的无效集合名(全名){
firstName=name.getFirst();
lastName=name.getLast();
}
//其余抽象方法的实现。。。
//请注意,这里完全没有“业务人员”。
}
需要注意的几点:

  • 任何修改实体状态的东西都是受保护的,但getter可以是公共的(也可以不是)。这使得跨聚合遍历关系以获取所需数据实际上非常安全(实体看起来就像来自包外的值对象)
  • 由于上述原因,修改聚合状态的应用程序服务必须与聚合在同一个包中
  • 您的存储库可能需要执行一些强制转换,但应该非常安全
  • 所有跨越聚合边界的状态更改都是通过域事件完成的
  • 根据设置FK的方式,如果要在多个聚合中运行预删除域逻辑,则从数据库中删除实体可能会有点棘手,但无论如何,在这样做之前,您确实应该三思而后行
  • 就是这样。我肯定这不是什么灵丹妙药,但这种模式对我有一些好处