최근 도메인 모델을 설계하다가 흥미로운 구조를 마주하게 되었다. JPA 엔티티에는 당연히 존재하는 id
필드가, 도메인 객체에는 존재하지 않았다. 처음엔 어색했다. "ID 없이 어떻게 동일한 객체인지 판단하지?" 라는 의문이 들었고, 테스트 코드를 보며 이해가 되기 시작했다.
바로 이 지점에서 equals()
와 hashCode()
의 역할을 다시 짚고 넘어가야겠다는 생각이 들었다. 이 글에서는 ID 없이도 equals/hashCode로 도메인 객체를 다룰 수 있는 구조를 예시와 함께 설명한다.
id
가 없다고?다음은 내가 사용 중인 Member
도메인 객체다. 아래와 같이 정의되어 있다.
@EqualsAndHashCode
public class Member {
private final MemberName name;
private final MemberProfileImageKey profileImageKey;
private final MemberAccountStatus accountStatus;
private Member(MemberName name, MemberProfileImageKey profileImageKey, MemberAccountStatus accountStatus) {
this.name = name;
this.profileImageKey = profileImageKey;
this.accountStatus = accountStatus;
}
public static Member of(MemberName name, MemberProfileImageKey profileImageKey, MemberAccountStatus accountStatus) {
return new Member(name, profileImageKey, accountStatus);
}
}
놀랍게도 id
필드가 없다. 하지만 이 도메인 객체는 테스트에서 아무 문제 없이 저장되고 조회된다. 그 비밀은 바로 @EqualsAndHashCode
에 있다.
public class FakeMemberRepository implements MemberRepository {
private final Set<Member> storage = new HashSet<>();
@Override
public void save(Member member) {
storage.remove(member);
storage.add(member);
}
public Optional<Member> findBy(Member target) {
return storage.stream()
.filter(saved -> saved.equals(target))
.findFirst();
}
}
Set은 내부적으로 equals()
와 hashCode()
를 기준으로 중복 여부를 판단한다. 따라서 Member
객체의 동일성을 판단할 수 있는 기준이 필요하고, @EqualsAndHashCode
덕분에 이 기준이 VO 기반으로 정의된다.
이렇게 테스트에서는 id
없이도 객체의 동등성을 비교하며 저장/조회할 수 있다.
만약 우리가 @EqualsAndHashCode
를 빼고 테스트를 했다면, 다음과 같은 문제가 생긴다.
repository.save(member);
Optional<Member> result = repository.findBy(member);
findBy()
로 찾으려는 객체가 같다고 판단되지 않음Set
내부에서 equals()
로 판단하지 못하므로 조회 실패즉, 객체 간 비교 기준을 정의하지 않으면, 테스트나 컬렉션 관리가 불가능하다.
id
는 정말 필요 없는가?사실, 도메인 모델에 꼭 id
가 있어야 하는 것은 아니다. 아래와 같은 상황에서는 ID가 없어도 전혀 문제 되지 않는다.
반대로, 다음과 같은 경우라면 id
를 포함하는 것이 낫다.
id
로 식별되는 객체 (ex. /members/1
)equals()
와 hashCode()
는 객체가 같은지를 판단하는 기준이다.이번 경험을 통해 알게 된 것은, 도메인 모델은 꼭 JPA의 구조를 따라갈 필요가 없다는 점이다. 도메인은 “무엇을 하는가”에 집중하고, 저장소나 식별은 인프라 계층에 맡겨도 충분하다. 그리고 그 중심에는 equals()
와 hashCode()
가 있다.