스프링 부트:JPA/Hibernate 주석에서 DDD 엔티티를 깨끗하게 유지하는 방법은 무엇입니까?
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;
}
보시다시피 JPA/Hibernate는 엔티티 클래스에 대한 주석에 크게 의존하기 때문에 현재 도메인 엔티티 클래스는 지속성 인식 주석에 의해 오염되었습니다.이는 DDD 원칙 및 계층 분리를 위반합니다.또한 이벤트와 같은 ORM과 관련이 없는 속성에 대한 문제도 제공합니다.@Transient를 사용하면 이벤트 목록이 초기화되지 않으며 수동으로 이 작업을 수행하거나 이상한 오류가 발생합니다.
도메인 엔티티가 POJO(또는 Kotlin을 사용하는 POKO)이기를 원하기 때문에 엔티티 클래스에 이러한 주석을 달지 않습니다.하지만 저는 XML 구성을 사용하고 싶지 않습니다. 이는 끔찍한 일이며 Spring 개발자들이 애초에 주석으로 이동한 이유입니다.
제가 사용할 수 있는 옵션은 무엇입니까?이러한 주석을 포함하는 DTO 클래스와 각 DTO를 해당 도메인 엔티티로 변환하는 Mapper 클래스를 정의해야 합니까?이것이 좋은 관행입니까?
편집: C#에서 Entity Framework가 구성 클래스로 Entity 클래스 외부에 매핑 클래스를 만들 수 있다는 것을 알고 있습니다. 이는 XML 지옥보다 훨씬 나은 대안입니다.JVM 세계에서 그러한 기술이 사용 가능한지 아닌지 확신합니다. 아래 코드가 스프링으로 수행될 수 있는지 여부를 아는 사람이 있습니까?
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)
}
이 문제에 대한 해결책은 지속성 계층에서 내 클래스에 의해 구현되는 추상 도메인 엔티티(최대 절전 모드 엔티티 또는 최대 절전 모드 엔티티가 아닐 수 있음)를 갖는 것입니다.그런 식으로 도메인 클래스는 지속성 메커니즘에 대해 아무것도 모르고, 지속성 클래스는 비즈니스 로직에 대해 아무것도 모르고, 저는 대부분 매핑 코드를 피합니다.이에 대해 자세히 설명하겠습니다.
다음과 같은 프로젝트를 상상해 보십시오(이것이 제가 프로젝트를 구성하는 방식입니다).
-
|-business_logic
| |-person
| | |-Person.java
| | |-Item.java //assuming "item" is inside the Person aggregate
| | |-FullName.java // Let's make FullName a Value Object.
| | |-DoXWithPersonApplicationService.java
| |-aggregateB
| |-aggregateC
|
|-framework
| |-controllers
| |-repositories
| |-models
| | |-JpaPerson.java
| | |-JpaItem.java
| | |-etc.
그러면 사용자 클래스는 다음과 같이 보일 수 있습니다.
public abstract class Person {
public abstract int getId();
public abstract FullName getName();
protected abstract void setName(FullName name);
public abstract ImmutableSet<Item> getItems(); // Say you're using Guava
protected abstract void addItem(String itemName, int qtd);
protected abstract void removeItem(Item item);
void doBusinessStuff(String businessArgs) {
// Run complex domain logic to do business stuff.
// Uses own getters and setters.
}
}
FullName 클래스는 다음과 같습니다.
public final class FullName {
private final String firstName;
private final String lastName;
// Constructors, factories, getters...
}
마지막으로 JpaPerson 클래스는 다음과 같습니다.
@Entity
@Table(name = "mydomain_persons")
public class JpaPerson extends Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name="firstName")
private String firstName;
@Column(name="lastName")
private String lastName;
@OneToMany(cascade=ALL, mappedBy="item")
private Set<Item> items;
@Override
public int getId() { return id; }
@Override
public FullName getName() { return FullName.of(firstName, lastName); }
@Override
protected void setName(FullName name) {
firstName = name.getFirst();
lastName = name.getLast();
}
// Implementations for the rest of the abstract methods...
// Notice the complete absence of "business stuff" around here.
}
주의해야 할 몇 가지 사항:
- 엔티티 상태를 수정하는 모든 것은
protected
하지만 게터는 그럴 수 있습니다.public
(아니면).따라서 필요한 데이터를 얻기 위해 Aggregate 간의 관계를 이동하는 것이 실제로 상당히 안전합니다(엔티티는 패키지 외부의 Value Object와 동일). - 위와 같은 이유로 Aggregate의 상태를 수정하는 애플리케이션 서비스는 Aggregate와 동일한 패키지 내에 있어야 합니다.
- 저장소에서 약간의 캐스팅 작업을 수행해야 할 수도 있지만, 상당히 안전할 것입니다.
- 집계 경계에 걸친 모든 상태 변경은 도메인 이벤트에서 수행됩니다.
- FK를 설정하는 방법에 따라 여러 Aggregate에서 실행되는 사전 삭제 도메인 로직이 있는 경우 데이터베이스에서 엔티티를 삭제하는 것이 다소 까다로울 수 있지만, 어쨌든 이 작업을 수행하기 전에 두 번 생각해야 합니다.
바로 그겁니다.어떤 종류의 은색 총알도 아닐 테지만, 이 무늬는 지금까지 저에게 도움이 되었습니다.
해결책이 없는 것은 몇 가지 이유로 좋은 일일 수 있습니다.일반적으로 도메인 구조와 지속성 전략이 분리되는 것은 제가 보기에 꽤 정상적입니다.도메인 모델을 설계하는 방법과 관련하여 독립적인 방법으로 일부 지속성 패턴을 적용할 수 있습니다.당신은 위에서 아래로 디자인하면서 레거시 테이블을 다루는 것에 신경 쓰지 않으며, 당신은 도메인 엔티티와 상당히 다른 jpa 엔티티를 가질 수 있습니다.그게 뭐가 문제죠?따라서 FP와 같은 접근 방식으로 레포에 도메인/jpa 엔티티 매핑을 계속 구현하여 볼러플레이트 작업을 줄이고 DAO 호출에 대한 부작용을 배제하기 때문에 문제가 되지 않습니다.
언급URL : https://stackoverflow.com/questions/58018542/spring-boot-how-to-keep-ddd-entities-clean-from-jpa-hibernate-annotations
'programing' 카테고리의 다른 글
테이블의 행 크기 결정 (0) | 2023.07.01 |
---|---|
SQLPLUS 오류:ORA-12504: TNS: 수신기에 CONNECT_DATA의 SERVICE_NAME이 지정되지 않았습니다. (0) | 2023.07.01 |
회전된 xtick 라벨과 해당 xtick 라벨 정렬 (0) | 2023.07.01 |
Oracle에서 Dapper QueryMultiple 사용 (0) | 2023.07.01 |
Mac에서 VBA(Excel) 사전? (0) | 2023.07.01 |