마켓 - 도메인 설계 1. 유의점

카일·2020년 9월 11일
5

우아한 테크 마켓

목록 보기
1/6
post-thumbnail

안녕하세요. 이번 포스팅에서는 우아한 마켓을 개발하는 과정에서 생각했던 도메인 설계 모델을 공유하고자 합니다. 해당 포스팅에서는 도메인 모델을 직접 설명하기 보단, 설계 과정에서 유의했던 부분을 먼저 공유해드리고자 합니다. 도메인 설계 내용은 다음 포스팅에서 작성하도록 하겠습니다.

해당 글에 포함되는 내용이 조금 많기 때문에 궁금하신 부분만 찾아서 보시면 될 것 같습니다.😊

  • 객체지향적 설계와 엔티티 설계의 차이
  • 엔티티간의 연관 관계 매핑
    • 양방향을 지양한 이유와 양방향이 필요한 시점
    • OneToMany를 지양한 이유

객체지향적 설계와 엔티티 설계의 차이

ORM은 객체와 데이터베이스의 패러다임의 차이를 극복하고자 나온 기술입니다. 자바에서 사용하는 객체와 데이터베이스의 특성이 다르기 때문에 이를 중간에서 매핑해주는 작업을 대신해줌으로써 개발자가 데이터베이스에 덜 의존적인 형태로 개발할 수 있도록 만들어주는 역할을 하는 것입니다. 현대의 어플리케이션은 매우 복잡하여, 객체지향적으로 이러한 복잡성을 낮추고자 노력합니다. 이 과정에서 발생하는 데이터베이스와의 패러다임의 불일치는 ORM이 해결해주는 형태이구요.

성숙한 ORM인 JPA를 사용한다면 우리는 객체지향적 설계만 하면 될까요?

객체지향적 설계

상황에 따라 달라지겠지만 '아닌 경우가 있을 수 있다' 라는 것이 저의 의견입니다. 예를 들어 영화 티켓과 티켓을 관리하는 판매원이 있다고 가정하겠습니다. 이 경우 티켓이 판매원을 알고 있는 것과 판매원이 티켓을 가지고 있는 경우 어떤 것이 바람직할까요? 객체지향적으로 이 둘의 관계를 설계할 때 고려해야 하는 주된 요소는 다음과 같습니다.

  • 정상적으로 동작해야 한다.
  • 변경에 용이해야 한다.
  • 이해하기 쉬워야 한다.

우리의 일반적인 이해로 판매원이 티켓을 가지고 이를 판매하는 방식이 직관적입니다. 또한 변경에 용이하게 하기 위해서 티켓의 List를 관리하는 Tickets 라는 일급컬렉션을 만들더라도 전체 티켓의 관리는 판매원이 하는 것이 바람직합니다. 왜냐하면 티켓 자체의 기능이 변경된다면 티켓만 변경될 것이고, 전체 티켓을 관리하는 로직이 변경된다면 Tickets만 변경하면 되기 때문입니다.

그렇다면 객체와 테이블을 매핑하는 과정에서 판매원과 티켓의 관계는 1 : N 이 되고 새로운 티켓을 저장하고 삭제하는 관리는 판매원이 담당하게 됩니다. 이를 자바 코드와 데이터베이스로 옮기면 아래와 같습니다.

판매원과 티켓은 1 : N 의 관계를 가지고 연관관계의 주인은 판매원이 됩니다.

  • 자바
@Entity
public class Seller {
    @Id @GeneratedValue
    private Long id;

    @OneToMany
    @JoinColumn(name = "seller_id")
    private List<Ticket> tickets = new ArrayList<>();

}

@Entity
public class Ticket {
    @Id @GeneratedValue
    private Long id;
}

데이터베이스는 1 : N 관계에서 항상 N이 1의 아이디를 가지고 있는 것 밖에 구조적으로 지원할 수 없기 때문에 아래와 같은 형태가 됩니다.

  • 데이터베이스

문제점

하지만 이 경우 OneToMany 관계에서 연관관계의 주인이 One이 되었을 때 발생하는 문제가 있습니다. OneToMany를 사용하는 대신 ManyToOne의 양방향 관계로도 이를 풀어나갈 수 있는데요. 이 경우에는 양방향 관계에서의 문제점이 다시 발생합니다.

결론

자바와 데이터베이스의 패러다임의 차이 중 많은 것을 ORM이 해결해 주지만 설계에 있어서는 트레이드 오프 관계에 있는 것 같습니다. 객체지향적으로 설계를 하는 것이 OneToMany 그리고 양방향 관계의 단점을 커버할 수 있을만큼 어플리케이션의 복잡도를 낮출 수 있는가? 라는 질문을 생각하며 도메인을 설계하는 것이 좋을 것 같습니다.

물론 객체지향적으로 설계하는 것이 항상 이러한 설계만을 가지는 것은 아닙니다. 다만 제가 말하고자 했던 부분은 이런 설계가 나온 경우 해당 설계를 그대로 가져갈 것인가, 아니면 조금 디비 종속적으로 설계하더라도 어플리케이션의 복잡도는 낮출 것인가? 에 대한 고민을 해보는 것은 가치 있다고 말씀드리고 싶었습니다.


지양하는 연관 관계

OneToMany 관계를 지양하는 이유

여기서 말하는 OneToMany란 1 : N 관계에서 1이 연관관계의 주인이 되는 경우를 의미합니다. 이런 경우 크게 두가지 문제점을 가지고 있습니다.

  1. 변경한 엔티티와 수정되는 엔티티가 달라질 수 있다.
    • 위의 사례에서 셀러가 핸드폰을 관리하는 경우 휴대폰을 저장하는 시점에는 Seller의 정보를 알 수 없습니다. 따라서 외래키가 null 로 들어가고, 셀러가 휴대폰을 List.add 하고 셀러가 저장 혹은 수정되는 시점에 휴대폰 데이터에 Update쿼리가 발생합니다. 즉 변경한 엔티티는 Seller라고 생각했지만 쿼리는 CellPhone 테이블에 발생하는 것입니다. 지금은 두개의 엔티티만이 이런 관계를 가지고 있지만 여러 엔티티가 1 : N 관계를 갖게 된다면 어플리케이션을 관리하는 관점에서 유지보수가 매우 어려워집니다. (직관을 벗어나게 됩니다.)
  2. N + 1 문제를 무조건적으로 마주쳐야 합니다.
    • 일이 N을 가지고 있는 경우 Jpa에서 자주 발생하는 N + 1 문제를 항상 고려하여야 합니다. N이 1을 갖는 구조(ManyToOne)의 경우 이러한 문제에 있어서 벗어날 수 있습니다.
    • 물론 여러 엔티티를 함께 조회해야하는 경우, N + 1 문제를 고려해야하는 것은 마찬가지입니다. 양방향관계에서도 마찬가지로 N + 1을 고려해야 하는 것도 맞습니다. 다만 양방향 관계 혹은 OneToMany가 아닌, ManyToOne 관계만으로도 이미 연관관계 매핑은 끝이 날 수 있는 것을 N + 1 문제까지 무조건적으로 고려해야하는 설계를 작성한다는 것은 미리 위험을 맞이하는 것과 같다고 생각합니다.
  3. 결론 - OneToMany관계 보다는 ManyToOne 단방향을 사용하자
    • 물론 필요에 따라 OneToMany 관계를 사용하는 것이 바람직할 수 있습니다. 다만 초반 설계 과정에서 OneToMany 관계를 가져갔을 때 위에서 언급한 문제들이 발생하더라도 필요한가?를 고민 해보고 그럼에도 필요하거나 다른 방법이 없는 경우에 사용하는 것이 좋을 것 같습니다.

ManyToOne 양방향 관계를 지양하는 이유

ManyToOne 단방향 관계에서 반대편으로 탐색할 수 있는 참조를 추가하는 것을 양방향 관계라고 합니다. 양방향 관계는 다음과 같은 단점을 가지고 있습니다.

  1. 순환 참조의 문제
    • RestController에서 엔티티를 반환하는 경우 엔티티를 toString() 하는 형태로 응답을 구성합니다. 이 과정에서 엔티티가 참조하는 다른 엔티티가 존재하는 경우 무한 루프에 빠질 수 있습니다. (Member → Team → Member → Team)
    • 위와 같은 순환 참조의 문제는 사실, 조금만 주의하면 해결할 수 있습니다. 하지만 양방향이 가지고 있는 큰 단점은 어플리케이션이 커진 경우 이러한 순환 관계를 디버깅 하는 것이 너무 어렵다는 것입니다. 엔티티의 개수가 많아짐에 따라 이러한 순환 참조는 잠재적인 버그를 발생시킬 확률이 너무 높기 때문에 지양하는 것이 맞다고 생각했습니다.
  2. 변경의 관점
    • 양방향 관계를 사용하는 경우 양방향으로 맺어진 두 엔티티의 변경은 서로에게 영향을 확률이 높습니다. ( 모든 변화가 서로를 바꾸진 않기 때문에 확률이라 표현하였습니다.) 즉 변경에 있어서 유연하게 대처하기 힘들며 관리해야 할 포인트가 두개로 늘어난 것을 의미합니다. 이 부분도 충분히 개발의 복잡도를 증가시킬 수 있을 것이라 생각했습니다.
  3. OneToMany와 이어지는 N + 1 문제 - 윗 부분의 내용과 동일합니다.
  4. 결론 - 꼭 필요한 경우에만 양방향 관계를 사용하자
    • 개발을 하다보면 항상 함께 조회되는 엔티티가 존재합니다. 또한 1쪽으로 접근해야 개발이 편해지는 경우도 많습니다. 이런 경우에만 선택적으로 양방향 관계를 사용하는 것이 바람직할 것 같습니다.

연관 관계의 결론

Q.

그러면 도메인간의 연관 관계를 어떻게 세팅할건가요?

A.

연관 관계 특히 1 : N의 관계에서 객체 지향적인 설계는 개인적으로 득보다는 실이 클 것이라고 생각합니다. 따라서 이런 관계에 있어서는 조금 더 데이터베이스 친화적으로 설계하는 것이 좋다고 생각하였습니다. 1 : N 관계가 아닌 부분에 있어서는 객체 지향적으로 설계하는 것이 좋다고 생각합니다.

객체 지향의 꽃은 어플리케이션의 복잡도를 낮추어 유지보수를 용이하게 하고, 협력하는 객체를 통해 복잡한 문제를 해결하는 프로그램을 작성하는 것이라고 생각합니다. 하지만 데이터베이스와의 패러다임의 차이로 결국 완전한 객체 지향만으로는 어플리케이션의 복잡도가 감소하지는 않는 것 같습니다. 적절한 형태의 혼용(객체지향적 설계와 데이터베이스 친화적 설계)가 함께 사용될 때 프로그램의 복잡도가 줄어들고, 유지보수하기 좋은 형태가 된다고 생각합니다.


결론

현대적인 어플리케이션은 대부분 RDBMS로 구성되어 있습니다. 뿐만 아니라 복잡도를 관리하기 위해 객체지향 언어를 자주 사용합니다. 하지만 우리는 여전히 객체지향, 데이터베이스 사이에서 발생하는 패러다임에 대해서 고민해야하는 것 같습니다. ORM이 이러한 차이의 많은 부분을 해결해주지만 근본적인 차이로 인해 해결 아닌 해결(해결은 되지만 오히려 어플리케이션의 복잡도가 올라가는)이 될 수 있습니다.

결국 우리의 목적은 복잡도를 관리하며 유지보수 하기 좋은 코드를 생산성 있게 개발하는 것입니다. 그러기 위해서는 객체지향과 데이터 베이스 친화적인 코드 사이에서 늘 TRADE-OFF를 겪어야 하고 현명한 판단을 내리는 것이 중요할 것 같습니다.

얕은 지식으로 나름대로 생각해 본 결과인데 부족하거나 틀린 부분이 있으면 알려주시면 너무 감사하겠습니다.!!!!

0개의 댓글