본문 바로가기

우아한테크코스

스탬프크러쉬 테이블 구조 개선기

레벨 3 때 우리 팀은 테이블 구조를 바탕으로 엔티티를 만들기 위해 테이블 설계부터 먼저 진행했다.

구현할 기능이 많았고 다들 프로젝트가 처음이었기에 여러 의견을 나누면서 테이블을 설계했었다. 

그렇게 총 20개의 테이블이 나오게 되었다. 

 

 

그리고 기능을 구현하면서 테이블 구조를 변경할 필요성을 느끼게 되었다.

구조 상 큰 변경이 있던 두가지 경우에 대해서 이전의 테이블 구조가 나오게 된 배경과 왜 바꾸게 되었는지의 내용을 정리해보았다. 

Customer 테이블 상속 구조 제거

우리 서비스는 고객의 편리한 적립을 위해 전화번호만으로 임시 회원으로 가입이 되어 스탬프 적립이 가능한 기능을 제공하기 때문에 Customer 테이블을 Register 와 Temporary 로 나눌 필요성이 있었다. 그때 한창 다들 JPA 를 공부하고 있었고, Temporary Table 에는 oauth_id, oauth_provider 등의 컬럼 값이 필요 없기 때문에 NULL 값이 들어가는 걸 피하기 위해 상속 관계 매핑을 사용했고 Join 전략을 사용했었다. 

 

5차 데모데이 기간동안 임시회원 <-> 가입회원 간 데이터 연동이 필요해지면서 임시회원의 레코드를 가입회원의 레코드로 변경해줄 필요가 생겼다. 그런데 기존 테이블 구조를 사용할 시에는 어떤 방법을 써도 데이터 연동 기능을 구현할 수가 없었다. Temporary_Customer 의 정보를 지우고 Register_Customer 에 새로 데이터를 넣으려고 해도 Temporary_Customer 에 외래키가 걸려있기 때문에 삭제가 불가능했다. 그리고 기존 Temporary_Customer 의 정보를 업데이트하려고 해도 Temporary_Customer 에는 oauth_id 등의 컬럼이 없기 때문에 업데이트하는 방법도 사용할 수가 없었다.

 

그래서 상속 관계 매핑은 그대로 사용하고 단일 테이블 전략으로 변경하려 했다. 단일 테이블 전략으로 변경하면 oauth_id 등의 컬럼도 다 존재하기 때문에 업데이트를 하면 될 줄 알았으나..! dtype 의 존재가 변수였다. JPA 는 dtype 을 사용해서 해당 레코드가 Temporary 테이블의 것인지, Register 테이블의 것인지 구분한다. 그리고 dtype 은 필드로 존재하는 것이 아니고 JPA 에서 어노테이션으로 구현이 되어 있기 때문에 코드 상으로 값을 변경할 수가 없었다. 네이티브 쿼리로 변경하는 방법밖에 남지 않았는데.. 네이티브 쿼리로 변경하는 방법은 유지보수성에도 너무 좋지 않고 다른 개발자들이 코드를 읽었을때 전혀 이해하지 못할 수 있다고 생각했다. 

 

@AllArgsConstructor
@NoArgsConstructor(access = PROTECTED)
@Getter
@Entity
@DiscriminatorColumn(name = "dtype")
@Inheritance(strategy = JOINED)
public abstract class Customer {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "customer_id")
    private Long id;
    private String nickname;

    @Column(unique = true)
    private String phoneNumber;

    public Customer(String nickname, String phoneNumber) {
        this(null, nickname, phoneNumber);
    }

    public void registerPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public abstract boolean isRegistered();
}

 

결론적으로 Customer 테이블에서 상속 구조를 빼고 enum 으로 타입을 구분하는 식으로 변경하기로 결정이 되었다. 결정하는 과정에서 코드 상 변경점이 많을 것이라 예상되어 일이 많아질 것을 걱정했다.. 그래서 네이티브 쿼리로 dtype 을 변경할지 살짝 고민을 더 했으나 해당 구조를 계속 안고 갔을때 문제가 또 발생한다면 그때 일이 더 커질 것이라 생각했기 때문에 Customer 테이블의 구조를 바꾸는 것으로 최종 결정을 하였다.

 

그리고 이렇게 깔끔해진 Customer 테이블로 변경이 되었다.

 

테이블 구조를 바꾸고, 기존 데이터를 마이그레이션하고, 코드를 배포하는 과정에서 DDL 형상관리의 필요성을 느껴서 flyway 를 도입했고, 무중단 배포에 대해서 공부할 필요성을 느꼈다.

Coupon 생성 시 Design 및 Policy 복사용 테이블 제거

우리 서비스에서는 쿠폰 정책과 디자인을 바꿀 수 있는 기능을 제공한다. 그리고 해당 쿠폰이 발급되었을 때의 유효기간이나, 리워드 등의 정보는 고객과의 약속이기 때문에 쿠폰은 발급되었을 때 자신의 디자인과 정책을 기억하고 있어야 한다. 그래서 처음에는 coupon 에서 cafe 와, cafe_coupon_design 과 cafe_policy 를 외래키로 가지고 있도록 설계했었다. 

 

cafe_coupon_design - cafe - cafe_policy

            \         |          /

                    coupon

 

그러다보니 이런 모양의 테이블 설계가 되었다. 연관관계를 표시해보자면 아래와 같다. 

 

그림 상 테이블 간 순환 의존이 생긴듯한 모양새여서 이렇게 해도 되나? 싶었다. 우리는 엔티티를 다 객체 참조로 설계할 예정이었기 때문에 혹시라도 순환 의존이 생기면 큰 문제가 벌어질 것 같아서 이 부분을 설계하는데 가장 시간을 많이 썼던 것 같다 (사실 Cafe 에서 연결이 끊기기 때문에 순환 의존이 발생할 일은 없었다)

 

그래서 coupon 을 생성할 때의 cafe_policy 와 cafe_coupon_design 의 값을 coupon_policy 와 coupon_design 에 insert 해주고 해당 coupon_policy 와 coupon_design 은 각각 coupon 과 1:1 관계가 되게끔하는 방식으로 확실하게 의존을 끊어주기로 결정했다.

 

테이블 구조를 결정한 뒤 화이트보드에 다시 정리해본 그림

 

하지만 이런 방식으로 코드를 짜다보니 불편한 점이 생겼는데, coupon 을 발행해줄때마다 cafe_policy 와 cafe_coupon_design 의 값을 꺼내 coupon_policy 와 coupon_design 객체를 생성해서 데이터베이스에 저장해줄 필요가 있었다. 그래서 테스트 코드를 작성할때도 너무 관리해야 할 repository 가 많아져 불편했다. 또 쿠폰 디자인에는 스탬프들의 좌표 데이터를 저장하는 테이블도 엮여있기 때문에 관리해야 할 대상이 더 많았다.

 

또 쿠폰의 정책이 카페의 현재 정책인지 아니면 이전 정책인지 판별해주는 로직에서도 coupon_policy 와 cafe_policy 객체를 비교해줘야 하기 때문에 값을 다 get() 해와서 비교해야 하는 등 번거로운 로직들이 추가가 되었다.

 

우리는 모든 delete 를 soft delete 로 하도록 정했기 때문에 카페의 정책이나 쿠폰 디자인이 변경되면 이전 정책과 쿠폰 디자인은 deleted = true 로만 레코드가 업데이트될뿐 DB 상에서 없어지지 않는다. 그래서 coupon 테이블이 cafe_policy 와 cafe_coupon_design 의 id 를 외래키로 참조하고 있어도 별다른 문제가 생기지 않는다.

 

코드를 작성하면서 느낀 여러가지 불편함과 복사용 테이블을 사용함으로써 생기는 이점이 없다고 판단되어서 복사용 테이블(coupon_design, coupon_policy, coupon_stamp_coordinates) 를 없애기로 팀 내부에서 결정이 되었다!

 

https://github.com/woowacourse-teams/2023-stamp-crush/discussions/238

 

데이터 마이그레이션과 무중단 배포에 대한 학습 커브때문에 진행 대기 상태이다. (최종 데모까지 진행할 예정)