본문 바로가기

programming/DDD

[DDD] chapter 4

반응형

Chapter 4

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

4.1.1 모듈 위치



  • 위의 그림처럼 Repository interface는 도메인 영역에 속한다.
    • 이를 구현하는 구현체는 인프라영역에서 구현한다.
    • 가능하면 리포지터리 구현 클래스를 인프라스트럭쳐 영역에 위치 시켜서 인프라스트럭처에 대한 의존을 낮춰야한다.

4.2.1 리포지터리 기본 기능 구현

  • 인터페이스는 애그리거트 루트를 기준으로 작성한다.
    • 주문 애그리거트는 Order 루트 엔티티를 비롯해 OrderLine, Orderer, ShippingInfo 등 다양한 객체를 포함하는데, 이 구성요소 중에서 루트 엔티티인  Order를 기준으로 리포지터리 인터페이스를 작성한다.
  • 애그리거트의 널리 사용되는 규칙은 'findBy프로퍼티 이름(프로퍼티 값)' 형식을 사용하는 것이다.
public class JpaOrderRepository implements OrderRepository{
    @PersistenceContext
    private EntityManager entityManager;
    
    @Override
    public Order findById(OrderNo id){
        return entityManager.find(Order.class, id);
    }
    
    @Override
    public void save(Order order){
        entityManager.persist(order);
    }
}
public class ChangeOrderService {
    //두 개 이상의 애그리거트를 변경해야 하면,
    //응용 서비스에서 각 애그리거트의 상태를 변경한다.
    @Transactional
    public void changeShippingInfo(OrderId id, ShippingInfo newShippingInfo, boolean useNewShippingAddrAsMemberAddr){
        Order order = orderRepository.findbyId(id);
        if(order == null) throw new OrderNotFoundException();
        order.changeShippingInfo(newShippingInfo);
        if(useNewShippingAddrAsMemberAddr){
            //ID를 이용해서 참조하는 애그리거트를 구한다.
            Member member = memberRepository.findById(order.getOrderer().getMemberId());
            member.changeAddress(newShippingInfo.getAddress());
        }

    }
}
  • changeShippingInfo() 메서드는 스프링 프레임워크의 트랜잭션 관리 기능을 통해 트랜잭션 범위에서 실행된다.
  • order.changeShippingInfo() 메서드를 실행한 결과로 애그리거트가 변경되면 JPA는 변경 데이터를 DB에 반영하기 위해 UPDATE 쿼리를 실행한다.
    •  이게.. 무슨 소리인가...?

4.2 스프링 데이터 JPA를 이용한 리포지터리 구현

public class Order {
    private Orderer orderer;
    private Money totalAmounts;
    private List<OrderLine> orderLines;
    private OrderNo number;
}
  • 평범한 타입이 아닌 OrderNo 객체를 생성하다니... ㄷㄷ
  • 스프링 데이터 JPA는 OrderRepository를 리포지터리로 인식해서 알맞게 구현한 객체를 빈으로 등록한다.

4.3 매핑 구현

4.3.1 엔티티 밸류

애그리거트와 JPA 매핑을 위한 기본 규칙은 다음과 같다.

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

한 테이블에 엔티티와 밸류 데이터가  같이 있다면 다음과 같은 규칙을 따른다.

  • 밸류는 @Embeddable로 매핑 설정한다.
  • 밸류 타입 프로퍼티는 @Enbedded로 매핑 설정한다.

주문 애그리거트의 데이터 구조는 다음과 같다.

  • Entity: Order
    • Value Type: Orderer
    • Value Type: ShippingInfo
      • Value Type:  Address
      • Value Type: Receiver

자 위의 데이터 구조를 다음과 같이 객체로 표현할 수 있다. 일단 Order 객체는 간단하게 다음과 같다.

@Entity
@Table(name = "purchase_order")
public class Order {

}

 

Order에 속하는 Orderer는 밸류이므로 @Embeddable로 매핑한다. 일단 어노테이션에 대해 좀 정리해보았다.


@Embeddable: 값 타입을 정의하는 곳에 표시.

@Embedded: 값 타입을 사용하는 곳에 표시.

효과: JPA안의 컬럼을 하나의 객체로 표시할 수 있다.

 

@AttributeOverride: @MappedSuperclass를 통해 상속 받은 경우나 @Embedded를 통해 다른 객체를 필드에 선언한 경우 해당 엔티티에서는 다른 컬럼명을 사용하고 싶을 때가 있는데, 이러한 경우에 사용할 수 있는 어노테이션이다.


어노테이션을 정리하다보니, 문법도 문법이지만 결국 가독성과 객체화를 위한 기능인 것 같은 느낌이 들었다.

 

4.3.2 기본 생성자

Receiver 객체를 예로 들어보자!

  • 해당 객체가 불변탕비이면 생성 시점에 필요한 값을 모두 전달받으므로 값을 변경하는 set 메서드를 제공해서는 안된다.
  •  그러나 JPA에서 @Entity, @Embeddable로 객체를 매핑하려면 기본 생성자를 제공해야한다. DB에서 데이터를 읽어와 매핑된 객체를 생성할 때 기본 생성자를 사용해서 객체를 생성하기 때문이다.
  • 따라서 다음과 같이 기본 생성자를 문법형식에 맞춰 생성해야한다. (호호.... 조금은 불편한듯...)
@Embeddable
public class Receiver {
    private String name;
    private String phoneNumber;

    protected Receiver(){}
    
    public Receiver(String name, String phoneNumber){
        this.name = name;
        this.phoneNumber = phoneNumber;
    }
    
    public String getName(){
        return name;
    }

    public String getPhoneNumber(){
        return phoneNumber;
    }
}

 

  • 기본 생성자는 개발자를 위한 것이 아닌, JPA를 위한 것이다. 그래서 Protected로 선언한 것...!

4.3.3 필드 접근 방식 사용

  • Entity에 프로퍼티를 위한  공개 get/set 메서드를 추가하면 도메인의 의도가 사라지고 객체가 아닌 데이터 기반으로 엔티티를 구현할 가능성이 높아진다.
    • 특히 set 메서드는 내부 데이터를 외부에서 변경할 수 있 는 수단이 되기 떄문에 캡슐화를 깨는 원인이 된다.
  • 의도가 잘 드러나는 코드를 짜자!
    • setState() 보다는 cancle() 처럼 도메인을 잘 표현하고 의미를 잘 알아들을 수 있도록 작성해야한다.

4.3.4 AttributeConverter를 이용한 밸류 매핑 처리 ~ 는 github에

오늘은 참... 처음 보는 개념이 많이 나오는 것  같다. 사실 이런 도구(?)나 기술을 공부하고 빨리 적용하는 것이 개발자에게 중요한 자세라는 것을 안다... 뭐 쨎든... 이번에도 검색을 통해 조금 더 정리해봤다.


AttributeConverter

  • convertToDatabaseColumn 와 convertToEntityAttribute 가 내장이 되어있다.
  • convertToDatabaseColumn의 경우 테이블에 저장혹은 변경이 있을 때 호출이 되는 메소드.
  • convertToEntityAttribute의 경우 테이블에서 출려이 될때 호출이된는 메소드.

4.4 애그리거트 로딩 전략

JPA를 사용할 때는 애그리거트에 속한 모든 객체가 모두 모여야 한다는 것이다.

  • 해당 상황을 위해 연관 매핑의 조회 방식을 즉시 로딩으로 설정하면 된다.

4.5 애그리거트의 영속성 전파

애그리거트가 완전한 상태여야 한다는 것은 애그리거트 루트를 조회할 때만이 아니라, 저장하고 삭제할 때도 하나로 처리해야 함을 의미한다.

  • @Embeddable 매핑 타입은 함게 저장되고 삭제 되므로 별도 설정을 할 필요가 없다.
  • @Entity 타입에 대한 매핑은 cascade 값을 별로로 설정해야한다.
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}

4.6 식별자 생성 기능

식별자는 크게 세 가지 방식 중 하나로 생성한다.

  • 사용자가 직접 생성
  • 도메인 로직으로 생성
  • DB를 이용한 일련번호 사용

4.7 도메인 구현과 DIP

구현 기술에 대한 의존 없이 도메인을 순수하게 유지하려면 스프링 데이터 JPA의 Repository인터페이스를 상속받지 않도록 수정하고, ArticleRepository 인터페이스를 구현한 클래스를 인프라에 위치시켜야 한다.(오래된 궁금증이 해결되는 구만)

  • DIP를 적용하는 주된 이유는 저수준 구현이 변경되더라도, 고수준이 영향을 받지 않도록 하기 위함이다.
  • 변경이 없는 사용에서는 변경에 미리 대비하기 보다는 실용성을 챙기자...
반응형

'programming > DDD' 카테고리의 다른 글

[DDD] chapter 6  (0) 2024.02.10
[DDD] chapter 5  (1) 2024.02.07
[DDD] chapter 3  (0) 2024.01.18
[DDD] chapter2  (1) 2024.01.13
[DDD] chapter 1  (0) 2024.01.13