1. Value Object
•
간단한 값 객체 예시
public final class Color {
public final int r;
public final int g;
public final int b;
public Color(int r, int g, int b) {
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
throw new IllegalArgumentException("RGB should be 0 to 255");
}
this.r = r;
this.g = g;
this.b = b;
@Override
public boolean equals(Object o) {
if (this == 0) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Color color = (Color) o;
return r == color.r && g = color.g && b = color.b;
}
@Override
public int hashCode() {
return Object.hash(r, g, b);
}
}
Java
복사
•
Color 클래스가 값 객체라는 것은 Color 클래스로 만들어진 객체를 숫자 1, 2, 3과 같은 값 또는 리터럴과 같이 볼 수 있다는 의미다.
◦
즉 Color는 객체지만 동시에 값이다. 그래서 값 객체라고 부른다.
•
소프트웨어 관점에서 값의 특징은 다음과 같다. 즉 값의 특징을 만족하는 객체이기 때문에 값 객체라고 불리는 것이다.
◦
불변성: 값은 변하지 않는다. ⇒ 숫자 1은 영원히 숫자 1이다.
◦
동등성: 값의 가치는 항상 같다. ⇒ 값이 가지는 지위는 항상 같다. 즉 모든 숫자 1은 상황이나 맥락에 관계없이 항상 1의 지위를 가진다.
◦
자가 검증: 값은 그 자체로 올바르다. ⇒ 숫자 1은 그 자체로 올바른 숫자. 즉 값을 의심하지 않아도 된다는 의미다.
•
값 객체의 개념을 이해하는 것도 중요하지만 실제 개발할 때 중요한 것은 그래서 이 객체가 값객체인가 아닌가?가 아니다. 값 객체의 목적을 고민해보는 과정이다.
◦
신뢰할 수 있는 객체를 어떻게 만들지, 어떤 값을 불변으로 고정시킬 지, 어디까지 값을 보장함을 이 객체가 책임질지 등을 고민하는 과정이 개발에 더 도움이 된다.
•
값 객체를 추구하기보다 구체적인 속성인 불변성, 동등성, 자가 검증, 신뢰할 수 있는 객체를 추구하는 것이 좋다.
1. 불변성
•
불변성이란 말 그대로 변하지 않는다는 의미로 값은 변하지 않는다.
•
변하지 않는다라는 이 개념은 시스템의 복잡도를 획기적으로 낮출 수 있는 개념이라 소프트웨어 설계에서 정말 중요한 개념이다.
◦
이는 불변성이라는 특징이 소프트웨어 중 일부를 예측할 수 있고 신뢰할 수 있게 만들기 때문이다.
•
소프트웨어는 불확실성으로 가득찬 복잡계다. 이는 우리가 작성한 코드에 불확실한 점이 너무 많기 때문이다.
◦
이처럼 소프트웨어에는 불확실한 요소가 너무 많으므로 믿을 수 있고 확실한 영역을 많이 만들어두는 것이 중요하다. 그리고 여기서 믿을 수 있는 코드란 항상 변하지 않고 똑같은 결과와 값만 돌려주는 코드를 의미한다.
•
값 객체는 이러한 불변성이라는 특징을 갖고 있는 객체를 의미한다. 따라서 값 객체는 불변이어야 한다. 객체가 생성된 후, 상태가 변경되어서는 안 된다.
◦
또한 이 불변성은 변수에만 적용되지 않고 함수에도 적용될 수 있다. 이는 입력값이 같은 경우, 항상 같은 출력값을 반환하는 함수이며 이를 가리켜 순수 함수 또는 함수라고 부른다.
•
멤버 변수와 마찬가지로 값 객체 안의 모든 함수는 순수 함수여야 한다.
◦
또한 자바에서는 값 객체가 그 자체로 불변성을 유지하기 힘드므로 final 클래스로 선언하여 상속으로 인한 불변성이 깨지는 걸 막아야 한다.
•
소프트웨어의 불확실성을 낮춰 복잡도를 제어하기 위해서 불변성을 추구하고 불확실성은 제거하는 편이 좋다.
2. 동등성
•
값 객체는 값이 가지고 있는 동등성이라는 가치를 제공하기 위해 hashCode와 equals라는 메소드를 재정의하여 제공하고 있다.
•
이를 조금 더 간편하게 제공하기 위해서 롬복의 @Value 어노테이션을 제공할 수 있다. 이 어노테이션이 매핑된 클래스는 다음과 같은 기능을 제공한다.
◦
equals와 hashCode 메소드가 객체의 상태에 따라 비교하는 메소드로 자동 생성된다.
◦
멤버 변수가 final로 선언된다.
◦
클래스가 final로 선언된다.
3. 자가 검증
•
자가 검증(self-validation)이란 말 그대로 클래스 스스로 상태가 유효한지 검증할 수 있음을 의미한다. 즉 유효하지 않은 상태의 객체가 만들어지지 않는 다는 것을 보장하는 것이다.
•
우리가 값 객체를 사용할 때, 자가 검증이 완벽한 객체라면 신뢰할 수 있다는 것이다.
◦
물론 자가 검증이라는 특징은 값 객체에서만 사용할 수 있는 것은 아니다. 값 객체든 아니든 자가 검증이 완료된 객체는 우리가 신뢰하고 사용할 수 있음을 항상 보장한다.
2. Data Transfer Object
•
DTO를 직역하면 데이터 전송에 사용되는 객체를 의미한다. 다음은 대표적인 DTO의 예이다.
public class UserCreateRequest {
public String username;
public String password;
public String email;
public String address;
public String gender;
public int age;
}
Java
복사
•
UserCreateRequest 클래스는 메소드를 호출할 때 데이터를 전송할 목적으로 만들어진 클래스다. 이 클래스의 타입을 가지는 객체는 DTO라고 볼 수 있다.
◦
이런 객체를 만들어 사용하는 이유는 간단한다. 다른 객체의 메소드를 호출하거나 시스템을 호출할 때 매개변수를 일일이 전달하는 것은 특정 개수 이상이 되면 비효율적인 작업이 되기 때문이다.
◦
다시 말해 DTO는 다른 객체나 시스템에 데이터를 구조적으로 만들어 전달하기 위한 객체다.
•
이런 이유는 DTO를 객체로 보기에 애매하게 만드는데, 이름부터 이 객체는 데이터 덩어리임을 어필하고 있기 때문이다. DTO는 단순히 데이터를 효과적으로 전달하는 데만 집중한다.
◦
그 밖의 능동적인 역할이나 책임을 가지고 있지는 않다. 그러므로 DTO에는 데이터를 읽고 쓰는 것 외의 비즈니스 로직이 들어가서는 안 된다.
•
정리하자면 DTO는 그저 데이터를 하나하나 일일이 나열하여 전달하는 것이 불편하여 데이터를 하나로 묶어서 보내려고 만들어진 객체다.
•
DTO를 잘못 해석한 글들은 다음과 같은 내용이 나오곤 한다.
◦
DTO는 프로세스, 계층 간 데이터 이동에 사용된다.
◦
DTO는 게터, 세터를 가지고 있다.
오해 1. DTO는 프로세스, 계층 간 데이터 이동에 사용된다.
•
일부 맞는 설명이긴 하나 불충분하다. 이 설명에 따르면 DTO는 API 통신이나 데이터베이스 통신 같은 곳에서 사용되는 객체를 의미한다.
•
DTO는 이보다 조금 더 단순하고 범용적인 개념으로 DTO의 목적은 단순히 데이터를 전달하는 것이다. 그러므로 데이터를 전달하기 위한 그 어떤 상황에서도 사용될 수 있다.
•
따라서 DTO가 어디에서 사용되느냐는 중요하지 않다. 데이터 전송이 필요한 모든 곳에서 사용할 수 있다.
다만 매개변수가 너무 많아 이를 객체로 감싸는 것을 일반적으로 추천되는 방법은 아닌데, 이는 메소드에 필요한 매개변수가 무엇이지에 관한 의존성을 감춰버리기 때문이다.
즉, 메소드가 무슨 책임을 가지고 있는지 추상화시켜버리기 때문에 코드를 읽기에 조금 더 어려움이 생기는 것 같다.
오해 2. DTO는 게터, 세터를 가지고 있다.
•
두 번째 오해는 DTO가 반드시 게터와 세터를 갖고 있어야 한다고 생각하는 것이다.
•
게터와 세터는 내부 데이터를 전달하기 위한 구현 중 하나일 뿐으로 게터, 세터 없이도 데이터를 전달할 수 있다.
◦
바로 멤버 변수를 public으로 선언하는 것이다.
◦
어차피 private으로 만들어서 getter, setter 남발하나 public으로 만드나 똑같을 것 같다.
•
그렇다고 public 선언을 남발하라는 의미는 아니다. 상황과 맥락에 따라 필요로 인해 public 선언을 사용할 수도 있다는 의미다.
◦
둘의 차이는 행동에 의존하느냐 속성에 의존하느냐다. 보통 캡슐화의 주요 목표 중 하나인 정보 은닉을 달성하기 위해서라도 모든 멤버 변수는 private으로 가는 것이 유리하다.
3. Data Access Object
•
DAO는 데이터베이스 접근과 관련된 역할과 책임을 가진 객체를 가리키는 용어로 이러한 정의에 따라 다음과 같은 역할을 가지고 있다.
◦
데이터베이스와의 연결을 관리한다.
◦
데이터베이스에 연결해 데이터에 대한 CRUD 연산을 수행한다.
◦
보안 취약성을 고려한 쿼리를 작성한다.
•
DAO는 말 그대로 데이터에 접근하기 위해서 만들어진 객체다. 복잡하고 번거로운 데이터베이스 접근 관련 로직을 전문적으로 처리하기 위해 만들어진 객체로 Repository와 유사하다고 보면 된다.
•
DAO가 만들어진 이유는 무엇일까? 이는 도메인 로직과 데이터베이스 연결 로직을 분리하기 위해서다.
4. Entity
•
먼저 오라클에서 제공하는 엔티티의 정의를 살펴보자.
◦
엔티티란 경량화된 영속성 도메인 객체다. 일반적으로 엔티티는 관계형 데이터베이스의 테이블을 나타내며 각 엔티티 인스턴스는 테이블의 행에 해당한다.
•
애석하게도 이런식의 접근은 틀렸다. 왜냐하면 엔티티는 JPA에서 만들어진 용어가 아니기 때문이다.
◦
엔티티라는 개념은 JPA가 등장하기도 전부터 존재했던 개념으로 JPA의 엔티티는 단순히 이를 표현하는 수단 중 하나에 불과하다.
•
우선 엔티티를 설명하기에 앞서 헷갈리기 쉬운 엔티티 개념 셋은 다음과 같다.
◦
도메인 엔티티
◦
데이터베이스 엔티티
◦
JPA 엔티티
1. 도메인 엔티티
•
도메인 엔티티를 이해하려면 도메인이 무엇인지부터 알아야 한다. 도메인은 간단하게 어떠한 비즈니스 영역으로 설명할 수 있다.
•
어떠한 문제 영역에서 해결을 위해 사용하는 개념 모델들을 도메인 모델이라고 부르는데, 이 도메인 모델들 중에서도 식별자가 존재할 수 있으며 비즈니스 로직을 스스로 가질 수 있는 것들을 도메인 엔티티라고 부른다.
•
즉 도메인 엔티티는 다음과 같은 특징들을 가진다.
◦
식별 가능한 식별자를 갖는다.
◦
비즈니스 로직을 갖는다.
•
즉 도메인 엔티티는 식별 가능하고 비즈니스 로직을 갖고 있으며 생애주기와 같이 조금 특별하게 관리되는 클래스로 만들어진 객체다.
◦
일반적으로 소프으웨어 개발 분야에서 말하는 엔티티는 이 도메인을 뜻한다. 소프트웨어를 개발하는 것은 특정한 영역의 문제를 해결하기 위한 작업들을 수행하는 것이기 때문이다.
2. 데이터베이스 엔티티
•
그렇다면 데이터베이스 엔티티는 뭘까? 이는 데이터베이스라는 범위에 국한되는 어떤 유무형의 객체를 표현하는 데 사용하는 용어이다.
3. JPA 엔티티
•
관계형 데이터베이스에 있는 데이터를 객체로 매핑하는 데 사용되는 클래스를 JPA 엔티티라고 부르는데, 이때 클래스에 @Entity 어노테이션을 매핑해준다.
◦
JPA 엔티티를 도메인 엔티티나 데이터베이스 엔티티와 비교하자면 데이터베이스 엔티티에 조금 더 가까운 개념으로 이해할 수 있다. JPA는 관계형 데이터베이스에 그 뿌리를 두고 있기 때문이다.
4. 해석
•
이 세가지 개념들은 모두 엔티티라는 큰 카테고리의 하위 개념일 뿐이다. 그러니 추상적인 본질에 대해서 이 개념이 무엇이고 어떻게 받아들이면 좋을지 알아두는 것이 좋다.
•
최초에 프로그래밍 언어를 연구하는 사람들과 데이터베이스를 연구하는 사람들에게는 하나의 고민이 있었는데, 어떤 유무형의 자산을 데이터로 어떻게 표현할 것이냐였다.
◦
예를 들어 사용자라는 데이터를 표현할 때, 사용자는 아이디, 이메일, 이름 같은 정보를 갖고 있다면 이 ‘사용자’라는 자산 정보를 어떻게 불러야 할까?
•
프로그래밍 언어와 데이터베이스 분야에서 이처럼 표현하고 싶은 유무형의 자산 정보를 지칭하는 데 개체(entity)라는 용어를 사용하기로 결정했다.
•
용어를 정의했으니 다음으로 이 엔티티를 어떻게 표시할지를 고민할 차례였으며 두 영역의 연구자들은 각 분야에 기초하여 표현 방식이 조금 달랐다.
◦
객체지향 진영에서는 엔티티를 표현하기 위해 클래스를 사용했으며 데이터베이스 진영에서는 테이블을 사용했다.
•
나름 두 개의 entity는 각자의 영역에서 안정적으로 유무형의 자산을 표현함에 있어서 적절하게 사용되어지다가 경합하는 상황이 발생했다.
◦
실세계의 서비스를 만들기 위해 두 영역이 협업할 필요가 있었던 것이다.
◦
이 둘은 비슷해보이면서도 달랐기 때문에 변환을 수행하는 Mapper, ORM과 같은 개념 등이 등장한 것이다.
•
데이터베이스, 도메인 엔티티가 무엇인지 알았으니 이제, JPA 엔티티는 뭘까. 그럼? 저자는 JPA 엔티티를 엔티티라고 부르는 것보다는 영속성 객체라고 부르는 것이 더 정확하다고 생각한다.
◦
JPA 엔티티는 관계형 데이터베이스의 엔티티를 지칭하고 있다.
◦
JPA의 @Entity 어노테이션이 적용된 객체는 영속성 객체가 된다.
◦
JPA의 @Entity 어노테이션은 영속성 객체를 만들기 위한 도구일 뿐이다.
•
JPA 엔티티는 엔티티라고 볼 수 없다. 단순히 영속성 객체를 만들기 위한 도구일 뿐이다.
•
우리는 소프트웨어 분야에서 작업하고 있으므로 엔티티가 뭐냐고 물어본다면 도메인 엔티티를 떠올려야 한다.
5. 객체의 다양한 종류
•
당연하게도 지금까지 알아본 객체 외에도 더 다양한 종류의 객체가 존재한다. 영속성 객체가 그중 하나가 될 수 있으며 서비스 객체라는 개념도 존재한다.
◦
서비스 객체는 데이터 접근 객체같은 영속성 객체를 통해 도메인을 불러와 도메인으로 하여금 처리를 지시하기도 하며 비즈니스 로직이라 불리는 어플리케이션의 핵심 로직을 처리하는 객체를 의미한다.
⇒ 그렇다. Service다.
•
우리는 은연 중에 정말 많은 종류의 객체를 활용하고 있다. 따라서 각 종류의 이름과 정의를 외우고 분류하려 시도하는 것은 의미없는 행동이다.
•
더불어 역할을 칼같이 구분하는 것도 바람직한 자세는 아니다. VO니 DTO니 DAO니 이름을 붙이기 보다, 그 객체가 무언가를 표현하고 있음을 표현하자.
◦
그 객체는 VO를 구현할수도 DTO를 구현할수도 있다. 물론 다중 상속도 가능하다. 이들은 개념으로, 개념은 그저 개념일뿐이다.
•
우리가 집중해야 하는 것은 개념 안에 숨겨진 소프트웨어 설계가 추구하는 가치들이다. 그리고 지금까지 배운 가치는 다음과 같다.
◦
불변성
◦
예측 가능성
◦
역할의 분리
◦
항상성
•
그저 이름을 붙이고 외우는 것이 아닌 각 개념을 실제로 이해하고 적용하는 과정에서 소프트웨어 개발 역량이 향상된다. 즉 인출을 해봐야 한다.
번외
•
자바 14에 프리뷰 기능으로 추가되었고 16에 정식으로 추가된 record라는 키워드가 있다.
•
이는 데이터를 담는 간단한 클래스를 만들 때 사용할 수 있는 키워드로 이 키워드를 사용하여 만들어진 객체는 값 객체의 특징을 가지게 된다.
•
이 키워드를 이용해 만들어지는 객체의 변수는 final 선언을 해두지 않아도 final 선언을 한 것과 같이 동작하며 equals, hashCode, toString 같은 메소드들이 자동으로 만들어진다.
⇒ 재정의로 만들어지는 걸까? Object를 상속한 게 아닐까?
•
더블어 레코드 객체의 변수들은 public 게터 메소드가 자동으로 만들어져서 다음과 같이 사용할 수 있다.
◦
recordObject.propertyName()