안녕하세요. 이번 포스팅에서는 우아한 마켓을 개발하는 과정에서 생각했던 도메인 설계 모델을 공유하고자 합니다. 해당 포스팅에서는 도메인 모델을 직접 설명하기 보단, 설계 과정에서 유의했던 부분을 먼저 공유해드리고자 합니다. 도메인 설계 내용은 다음 포스팅에서 작성하도록 하겠습니다.
해당 글에 포함되는 내용이 조금 많기 때문에 궁금하신 부분만 찾아서 보시면 될 것 같습니다.😊
- 객체지향적 설계와 엔티티 설계의 차이
- 엔티티간의 연관 관계 매핑
- 양방향을 지양한 이유와 양방향이 필요한 시점
- OneToMany를 지양한 이유
ORM은 객체와 데이터베이스의 패러다임의 차이를 극복하고자 나온 기술입니다. 자바에서 사용하는 객체와 데이터베이스의 특성이 다르기 때문에 이를 중간에서 매핑해주는 작업을 대신해줌으로써 개발자가 데이터베이스에 덜 의존적인 형태로 개발할 수 있도록 만들어주는 역할을 하는 것입니다. 현대의 어플리케이션은 매우 복잡하여, 객체지향적으로 이러한 복잡성을 낮추고자 노력합니다. 이 과정에서 발생하는 데이터베이스와의 패러다임의 불일치는 ORM이 해결해주는 형태이구요.
객체지향적 설계
상황에 따라 달라지겠지만 '아닌 경우가 있을 수 있다' 라는 것이 저의 의견입니다. 예를 들어 영화 티켓과 티켓을 관리하는 판매원이 있다고 가정하겠습니다. 이 경우 티켓이 판매원을 알고 있는 것과 판매원이 티켓을 가지고 있는 경우 어떤 것이 바람직할까요? 객체지향적으로 이 둘의 관계를 설계할 때 고려해야 하는 주된 요소는 다음과 같습니다.
우리의 일반적인 이해로 판매원이 티켓을 가지고 이를 판매하는 방식이 직관적입니다. 또한 변경에 용이하게 하기 위해서 티켓의 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이 연관관계의 주인이 되는 경우를 의미합니다. 이런 경우 크게 두가지 문제점을 가지고 있습니다.
null
로 들어가고, 셀러가 휴대폰을 List.add
하고 셀러가 저장 혹은 수정되는 시점에 휴대폰
데이터에 Update쿼리가 발생합니다. 즉 변경한 엔티티는 Seller라고 생각했지만 쿼리는 CellPhone 테이블에 발생하는 것입니다. 지금은 두개의 엔티티만이 이런 관계를 가지고 있지만 여러 엔티티가 1 : N 관계를 갖게 된다면 어플리케이션을 관리하는 관점에서 유지보수가 매우 어려워집니다. (직관을 벗어나게 됩니다.)ManyToOne 양방향 관계를 지양하는 이유
ManyToOne 단방향 관계에서 반대편으로 탐색할 수 있는 참조를 추가하는 것을 양방향 관계라고 합니다. 양방향 관계는 다음과 같은 단점을 가지고 있습니다.
Q.
그러면 도메인간의 연관 관계를 어떻게 세팅할건가요?
A.
연관 관계 특히 1 : N의 관계에서 객체 지향적인 설계는 개인적으로 득보다는 실이 클 것이라고 생각합니다. 따라서 이런 관계에 있어서는 조금 더 데이터베이스 친화적으로 설계하는 것이 좋다고 생각하였습니다. 1 : N 관계가 아닌 부분에 있어서는 객체 지향적으로 설계하는 것이 좋다고 생각합니다.
객체 지향의 꽃은 어플리케이션의 복잡도를 낮추어 유지보수를 용이하게 하고, 협력하는 객체를 통해 복잡한 문제를 해결하는 프로그램을 작성하는 것이라고 생각합니다. 하지만 데이터베이스와의 패러다임의 차이로 결국 완전한 객체 지향만으로는 어플리케이션의 복잡도가 감소하지는 않는 것 같습니다. 적절한 형태의 혼용(객체지향적 설계와 데이터베이스 친화적 설계)가 함께 사용될 때 프로그램의 복잡도가 줄어들고, 유지보수하기 좋은 형태가 된다고 생각합니다.
현대적인 어플리케이션은 대부분 RDBMS로 구성되어 있습니다. 뿐만 아니라 복잡도를 관리하기 위해 객체지향 언어를 자주 사용합니다. 하지만 우리는 여전히 객체지향, 데이터베이스 사이에서 발생하는 패러다임에 대해서 고민해야하는 것 같습니다. ORM이 이러한 차이의 많은 부분을 해결해주지만 근본적인 차이로 인해 해결 아닌 해결(해결은 되지만 오히려 어플리케이션의 복잡도가 올라가는)이 될 수 있습니다.
결국 우리의 목적은 복잡도를 관리하며 유지보수 하기 좋은 코드를 생산성 있게 개발하는 것입니다. 그러기 위해서는 객체지향과 데이터 베이스 친화적인 코드 사이에서 늘 TRADE-OFF를 겪어야 하고 현명한 판단을 내리는 것이 중요할 것 같습니다.
얕은 지식으로 나름대로 생각해 본 결과인데 부족하거나 틀린 부분이 있으면 알려주시면 너무 감사하겠습니다.!!!!