식별자란,
식별자(識別子, identifier)는 어떤 대상을 유일하게 식별 및 구별할 수 있는 이름을 뜻한다. 식별자는 정보를 다루는 모든 체계에서 내부적으로 사용되는데, 정보를 처리하기 위해서는 그 정보를 가리킬 방법이 있어야 하기 때문이다. 라고 위키백과에서 얘기한다.
그럼 이걸 우리가 왜 알아야하고 이 글을 왜 쓰고 있는지 궁금할 것이다. 객체에 식별자를 둠으로써 우리는 그 객체가 같은 객체인지 구분을 할 수 있다. 그러면 코드상에서, 로직을 수행하기에 아주 편리하다. 특히 충분히 변경가능한 상태를 가진 객체들에게는 식별자는 아주 유의미하다.
식별자를 그럼 뭐로 하는 것이 좋을까?
일단 현실 세계에 대입해보자, 우리를 식별하기 위해 여러가지 정보가 있다. 가장 먼저 생각나는 것이 나의 주민등록번호이다. 그리고 지문도 있을테고, 휴대폰 번호, 이메일 주소, 닉네임, 얼굴 등등 아주 많은 나를 구분할 수 있는 것이 있다. 그 중 어떤 것이 좋을까? 하나씩 살펴보자
- 주민등록번호는 절대 바뀔 일이 없다. 그리고 전산 오류가 나지 않는 이상 다른 사람의 정보와 겹칠 일도 없다.
- 휴대폰 번호는 변경될 수 있다. 그리고 내가 바꾼 번호를 누군가가 나중에 사용할 수도 있다. 이래서 식별자로 사용하기는 어렵다.
- 이메일 주소 또한 변경될 수 있다. 내 이메일 주소는 unique 하겠지만 만약 gmail을 사용하다 naver로 사용하면 이전의 나와 지금의 나는 다른 사람이 되버린다.
- 닉네임, 지금의 5기는 닉네임이 unique하다 하지만 4기와 비교했을 때 오리 처럼 겹치는 닉네임이 있다. 그럼 얘도 패스
- 얼굴, 얼굴도 사실 변경가능성이 있다. 성형한다면 성형하기전 나와 성형한 나는 같은 사람이다. 얘도 패스
이렇듯 식별자를 정하는 것은 아주 어렵다. 그래도 식별자가 많으면 많을수록 구분하기 편하지 않을까 라는 생각을 했다. 주민등록번호와 id 값을 둘 다 식별자로 가져, id로 비교해도 되고, 혹은 주민등록번호로 비교해도 되면 좀 더 편하고 유연하게 로직을 풀 수 있을 것 같았기 때문이다.
현실 세계에서는 그렇게 사용할 수도 있을 것이다. 주민등록번호로도 비교하고 주민등록번호가 없다면 운전면허번호로 식별할 수 있을 것이다. 하지만 Java에서는 불가능하다. 일단 java에서 식별자임을 나타내고, 식별자를 제대로 사용하기 위해서는 equals & hashcode를 재정의해줘야한다. IDE의 도움을 받아 생성을 하고 두개의 필드 중 하나를 비교하여 식별자로 사용하려는 코드를 작성해보자
public class Order {
private final Long id;
private final OrderNumber orderNumber;
...
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Order order = (Order) o;
return Objects.equals(id, order.id) || Objects.equals(orderNumber, order.orderNumber);
}
}
이런 방식의 코드라면 id가 같거나, 주문번호가 같다면 true를 반환한다. 즉 둘 중 하나라도 만족한다면 true를 반환한다. 그리고 만약 다른 식별자가 추가될 때마다 넣어주면 되는 것이다. 뭔가 간단하게 구현한 것 같다. 이렇게 된다면 id와 orderNumber를 둘 다 식별자로 사용할 수 있을 것만 같다. 하지만 여기에는 심각한 오류가 있다. id는 다르지만 orderNumber가 같다면 같은 객체로 볼 수 있을까?, orderNumber는 다르지만 id가 같다고 같은 객체로 볼 수 없다. 그러기에 java에서는 이러한 여러 식별자를 각각 equals를 구현하는 것은 불가능하다.
결국 식별자는 1개 그럼 어떤게 좋을까
그렇다면 식별자는 하나만 재정의할 수 있다. 그럼 어떤 식별자가 가장 좋을 것인지 선택해야한다. 장바구니 미션의 Order 기준으로 살펴보자
- id
- Order Number
이정도가 있을 수 있다.
먼저 id는 어떻게 생성할 수 있나. 지금 만든 서비스를 보면 id는 Database의 id에 의존한다. 이것이 가장 큰 단점이자 장점이다. 먼저 id가 DB를 의존한다면 이 도메인이 DB를 사용하지 않는 경우에 바로 문제가 될 수 있다. 현재 데이터가 map에 저장된다거나, 여러 다른 저장 방식에 따라 id가 자동으로 생성되지 않을 수가 있다, 그래서 그런 상황에 따라 수정을 해주거나 id를 생성하는 로직을 추가해야하는 것이다. 장점으론 아주 편하다는 것이다. 내가 신경쓰지 않아도 id를 알아서 만들어준다.
그리고 order number를 생각해보자. 장점으로는 db를 의존하지 않는다. 그리고 식별자를 위해 불필요한 필드가 생기지 않는다. 단점으로는 일단 귀찮다. 내가 직접 신경써서 만들어줘야하기 때문이다. 그리고 주문 번호라는 것이 평생 unique할 것이라는 확신이 있어야한다. 그리고 평생 이 필드는 사라지지 않을 거란 보장도 필요하다.
그럼 여기서 단점과 장점들을 보고 어떤 식별자가 좋은 식별자인가에 대해 생각해보자
- 외부에 의존하지 않는다
- 생성하기 편하면 좋다
- 식별자가 평생 unique라는 것이 보장되어야한다
- 식별자에 대한 정보는 절대 사라지지 않을거란 것이 보장되어야한다.
이것들을 조합하면 1번을 읽어보면 일단 내가 제어할 수 있어야 할 것이다. 그리고 생성을 편하게 하는 방법은 쉽게 식별자를 생성할 무언가가 있다면 좋을 것이다. 그리고 3,4번의 생각을 조합해보자면 아무 의미가 없어야한다는 것이다. 왜냐면 그 필드가 의미가 있다면 변경의 여지도 있고 삭제될 여지가 있다고 생각한다.
이 모두를 만족시킬 방법을 생각해보자. YYYY-MM-DD-SS-sss + random 와 비슷한 방법을 통해 unique를 만드는 것이다. 하지만 이러한 방법이 괜찮을까? 일단 1번은 통과, 2번도 통과, 3번은 애매하다. 만약 내가 엄청 거대한 서비스의 개발자라고 상상해보자, 같은 초에 엄청나게 많은 요청을 받을 때 문제가 생긴다. 컴퓨터에서의 random은 진짜 랜덤이 아니기 때문이다. 그래서 위와 같은 방법은 불가하다. random을 제어할 수 없기 때문이다.
결국 random을 쓰지않고는 불가능하다.
그럼 어떤 것을 사용하는 것이 좋을까 절대 중복되지 않는 난수 vs 순차적으로 증가하는 수 둘 중 어떤 것을 만드는게 쉬울까?
내 생각엔 순차적으로 증가하는 수가 훨씬 쉬워보인다. 만약 DB에 의존하고 싶지 않다면 우리가 순차적으로 증가하는 수를 만들어 넣어주면 되는 것이다.
그렇다고 현실세계처럼 비교할 수 있는게 id밖에 없어 불편할 수 있을까? 그래도 적당히 타협할 수 있다. 확실히 id로는 비교하지만 findByEmail(), findByNickname(), findByPhoneNumber() 와 같이 unique한 필드로 로직을 풀기에 도움을 받을 수 있다.
결론
식별자는 많으면 아주 편리할 것 같다. 하지만 식별자를 정하는데 중요한 조건들이 있다. 식별자를 사용하기에 적합한 것은 아무 의미 없는 무언가이다. 하지만 그 무언가를 생성하는데 가장 쉬운 방법은 순차적으로 증가하는 숫자이다. 하지만 식별자가 하나라고 힘든 부분은 아니다. 편리성을 위해 다른 unique한 필드를 사용할 수 있다. 하지만 그 필드가 나중에 바뀌거나 unique하지 않게될 때까지 해당 정보로 다른 로직을 풀 수 있다.
댓글남기기