[Spring] Spring Data JDBC 기반 도메인 엔티티 및 테이블 설계

zini9188·2023년 2월 22일
1

Spring

목록 보기
22/33

도메인 엔티티 및 테이블 설계

도메인에서 애그리거트 루트 찾기

애그리거트 간의 관계

  • 회원 정보(Member)와 주문 정보(Orders)의 관계 (1 대 N)
    • 한 명의 회원이 여러 번 주문을 할 수 있다.
  • 주문 정보(Orders)와 커피 정보(Coffee)의 관계 (N 대 N)
    • 한 주문에 여러 종류의 커피를 가질 수 있다.
    • 한 커피는 여러 건의 주문에 속할 수 있다.
  • N 대 N의 관계는 일반적으로 1 대 N, N 대 1의 관계로 재설계하므로 주문 커피 정보와의 1 대 N, N 대 1 관계로 재설계 된다.
    • 주문 정보(Orders)와 주문 커피 정보(Order_Coffee) (1 대 N)
    • 주문 커피 정보(Order_Coffee)와 커피 정보(Coffee) (N 대 1)

애그리거트 객체 매핑 규칙

1. 모든 엔티티 객체의 상태는 애그리거트 루트를 통해서만 변경할 수 있다.

애그리거트 내의 엔티티는 애그리거트 루트에 직간접적으로 참조를 받고 있다. 이는 배달 주소 정보를 변경하기 위해서는 주문 정보를 거쳐 배달 주소 정보 엔티티의 상태를 변경해야 함을 의미한다.

결과적으로 애그리거트 루트를 통해 나머지 엔티티에 접근하는 것은 어떤식으로든 애그리거트 루트가 나머지 모든 엔티티에 대한 객체를 직간접적으로 참조할 수 있음을 의미한다.

만약 애그리거트 루트를 통하지 않고 바로 배달 주소 정보로 접근하여 주소 정보를 변경한다면 배달의 상태(배달을 시작하기 전에만 주소 변경이 가능하다는 규칙)에 상관 없이 배달 주소를 변경하여 도메인 규칙을 무시하고 변경하는 것이 되므로 도메인 규칙에 대한 일관성이 무너진다.

따라서 항상 애그리거트 루트에서 도메인 규칙을 검증하고 검증된 규칙에 의해서만 하위 수준의 도메인을 변경하여도메인 규칙의 일관성을 유지할 수 있다.

2. 하나의 동일한 애그리거트 내에서의 앤티티 객체 참조

동일한 애그리거트 내의 엔티티끼리는 항상 객체 참조를 해야하는 것을 의미한다.

3. 애그리거트 루트 대 애그리거트 루트 간의 엔티티 객체 참조

서로 다른 애그리거트 루트 간에는 객체가 아닌 애그리거트 루트의 ID를 참조 값으로 참조해야 함을 의미한다.

도메인들의 테이블

CREATE TABLE IF NOT EXISTS MEMBER (
    MEMBER_ID bigint NOT NULL AUTO_INCREMENT,
    EMAIL varchar(100) NOT NULL UNIQUE,
    NAME varchar(100) NOT NULL,
    PHONE varchar(100) NOT NULL,
    PRIMARY KEY (MEMBER_ID)
);

CREATE TABLE IF NOT EXISTS COFFEE (
    COFFEE_ID bigint NOT NULL AUTO_INCREMENT,
    KOR_NAME varchar(100) NOT NULL,
    ENG_NAME varchar(100) NOT NULL,
    PRICE number NOT NULL,
    COFFEE_CODE char(3) NOT NULL,
    PRIMARY KEY (COFFEE_ID)
);

CREATE TABLE IF NOT EXISTS ORDERS (
    ORDER_ID bigint NOT NULL AUTO_INCREMENT,
    MEMBER_ID bigint NOT NULL,
    ORDER_STATUS varchar(20) NOT NULL,
    CREATED_AT datetime NOT NULL,
    PRIMARY KEY (ORDER_ID),
    FOREIGN KEY (MEMBER_ID) REFERENCES MEMBER(MEMBER_ID)
);

CREATE TABLE IF NOT EXISTS ORDER_COFFEE (
    ORDER_COFFEE_ID bigint NOT NULL AUTO_INCREMENT,
    ORDER_ID bigint NOT NULL,
    COFFEE_ID bigint NOT NULL,
    QUANTITY int NOT NULL,
    PRIMARY KEY (ORDER_COFFEE_ID),
    FOREIGN KEY (ORDER_ID) REFERENCES ORDERS(ORDER_ID),
    FOREIGN KEY (COFFEE_ID) REFERENCES COFFEE(COFFEE_ID)
);

서로 다른 1 : N 관계인 애그리거트의 참조 방식

주문 정보와 회원 정보는 서로 다른 애그리거트로 주문 정보에서 회원 정보를 ID 참조함을 알 수 있다.

@Getter
@Setter
public class Member {
    @Id
    private Long memberId;
    private String email;
    private String name;
    private String phone;
}
@Getter
@Setter
@Table("ORDERS")  
public class Order {
    @Id
    private long orderId;    
    private AggregateReference<Member, Long> memberId;
}

N 대 N 관계에서의 참조 방식

N 대 N 관계일 경우 우선 1 : N, N : 1 관계로 만드는 것이 필요하다. 지금의 경우에는 주문 커피 정보로 두 도메인을 나눴다.

우선 주문 정보와 주문 커피 정보는 1 : N 관계이다. 두 도메인은 주문이라는 같은 애그리거트 내에 있으므로 객체 참조가 가능함을 의미한다.

@Getter
@Setter
@Table("ORDERS")
public class Order {
    @Id
    private long orderId;
    private AggregateReference<Member, Long> memberId;
    
    @MappedCollection(idColumn = "ORDER_ID", keyColumn = "ORDER_COFFEE_ID")
    private Set<CoffeeRef> orderCoffees = new LinkedHashSet<>();
}

@MappedCollection 애너테이션의 역할

idColumn

@MappedCollection에서 idColumn 속성은 자식 테이블에 추가되는 외래키에 해당하는 컬럼 명을 지정한다.
ORDERS 테이블의 자식 테이블은 ORDER_COFFEE 테이블이며, 이 테이블은 ORDERS 테이블의 기본키인 ORDER_ID를 외래키로 갖게 된다.

keyColumn

keyColumn 속성은 외래키를 포함하고 있는 테이블의 기본키 컬럼명을 지정한다.

ORDERS 테이블의 자식 테이블인 ORDER_COFFEE 테이블의 기본키는 ORDER_COFFEE_ID이므로 keyColumn의 값이 ORDER_COFFEE_ID가 된다.

@Getter
@Builder
@Table("ORDER_COFFEE") 
public class CoffeeRef {
    @Id
    private long orderCoffeeId;
    private long coffeeId; 
    private int quantity; 
}

그러나 주문 커피 정보와 커피 정보는 N : 1 관계이며, 두 도메인은 서로 다른 애그리거트에 있음을 알 수 있다.
이러한 경우에 ID 참조를 사용해야 한다.

@Getter
@Builder
@Table("ORDER_COFFEE") 
public class CoffeeRef {
    @Id
    private long orderCoffeeId;
    private long coffeeId; 
    private int quantity; 
}
@Getter
@Setter
public class Coffee {
    @Id
    private long coffeeId;
    private String korName;
    private String engName;
    private int price;
    private String coffeeCode;  
}

주문 커피 정보가 주문 애그리거트에 있는 이유

주문 커피 정보가 커피 애그리거트에 있으면 안되나? 라는 생각을 할 수 있다. 그러나 주문 커피 정보 도메인은 주문이라는 정보가 있어야 의미 있는 정보가 된다. 물론 커피라는 정보가 없으면 사용할 수 없지만 주문이라는 정보가 조금 더 중요하다. 때문에 주문 애그리거트에 있는 것이 조금 더 바람직하다.

주문 커피 정보에서 커피 객체가 없음에도 CoffeeId를 사용할 수 있는 이유

주문 커피 정보에서 커피 객체가 없지만 coffeeId를 참조하고 있다. 이것이 가능한 이유는 데이터베이스 내에서는 서로 연결되어 있기 때문이다. 이를 연결하는 과정을 스프링이 내부적으로 동작하여 해결해준다고 한다.

결론적으로 스프링이 주문 커피 정보에서 coffeeId를 참조할 수 있도록 해준다.

엔티티 클래스 간 관계

데이터베이스 테이블 설계

profile
백엔드를 지망하는 개발자

0개의 댓글