티스토리 뷰
0. 환경
필자는 mysql8.0 을 사용중 이며, RDBMS 기준으로 이야기를 해보겠다
1. 들어가며
기본적으로 도메인에 대한 내용을 확실히 숙지했고, 비즈니스 흐름을 명확히 파악했을 때 DB 설계를 해야 큰 효율을 볼 수 있다고 생각한다
실무에서 가끔 테이블 설계를 하고, 필요한 컬럼들을 추가하는 작업을 진행할 때 가 있다
테이블 설계시 거시적인 관점이 아닌 당장 앞에 필요한 부분만 보고 설계하면 누구나 쉽게 설계 할 수 있다
하지만 확장성 및 유지보수성을 생각하는 미래지향적 설계라면 고려해야 할 부분이 여러가지가 있다.
- 정규화
- 키관리 전략
- 외래키, 복합키 등등
대표적으로 2가지가 있다
필자는 실무에서 JPA 를 사용중에 있고, JPA 연관관계 설정시 1:1, 1:N 관계을 테이블이 여러개있지만 새로운 설계에서는 외래키 설정은 제외를 시킨다
이유는 외래키는 정말 잘 설계하고 잘 아는 사람이 써야 이점을 본다고 생각한다.
- 외래키 자체는 문제가 없지만, 잘못된 외래키 설계는 간단한 변경에도 큰 비용이드는 상황이 발생할 것 이다.
그리고 DB에 특화되 있지 않은 본인 및 팀원들의 상황을 고려했을 때 외래키는 유지보수성을 해친다 판단하여 외래키 설계를 하지 않기로 rule 을 정했다
연관관계 에서 외래키를 잡지 않게 하는 방법 외래키 전략위 글을 참고해도 좋을 것 같다
본론으로 들어가 내가 고민하고 공부해본 내용을 알아보자
2. 본론
객체지향에 개념에 '고수준 모듈, 저수준 모듈' 이라는 키워드가 존재한다
그리고 객체지향 원칙에 DIP 라고 하여 의존성 역전 원칙이라는 개념이 있다
고수준 모듈은 저수준 모듈에 의존하면 안되고, 저수준 모듈도 고수준에 의존하지 않고 추상화에 의존해야 한다는 개념이다
RDBMS 설계시에도 비슷한 개념으로 설계를 하면 좋을 것 같다는 생각을하였고, 설계시에 적용을 해보았다
예시 테이블은 아래와 같다
- 고수준 개념
- merchant
- 저수준 개념
- merchant_contract (1:1)
- merchant_bank_account (1:n)
- merchant_transaction (1:n)
고수준 개념은 merchant 이고 고수준 개념이 존재하기에 하위에 저수준 개념들이 존재한다
위 테이블 설계시 기본적인 1정규화 정도는 진행을 했다고 가정하고, 다음으로 파악해야할 부분은 키관리 전략이다
일반적으로 JPA 를 사용하고 어떠한 옵션도 주지않고 컬럼 세팅 및 연관관계 전략을 잡으면 그에 맞춰서 JPA 가 ddl-auto 옵션을 확인 후 테이블을 생성해준다
하지만 위 옵션을 사용하면 Hibernate 설정에 의해 자동으로 연관관계를 파악하여 외래키를 생성하여 테이블 생성을 한다.
JPA 옵션을 통하여 테이블 생성시 외래키 자동 생성을 하며 인덱스를 잡지만 나는 외래키 전략을 사용하지 않았기에 인덱스를 생성해줘야 했다
MySQL 은 pk 를 설정하면 클러스터드 인덱스를 자동으로 생성해준다. 위 인덱스는 테이블당 딱 1개만 존재할 수 있다
MySQL InnoDB에서 외래키 생성 시 논클러스터드 인덱스가 자동 생성되지만, 외래키 제약조건 없이 참조 컬럼을 사용할 경우 명시적 인덱스 생성이 필요하다.
그러므로 외래키 대상이 되는 id 들은 인덱스를 직접 지정해서 설정해줘야 한다
그러므로 필자는 외래키 제약조건을 사용하지는 않고 연관 테이블 id 에 명시적으로 인덱스를 생성하는 것으로 변경을 하였다
@Table(name="merchant_contract", indexes = @Index(name="idx_merchant_id", columnList = "merchant_id"))
@Entity
public class MerchantContract {
@Id
private Long id;
@OneToOne(fetchType = FetchType.LAZY)
@JoinColumn(name="merchant_id")
private Merchant merchant;
}
위를 통해 외래키 전략을 없애며 복잡성을 조금이라도 줄일 수 있었다
이렇게 키 전략을 해결해 보았고, 마지막으로는 테이블 고수준 개념과 저수준 개념에 대한 궁금증이 있어서 검색을 해보았다
정규화 및 키 전략은 해결을 했는데, 상위 테이블이 하위 테이블을 어디까지 알고 있어야 하나? 라는 의문이 들었다
필자는 DB 설계시 객체지향 관점에서 생각하여 관심사 및 목적에 따라 명확하게 분리하는걸 선호하였다
(실무에서 다른 팀 테이블에 컬럼이 95개 인걸 보고 PTSD 가 와서 정규화 하는걸 항상 습관화 하였다)
사실 이 질문에는 모순이 있는게 정규화 및 키 전략을 잘 해결했다면 자연스럽게 고수준은 저수준을 정보를 알고있지 않을 것이고, 저수준은 고수준을 정보를 알 수 있게 설계가 되었을 것이다
(설계 당시에는 약간 헷갈리는 부분이 있어서 확실하게 하기 위해 정보를 찾아보았다..)
객체지향 관점에서 보면 상위 개념은 하위 개념을 정보를 알필요가 없고
하위 개념은 상위 개념의 추상적인 부분을 구현하는 부분이므로 상위 개념의 최소한의 정보만 알 필요가 있다고 생각했다
즉 상위 개념 Merchant (pk = merchant_id) 는 하위 개념들이 알고 있을 필요가 있지만
상위 개념 Merchant 는
merchant_contract(pk="merchant_contract_id",
merchant_bank_account(pk="merchant_bank_account_id"),
merchant_transaction(pk="merchant_transaction_id")
위 정보를 알 필요가 없다고 생각이 들었다
여기서도 객체지향 원칙이 한가지 추가되는 것이 있다
바로 '단일 책임 원칙' 이다. 정규화에 의해 독립적인 테이블로 만들면 위 원칙은 자동으로 지켜질 것이다
이외에도 관심사 분리, 확장성 측면에서 모두 더 나은 설계 방향을 만들어 줄 것이다.
추가적으로 JPA 를 사용한다면 어플리케이션에서 조회 편의성을 위해 양방향 관계를 지정할 수 있다.
@OneToOne(mappedBy = "merchant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private MerchantContract contract;
@OneToMany(mappedBy = "merchant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<MerchantBankAccount> accountList = new ArrayList<>();
@OneToMany(mappedBy = "merchant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<MerchantTransaction> merchantTransactionList = new ArrayList<>();
위 설정을 하면 merchant 테이블에 실제 컬럼은 생기지 않지만 JPA 를 사용하여 편하게 조회를 할 수 있다
3. 결론
테이블 설계를 하면서 얻게된 교훈은 아래와 같다
- 이론과 실무의 균형: 객체지향 원칙이 DB 설계에도 적용된다
- 팀 역량 고려: 기술적 이상과 현실적 제약의 조화
- 도메인 이해의 중요성: 비즈니스를 모르면 좋은 설계 불가능
테이블 설계시 아래 부분을 기본적으로 알면 좋은점은 아래와 같다.
- 확장성 방면
- FK 사용 지향
- 정규화 진행(필수!)
- 의존성 방면
- 하위 -> 상위 의존성 방향 사용
- 하위가 상위 FK 를 가지고 있는다
- Cascade 로 상위 삭제시 하위 삭제
좋은 설계를 지키기 위해서 기억해야 할 것은 도메인을 잘 이해한 후 정규화된 테이블만 잘 설계 하여도 자연스럽게 여러 문제가 해결 될 것으로 생각한다
잘 정규화된 설계는 각 테이블의 인덱스가 효율적으로 동작하게 만들기 때문에 나중에 쿼리 튜닝 시에도 더 많은 이점을 얻을 수 있을 것이다
이 글을 쓰며 얻게된 또 한가지 교훈은 객체지향은 대단하다이다.
객체지향은 여러 상황에대입이 되며, 좋은 설계를 할 수 있게 도와준다.
참 신기하다.. 이렇게 객체지향을 알게 모르게 잘 활용하고 있지만, 막상 실무에서 적용을 하려고 하면 잘 생각이 나지 않는다...ㅠ
필자는 아직 객체지향에 20% 도 이해하지 못한 것 같다...
객체지향은 알면 알수록 미궁에 빠지고 더 어려워지는 것 같다.
위 단점이 많은 객체지향 언어를 사용하는 개발자들이 객체지향에 끌리는 이유 라고 생각을 한다.
5. Q & A
1. 고수준 개념이 하위 정보를 한번에 다가지고 있으면 안돼나?
가끔가다가 1개의 테이블에 모든 컬럼의 몰아넣어 컬럼이 100개가 넘는 경우를 본적이 있다
위 경우를 보면 정규화가 전혀 되어있지 않다
위 같은 경우 정보를 몰아 넣기에 한 컬럼에 여러개의 값이 들어갈 수 있다
이는 ACID 원칙 중에 A 원칙 원자성을 위반하게 된다
그리고 확장성이 제한된다
확장에 자유로운 설계가 되어야하는데 한군데 집약적인 설계로 시작을 하면 나중에 확장에 단계가 왔을 때 많은 코드를 수정해야하는 상황이 발생할 것이다
2. 고수준 개념의 테이블이 저수준 개념의 하위 테이블 key 를 가지고 있어도 되지 않나?
위 사례의 경우 1:N 관계를 표현할 수 없다
1:1 매칭만 가능하기에 sql 을 직접 사용하여서 조회를 해야할때 많은 불편을 겪을 것이다
두번째로는 참조 무결성에 대한 복잡성이다
상위가 하위 key 를 가지고 하위가 상위 key 를 동시에 가진다면 양방향 참조로 인한 복잡성이 더 커질 것이다
이에 따른 비용이 더 많이 들 것으로 생각한다
이에 따른 확장성도 당연히 제한이 될 것이다