주난v 개발 성장기

[DDD START!] 4장. 리포지터리와 모델 구현 본문

개발 성장기/DDD

[DDD START!] 4장. 리포지터리와 모델 구현

주난v 2020. 5. 19. 08:50

JPA를 이용한 리포지터리 구현

- 애그리거트를 어떤 저장소에 저장하느냐!

- RDBMS를 사용한다면 JPA를 들 수 있다.

- 도메인 모델과 관계형 데이터 모델 간의 매핑 처리 기술(ORM - Object Relation Mapping)

 

모듈 위치

팀 표준에 따라 domain.impl에 인프라 스트럭쳐 코드를 구현할 수 있지만, 좋은 설계는 아니다.

가능하면 구현 클래스를 인프라 스트럭쳐에 위치시켜 의존을 낮춰야한다.

 

매핑 구현

  • 애그리거트 루트는 엔티티 이므로, @Entity로 설정한다.

@Embeddable
public class Orderer {

	@Embedded
    @AttributesOverrides(
    	@AttributeOverride(name = "id", column = @Column(name = "order_id"))
    )
    // 필드 명 재정의
    private MemberId memberId;
}

@Embeddable
public class MemberId {

	@Column(name = "member_id)
    private String id;
}

@Entity와 @Embeddable로 구현을 하려면 noArgsConstructor를 생성해줘야한다.(protected)

하이버네이트는 클래스를 상속한 프록시 객체를 이용해서 지연 로딩을 구현한다.

이 경우 프록시 클래스에서 상위 클래스의 기본 생성자를 호출할 수 있어야 하므로, protected로 지정해줘야 한다.

 

필드 접근 방식 사용

set은 외부에서 데이터를 변경하기 때문에 캡슐화를 깨는 원인이다.

@Access를 지정해주지 않으면, @Id나 @EmbeddedId 위치를 보고 결정, 필드에 위치하면 필드 접근 방식 사용, get 메서드에 위치하면 메서드 접근 방식 사용

 

AttributeConverter

대 부분의 Value는 한 개 컬럼에 매핑한다.

두 개 이상의 밸류 타입을 한 개 컬럼에 매핑해야 할 경우(ex, 주소 addr0, addr1, addr2)는 get/set으로 처리해야 했다.

--> AttributeConverter로 해결하자!(변환 처리)

@Converter(autoApply = true)
public class MoneyConverter implements AttributeConverter<Money, Integer> {
	
    @Overrid
    public Integer convertToDataBaseColumn(Money money) {
    	if (money == null) {
        	return 0;
        }
        
        return money.getValue();
    }
}

@Converter autoApply : default false, 값을 변환할 때 사용할 컨버터를 직접 지정할 수 있다.

이러한 방식이 밸류 컬렉션(주소 등)을 한 개의 컬럼에 매핑하는 방식

 

밸류를 이용한 아이디 매핑

@Id, @EmbeddedId + @Embeddable

 

@Entity
@Table(name = "purchase_order")
public class Order {
	@EmbeddedId
    private OrderNo number;
}

@Embeddable
publc class OrderNo implements Serializeable {
	@Column*name = "order_number")
    private String number;
}

 

별도 테이블에 저장하는 밸류 매핑

  • 루트 엔티티를 뺀 나머지 요소는 대부분 밸류이다. 테이블에 저장한다고 해서 엔티티는 아니다.
  • 주문 애그리거트도 OrderLine을 별도로 저장하지만, OrderLine 자체는 엔티티가 아니라 밸류이다.

 

@SecondaryTable - JoinColumn 지정

 

밸류 컬렉션을 @Entity로 매핑하기

 

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
...
public abstract class Image {

}

@Entity
@DiscriminatorValue("") //상속 관계 매핑
public class InternalImage extends Image {
}

...


@Entity
@Table(name = "product")
public class Product {
	@EmbeddedId
    private ProductId id;
    
    @OneToMany(cascade = {CasecadeType.PERSIST, CascadeType.REMOVE},
    				orphanRemoval = true)
    @JoinCoumn(name = "product_id")
    private List<Image> images = new ArrayList<>();
}

 

애그리거트 로딩 전략

즉시 로딩 방식을 설정하면, 애그리거트에 속한 모든 객체를 함께 로딩할 수 있는 장점이 있지만, 문제가 될 수 있음.

모든 것을 로딩 시점에 가져와야 하는 것이 아니다.

 

표현 영역에서 상태 정보를 보여줄 경우는 조회 전용 기능을 구현하는 방식이 유리할 것 이다.

트랜잭션 범위 내에서 지연 로딩을 허용하기 때문에, 상태를 변경하는 시점에 필요한 구성 요소만 로딩해도 문제가 되지 않는다.

 

애그리거트의 영속성 전파

애그리거트가 완전한 상태여야 한다는 것은 조회 저장 삭제 때에도 유지되는 의미이다.

 

save - 애그리거트에 속한 모든 객체를 저장해야 한다.

delete - 애그리거트 루트 뿐만 아니라 하위 객체들도 모두 삭제가 되어야 한다.

--> OrphanRemoval, Cascade

 

식별자 생성 기능

  • 사용자가 생성 (timestamp 등)
  • 도메인 로직으로 생성
  • DB 이용 @Id, @GeneratedValue(strategy = GenerationType.IDENTITY)
    • 도메인 객체를 저장한 뒤에서야 식별자를 구할 수 있다.