
엔티티(Entity)와 값 객체(Value Object)란 무엇인가?
Entity와 Value Object 개념은 에릭 에반스의 DDD 책에서 나왔다고 한다.
이에 대해 소개한 마틴 파울러의 글이 있는데, 이 글에 따르면 엔티티와 값 객체의 정의는 다음과 같다.
Entity: Objects that have a distinct identity that runs through time and different representations. You also hear these called "reference objects."
- Entity: 시간과 다양한 표현을 거치더라도 식별할 수 있는 고유성(identity) 을 가진 객체. "Reference Object"라고도 부른다.
Value Object: Objects that matter only as the combination of their attributes. Two value objects with the same values for all their attributes are considered equal.
- Value Object: 오직 속성들의 조합만이 의미를 가지는 객체. 모든 속성이 같다면 동일한 것으로 간주된다.
기술적 영속성과 논리적 영속성은 다르다
에릭 에반스가 이야기하는 Entity란, 시간이 지나고 표현이 달라져도 여전히 식별 가능한 객체를 뜻한다.
여기서 중요한 포인트는 다음과 같다:
- through time: 단순히 DB에 저장됐다는 뜻이 아니다.
- different representations: 객체의 외형이나 상태가 바뀌어도 그 정체성은 유지되어야 한다.
즉, DB에 저장되든 말든 상관없이, 메모리 상에만 존재하든, 파일에 저장되든, 항상 식별 가능한 상태여야 한다.
- 기술적 영속성: DB나 파일 등에 저장되어 오래 살아남는 것. (Persistence Layer concern)
- 논리적 영속성: 시간과 상태 변화를 지나면서도, 이 객체가 "그 객체"라는 것을 유지하는 것.
Entity의 "through time and different representations"은 "논리적 식별자의 지속"을 의미한다고 볼 수 있다. DB 저장 여부는 기술적 문제일 뿐이다.
우리가 만든 객체들은 엔티티였을까?
위와 같이 엔티티와 값 객체에 대해 알고 난 뒤, 큰 의문이 생겼다.
우테코 레벨 1 과정을 진행하며 우리는 수많은 객체를 만들었다.
그 객체들은 모두 엔티티였을까, 아니면 값 객체였을까?
- 대부분 id가 없었으니 모두 값 객체였던 것일까?
- 그러나 값 객체는 원칙적으로 모든 필드가 불변(immutable)이어야 한다.
- 그런데 우리가 만든 객체들 중에는 필드가 변할 수 있는 객체도 있었다.
이 점에서 엄청난 혼란이 생겼다. 끄아아아악. 그리고 그 혼란은 아직도 끝나지 않았다.
장기 미션을 돌아보며
장기 미션을 돌아보면, 나는 Piece
객체를 만들었다.
PieceType
과Position
을 가졌고,Position
은 명백히 값 객체라서 record로 만들었다.- 하지만
Piece
는 record로 만들지 않았다.
그 이유는, 같은 타입과 같은 위치를 가진 기물이 실제로 두 개 존재할 수 있었고, 우리는 실제로 그 둘을 구분해야 했기 때문이다.
그러나 지금 돌아보면 이 판단은 완벽하지 않았다.
엔티티와 값 객체는 우리가 정하는 게 아니다
엔티티인지, 값 객체인지는
프로그래머가 임의로 정하는 것이 아니라, 비즈니스 요구사항이 정한다.
- 장기 미션에서는 같은 포지션, 같은 타입의 말을 서로 구분할 필요가 없었다.
- 콘솔 UI 상의 장기판에서는 같은 타입의 두 개의 말을 동일하게 출력하면 됐다.
- 비즈니스 요구사항 상 식별성이 필요 없었다.
따라서 장기 미션에서 Piece
는 엔티티가 아니라, 값 객체로 취급하는 것이 맞았다.
만약 식별이 요구되었다면?
만약 체스 미션에서
- "룩1"과 "룩2"를 구분해야 했고,
- 보드판에서도 별도로 표현해야 했다면,
그때는 명시적인 식별자(id)를 부여해야 했을 것이다.
자바에서는 객체 참조값(메모리 주소)로 구분할 수 있지만,
이는 기술적 식별자일 뿐, 비즈니스 세계의 논리적 식별자는 아니다.
참조값에 의존하는 구분은 깨지기 쉽고, 비즈니스 변화에 취약하다.
따라서 비즈니스 상 식별이 필요했다면, 명시적인 논리적 식별자(id)를 추가했어야 한다.
참조값을 통해 객체를 구분한다?
그러면 또 의문이 생긴다.
- 자바에서는 식별자가 없어도 equals를 재정의하지 않았다면 서로 다른 객체로 취급하지 않나?
- 그렇다면 참조값만으로도 서로 다른 객체를 식별할 수 있으니 엔티티로 봐도 되는 것 아닐까?
예를 들어 다음과 같은 코드를 통해 객체들을 서로 식별할 수 있었다.
RookPiece rookPiece1 = new RookPiece();
RookPiece rookPiece2 = new RookPiece();
List<Piece> pieces = new ArrayList<>();
pieces.remove(rookPiece1);
pieces.find(rookPiece2);
자바에서는 참조값(메모리 주소)으로 객체를 구분할 수 있지만, 이것은 언어 차원의 임시 식별성일 뿐이다.
비즈니스 세계에서 요구하는 논리적 식별자가 아니다.
참조값은 불안정하다. (시스템을 다시 시작하면 깨진다.)
따라서 참조값은 runs through time and different representations을 만족하는 식별자가 아니다.
비즈니스 로직 상 구분이 필요했다면, 참조값이 아니라 명시적 식별자(id)를 추가했어야 한다.
내가 만든 Piece는 무엇일까?
내가 만든 Piece
는 equals, hashCode 재정의를 하지 않았고, 위의 예시처럼 참조값을 통해 구분할 수 있었다.
만일 참조값으로만 구분했다면, Piece는 엔티티도, 값 객체도 아니었다.
값 객체는 "값"으로 동등성을 판단하는 것이 본질인데, 나는 단지 참조값으로 객체를 구분했을 뿐이다.
따라서, 내가 만든 Piece는 아직 역할이 결정되지 않은 미완성 객체였다.
정확히 말하자면, 비즈니스 상 값 객체여야 했지만, 엄격한 불변성 및 동등성(equals/hashCode) 규칙을 따르지 않았다.
엔티티 클래스의 id는 null을 허용해도 될까?
아래 내용은 어디까지나 현재 나의 생각이다. 앞으로 미션을 진행하거나 새로운 지식을 습득하면서 달라질 수 있다.
문제 상황
Reservation이라는 도메인 엔티티를 만들면서,
id 값을 데이터베이스의 auto-increment 기능을 통해 부여할 예정이었다.
이때 "엔티티의 id 값에 null을 허용할 것인가?"라는 문제에 부딪혔다.
기존 주장
"엔티티의 id가 null이라는 것은 식별할 수 있는 고유성이 충족되지 않은 상태를 의미한다. 따라서 id가 null인 엔티티를 만드는 것은 문제가 된다."
이러한 관점은 충분히 설득력이 있었다.
나의 생각
하지만 나는 엔티티 클래스에서 항상 완성된 엔티티 인스턴스를 만들어야 한다고 생각하지 않는다.
- id가 null인 상태는 단순히 "아직 식별자가 부여되지 않은 상태"일 뿐이다.
- 이는 "식별 예정 상태"로 이해할 수 있다.
엔티티 객체의 본질은
"논리적 식별성(identity)을 가진다"는 것이지,
"항상 즉시 식별자가 채워져 있어야 한다"는 것은 아니다.
따라서,
- id가 null인 것은 해당 인스턴스가 아직 완성된 식별성을 가지지 못한 상태를 의미할 수 있다.
- 그러나 '엔티티로서의 성격' 자체를 부정하지는 않는다.
중요한 것은 객체의 설계 의도이고 엔티티는 기술적 개념이 아니라 논리적 개념이다.
id가 null인 기술적인 상태는 일시적 현상에 불과하다.
- 엔티티는 생성 시점에 id가 null이어도 될 수 있다.
- 실제 인스턴스가 논리적 식별자를 갖추었을 때 비로소 완성된 엔티티로 취급할 수 있다.
- id가 부여되지 않았더라도, 논리적 식별성을 전제로 만들어진 객체라면 "완성되지 않은 엔티티"일 뿐이다.
- 중요한 것은 설계 의도이며, id null은 일시적인 문제일 수 있다.
예시
Member member = new Member(); // id == null (비영속 상태)
member.setUsername("회원1");
// 아직 DB에 저장되지 않았으므로 id는 여전히 null
이러한 미완성 상태를 허용할지 여부는 결국 객체의 일관성을 얼마나 엄격히 관리할 것인가라는 설계상의 문제다.
어떤 시스템은 철저한 방어적 프로그래밍을 통해 id null을 절대 허용하지 않을 것이고, 어떤 시스템은 실용성을 위해 허용할 수도 있다.
그렇다면 왜 다른 필드는 null을 허용하지 않으면서 id는 허용하는가?
이에 대해 나는 다음과 같이 생각한다.
- 엔티티는 id를 직접 생성하거나 관리할 책임이 없다.
- id가 어떻게 생성되고 부여되고, 식별하는지는 외부(저장소, 시스템 등)의 책임이다.
- 엔티티의 주된 책임은 핵심 비즈니스 로직을 수행하는 것이며, 이 책임들은 id가 null이어도 수행할 수 있다.
- id는 엔티티가 외부에 식별자로서 제공해야 할 단순한 값일 뿐이다.
- 핵심 로직 수행 자체는 id 없이도 가능해야 한다.
즉, 객체가 생성된 직후
- 모든 외부 맥락에서 완전히 행동할 준비가 되어 있어야 하는 것은 아니다.
- 객체의 기본적인 책임은 내부 상태를 일관되게 유지하고, 필요한 시점에 식별자(id)를 제공하는 것이다.
- 이때 id가 null이라면, 단순히 "아직 식별 예정 상태"로 이해할 수 있다.
따라서 id가 null인 것을 허용하는 것은 결점이 될 수 있지만, 엔티티의 다른 필드들과 다르게 충분히 의식적 설계에 따라 타협 가능한 영역이다.
결론
id가 null인 엔티티 인스턴스를 허용하는 것은
- 도메인 일관성을 약화시킬 위험이 있지만,
- 기술적 현실과 편의성 때문에 타협적으로 허용할 수도 있다.
이러한 허용 여부는 "방어적 프로그래밍 수준"이나 "개발 팀의 정책"에 따라 달라질 문제라고 나는 생각한다.
'개발 > 우아한 테크코스' 카테고리의 다른 글
Level 1 장기 미션 - TDD의 시작점? (0) | 2025.03.24 |
---|
