티스토리 뷰

이전 포스팅에서 id 프로퍼티를 어느 위치에 선언하는게 좋을지 얘기해봤다. 이번엔 Long 과 Long? 중 어떤걸로 선언할지, 어떤차이가 있는지, 그리고 왜 이런 고민을 하게되는지 얘기해보려한다.

 

# Long? vs Long

@Entity
@Table(name = "person")
class Person(
    @Column(name = "person_name")
    val name: String,

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id")
    val id: Long? = null
)

id 값의 생성을 DB 에 위임하는 IDENTITY 방식을 주로 사용할텐데 이 방식을 사용하면 엔티티가 영속화되기 전에는 id 가 null 이기 때문에 nullable 타입으로 선언해야한다. 이 방식이 가장 자연스러운 방식인데, 코틀린 같은 경우 null 에 대한 핸들링이 자바에 비해 강력하다보니 영속화 이후 엔티티를 사용할때마다 id 를 사용할때 !! 같은 문법을 써야하는게 마음에 걸린다.

val personId: Long = person.id!!

보통의 애플리케이션에서 엔티티를 조회해서 요청에 대한 응답으로 id 를 이용하는 경우는 비일비재하기 때문에 위에 대한 고민은 누구나 한번쯤 해봤을 것이다.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "person_id")
val id: Long = 0,

위와 같이 id 타입을 Long 으로 선언하고, 영속화되기 전까지 0으로 초기화 해 놓으면 위 문제는 해결할 수 있다. 코틀린코드가 자바로 컴파일될때 Long? 은 Long(wrapper type)으로, Long 은 long(primitive type) 으로 다루기 때문에 자바로 치면 id 를 long 으로 선언한 것과 같은 의미이다. 이런식으로 선언하면 !! 를 써야하는 문제는 피할 수 있지만 0 으로 초기화해놓는게 가슴 한켠이 찜찜할 수 있다.

 

# 왜 이 고민이 시작되었는가

자바로 엔티티를 사용할땐 아무런 이슈가 없었는데 코틀린으로 오니 이런 고민이 시작된다. 그래서 코틀린의 이슈가 아닌가 생각할 수 있지만 사실 이 문제는 자바에도 동일하다. id 필드가 영속화되기 전에는 null 이고 영속화 된 이후에 값을 갖는 것이다. 다만 자바는 nullable 에 대한 핸들링을 타입차원에서 지원하지 않았기에 크게 와닿는 문제가 아니었는데 코틀린은 nullable 을 타입차원에서 지원하면서 null 초기화 가능 여부에 따라 타입 자체가 달라지기에 이슈가 붉어지게됐다.

 

# ID generation

이 문제는 처음 생성되는 엔티티의 id 를 왜 null 로 초기화하는지부터 고민해봐야한다. id 를 null 로 초기화하는 이유는 DB 에 저장될때 DB 정책에 따라 id 를 순차적으로 부여하기 위해서다. 그럼 id 생성의 역할을 왜 DB 로 위임할까? 애플리케이션 서버의 경우 멀티 인스턴스 환경으로 구성된다. 이때 id 는 글로벌하게 유니크가 보장되어야 하는데, 보통 애플리케이션 서버를 다중으로 구성하는 경우에도 DB 는 replica 로 고가용성을 지원하면서 master 를 1대만 두기때문에 1대의 master DB 에서 순차적으로 id 를 부여하면 절대 유니크함이 보장되기 때문이다. DB 가 1대인 상황에서는 가장 간단하면서도 속편하게 유니크함을 보장할 수 있다.

 

하지만 엔티티는 식별자를 통해 동일성을 검증한다. 고로 DB(좀 더 추상적으로하면 DBMS 가 아니라 어떤 Data Store라도)에 저장되지 않았다는 이유로 식별자인 id 가 없어선 안된다. 즉 엔티티가 생성되는 시점에 식별자가 부여되어야한다. id 를 생성하는 것도 도메인 로직이며 애플리케이션에서 id 를 생성하고, 엔티티 생성 시점에 전달되어야 하는 것이다.

val id: PersonId = personRepository.generateId()
val person = Person(id, "LichKing")
personRepository.save(person)

위와 같은 형태가 되면 엔티티에는 id 에 null 이 들어가는 순간이 아예 사라진다. 그러면 더 이상 id 를 nullable 로 두지 않아도 되고, Long 을 사용할 필요도 없다. 좀 더 의미를 살린 PersonId 라는 타입을 이용할 수 있게 된다.

 

DB 에 id 생성을 위임하는게 가장 속편한건 맞다. 하지만 서비스가 확장되며 샤딩같은 구조를 고민하게 되는 시기가 오면 이 속편한 구조도 문제가 된다. DB 조회 트래픽 분산을 캐시나 slave 로 견딜 수 있을 때까지는 master 가 1대여서 문제가 없지만 샤딩은 write 트래픽도 분산되기 때문에 더 이상 DB 의 자동증가만으로 유니크함을 보장할 수 없기 때문이다. IDENTITY 전략으로 id 를 생성하다가 샤딩을 도입하게 되면 애플리케이션 전반에 걸친 대대적인 구조 변경이 필요하게 될 것이다.

 

# 결론

지난 번 글에서는 그래서 어디에 어떻게 id 프로퍼티를 선언하라는건지 내 생각을 명확히 밝혔었다. 하지만 이번 글은 이런저런 문제제기와 왜 이런 고민을 하게 되었는지에 대해서는 밝히면서도 결론을 정하기가 쉽지 않다. 내 결론은 id 생성 로직을 애플리케이션 레이어로 끌어올리는게 좋다고 생각하지만 이 구조를 바꾸기엔 쉽지 않아 보인다.

 

id 생성 전략을 바꾸지 않는 한도에서는 id 타입을 Long? 이 아니라 Long 으로 하거나, id 를 사용하는 부분에서 !! 연산자 사용, 아니면 전 애플리케이션 레이어에서 id 만큼은 Long? 으로 통일하는 방안이 있을 것 같다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함