본 포스트 시리즈는 「도메인 주도 설계 철저 입문」책을 요약한 내용입니다.
이전 글 보기
02장 시스템 특유의 값을 나타내기 위한 '값 객체'
(2) 값 객체의 장점
값 객체의 장점 4가지
값 객체를 도입하면 다음과 같은 4가지의 장점이 있다.
- 표현력이 증가한다.
- 무결성을 보장할 수 있다.
- 잘못된 대입을 방지할 수 있다.
- 로직을 한 곳에 모아둘 수 있다.
1) 표현력이 증가한다.
원시타입으로 정의한 제품번호와, 값 객체로 표현한 제품번호를 비교해보자. 다음은 원시타입인 String으로 정의한 제품번호이다. 실제로 많은 정보를 담고 있는 제품번호임에도, 그 의미를 파악하기 어려워진다.
String modelNumber = "a120423-100-1";
다음은 값 객체로 나타낸 제품 번호이다. 클래스를 통해 제품번호의 의미를 더 자세히 알 수 있다. 값 객체는 정의를 통해 자신이 무엇인지에 대해 자기 문서화 한다.
public class ModelNumber {
// 필드: productCode, branch, lot
private final String productCode;
private final String branch;
private final String lot;
// 생성자
public ModelNumber(String productCode, String branch, String lot) {
if (productCode == null) throw new IllegalArgumentException("productCode cannot be null");
if (branch == null) throw new IllegalArgumentException("branch cannot be null");
if (lot == null) throw new IllegalArgumentException("lot cannot be null");
this.productCode = productCode;
this.branch = branch;
this.lot = lot;
}
// 필드를 문자열로 변환
@Override
public String toString() {
return productCode + "-" + branch + "-" + lot;
}
// 메인 메서드 (테스트용)
public static void main(String[] args) {
ModelNumber modelNumber = new ModelNumber("1234", "A", "001");
System.out.println(modelNumber); // 출력: 1234-A-001
}
}
2) 무결성을 보장할 수 있다.
'사용자명은 3글자 이상이어야 한다'는 규칙을 보장하기 위해서는 이름을 String으로 정의하는 것은 바람직하지 않다. String에는 언제든지 규칙을 위반하는 값이 들어갈 수 있기 때문이다. 따라서 시스템상 유효한 값만 허용하기 위해서는 fullName은 class로 정의되는 것이 좋다.
public class UserName {
// 필드: value
private final String value;
// 생성자
public UserName(String value) {
if (value == null) throw new IllegalArgumentException("value cannot be null");
if (value.length() < 3) throw new IllegalArgumentException("사용자명은 3글자 이상이어야 함");
this.value = value;
}
// 값 반환 메서드
public String getValue() {
return value;
}
}
3) 잘못된 대입을 방지할 수 있다.
다음은 사용자를 생성할 때 사용자 이름을 받아서 이를 사용자 ID에 대입하는 실수를 보여준다.
public class User {
// 필드: id
private String id;
// id 설정 메서드
public void setId(String id) {
this.id = id;
}
// id 반환 메서드 (선택사항)
public String getId() {
return id;
}
// CreateUser 메서드
public static User createUser(String name) {
User user = new User();
// name을 받아 id에 세팅하는 논리적 오류
user.setId(name);
return user;
}
}
이러한 실수를 방지하기 위해서 UserId와 UserName 클래스를 각각 만든 뒤 이를 사용해 User를 정의하면 다음과 같다.
public class UserId {
// 필드: value
private final String value;
// 생성자
public UserId(String value) {
if (value == null) {
throw new IllegalArgumentException("value cannot be null");
}
this.value = value;
}
}
public class UserName {
// 필드: value
private final String value;
// 생성자
public UserName(String value) {
if (value == null) {
throw new IllegalArgumentException("value cannot be null");
}
this.value = value;
}
}
public class User {
// 필드
private UserId id;
private UserName name;
// Getter 및 Setter 메서드
public UserId getId() {
return id;
}
public void setId(UserId id) {
this.id = id;
}
public UserName getName() {
return name;
}
public void setName(UserName name) {
this.name = name;
}
}
위와 같이 User를 UserId와 UserName 클래스를 활용해 정의하고 나면 실수를 했을 때 컴파일 에러가 발생한다. 따라서 자연스럽게 논리적 오류를 방지해준다.
public class Program {
// User 생성 메서드
private User createUser(UserName name) {
User user = new User();
UserId userId = name; // 컴파일 에러 발생
return user;
}
}
4) 로직을 한 곳에 모아둘 수 있다.
다음은 값 객체를 사용하지 않고 로직이 여기저기 흩어져 있는 예시다. User 클래스는 원시적 타입을 사용해서 이름을 정의하였고, '사용자 명은 3글자 이상이어야 한다'라는 규칙을 사용자 생성 및 수정시에 강제하고 있다.
public class User {
// 필드: name
private String name;
// 생성자
public User(String name) {
this.name = name;
}
}
// 사용자 생성 메서드
public void createUser(String name) {
if (name == null) {
throw new IllegalArgumentException("name cannot be null");
}
if (name.length() < 3) {
throw new IllegalArgumentException("사용자명은 3글자 이상이어야 함");
}
User user = new User(name);
}
// 사용자 업데이트 메서드
public void updateUser(String id, String name) {
if (name == null) {
throw new IllegalArgumentException("name cannot be null");
}
if (name.length() < 3) {
throw new IllegalArgumentException("사용자명은 3글자 이상이어야 함");
}
}
반면 값 객체를 사용해 User를 정의하면 '사용자명은 3글자 이상이어야 한다'라는 규칙을 값 객체 내부에 모아둘 수 있다. 먼저 userName을 String이 아닌 UserName이라는 클래스를 이용해 정의한 모습이다.
class UserName
{
private readonly string value;
public UserName(string value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
if (value.Length < 3) throw new ArgumentException("사용자명은 3글자 이상이어야 함", nameof(value));
this.value = value;
}
}
createUser와 updateUser는 다음과 같다.
public class Program {
// 사용자 생성 메서드
public void createUser(String name) {
// UserName 객체 생성
UserName userName = new UserName(name);
// User 객체 생성
User user = new User(userName);
}
// 사용자 업데이트 메서드
public void updateUser(String id, String name) {
// UserName 객체 생성
UserName userName = new UserName(name);
}
}
요약
값 객체의 장점 1. 표현력이 증가한다 : 클래스 정의를 통해 자기자신이 무엇인지에 대해 자기문서화 한다. 2. 무결성을 보장할 수 있다 : 원시타입으로 정의할 때와 달리 생성자 등을 통해 특정 규칙을 보장할 수 있다. 3. 잘못된 대입을 방지할 수 있다 : 의미적으로나 논리적으로 값을 알맞게 대입할 수 있게 보장할 수 있다. 4. 로직을 한 곳에 모아둘 수 있다 : 클래스 내부에 로직을 모아두면 수정사항이 생겨도 클래스만 고치면 된다. |
'DDD > 도메인 주도 설계 철저 입문' 카테고리의 다른 글
도메인 서비스란? - 「도메인 주도 설계 철저 입문」 4장 (2) | 2024.09.30 |
---|---|
엔티티의 생애주기와 도메인 객체 - 「도메인 주도 설계 철저 입문」 3장 (2) (0) | 2024.09.11 |
도메인 엔티티란? - 「도메인 주도 설계 철저 입문」 3장 (1) (1) | 2024.09.09 |
값 객체란? - 「도메인 주도 설계 철저 입문」 2장 (1) (2) | 2024.09.04 |
도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 1장 (7) | 2024.09.02 |