이 내용은 자바/스프링 개발자를 위한 실용주의 프로그래밍
chapter2의 내용을 정리한 것입니다.
목차
개요
객체의 종류에 관한 문제는 개념을 이해하지 않고 외우려고만 할 때 곧잘 발생한다.
- 납득이 안 가는 설명이 있음에도 암기해야 하는게 많아 일단 외우는 데 집중하다 보니 정신을 차리고 보면 누군가 설명해둔 내용을 그대로 읊을 줄밖에 모르게 되는 것이다.
- 또한, VO나 DTO를 왜 써야 하는지도 모르겠지만 일단 좋다고 하니 진행중인 프로젝트에 적용해본다. 그 다음 이런 걸 VO, DTO라고 부른다고 하니까 그러려니 하고 클래스를 만들며 클래스 이름 뒤에 접미어로 VO, DTO를 넣게된다.
이러한 용어 정의에서 어떤 내용이 잘못됐고, 어떤 내용이 충분하지 못한지 이야기해본다.
- 이 책에서는 보편적으로 받아들여지는 내용으로만 구성하려고 했으나 이 책의 내용과 충돌하는 다른 의견도 있을 수 있음
2.1 VO(Value Object: 값 객체)
궁금한 것은 값이라고 부르는 것의 특징과 이것이 “소프트웨어 관점에서 어떻게 해석되는가”이다.
- 소프트웨어 설계자 입장에서 값은 불변성, 동등성, 자가 검증이라는 특징이 존재한다.
- 객체를 값으로 만들려면 이러한 특징들이 필요하다고 생각하자.
2.1.1 불변성
불변성(Immutability)이란 말 그대로 “변하지 않는다”라는 의미다. “변하지 않는다”라는 이 간단한 개념은 시스템의 복잡도를 획기적으로 낮출 수 있는 개념이라서 소프트웨어 설계에서 정말 중요한 개념이다.
이유는 “불변성” 이라는 특징 덕분에 소프트웨어 중 일부를 예측할 수 있고 신뢰할 수 있게 만들기 때문이다.
: 자바에서는 어떤 값을 변하지 않게 하려면 어떻게 구현해야할까?
- final 예약어 사용하기
: VO는 이러한 불변성이라는 특징을 갖고 있는 객체를 말한다.
- VO는 불변이어야 한다.
- 객체가 생성된 이후 내재된 값이 변경되서는 안된다.
- 그래서 VO로 선언된 모든 멤버 변수는 불변(final)으로 선언돼있어야한다.
: VO가 무엇이냐
- 답은 “객체가 불변 상태여야한다”라는 답변이다
- 하지만 이를 오해해서 “모든 멤버 변수가 final로 선언되어있으면 VO다”라고 한다면 틀린 설명
- 이유는 모든 멤버 변수를 final로 선언하더라도 원시 타입이 아닌 참조 타입인 객체가 있다면 불변성이 보장되지 않을 수 있기 때문이다.
- public class FilledColor { public final int r; public final int g; public final int b; public final Shape shape; ... }
: 불변성은 “변수”에만 적용되는 개념이 아니다.
- 불변성은 함수에도 적용될 수 있는데 입력 값이 같으면 항상 같은 값을 반환하는 함수를 가리켜 순수함수라 한다.
- 그래서 “모든 멤버 변수가 final”이면 불변이다 라는 명제는 틀렸다.
- 멤버 변수와 마찬가지로 VO 안의 모든 함수는 “순수 함수”여야 하다.
- 그렇다면 모든 멤버 변수와 모든 메서드가 불변이면 불변성이 지켜질까? → 아니다.
: 불변의 가치는 상속에 의해서도 쉽게 꺠질 수 있다.
- VO 클래스는 final 클래스로 선언돼야한다. 아예 해당 클래스를 상속하지 못하게 해서 불변성이 유지되게 만드는 것이다.
❗모든 멤버 변수를 final로 만든다고 해서 불변이 되는 것은 아니며 “객체가 불변이다”라는 것은 단순히 “객체가 갖고 있는 값들이 변하지 않는다”에서 끝나지 않는다.
: 사실 불변성은 복잡하고 어렵게 생각할 개념이 아니기도 함
- 객체를 진정한 의미의 불변으로 만들려고 하는 것은 그거 나름대로 에너지 낭비다.
- VO의 불변성을 완벽하게 지키는 100점짜리 VO를 만들려고 노력할 필요 없다.
- 중요한 것은 불변성이 지닌 “가치”를 좇는 것
: “불변성” 자체에 주목하자.
- 불변성을 강조하는 이유는 객체를 신뢰할 수 있게 만들기 위함이다.
- 불변성을 가진 객체는 내부 상태가 변경되지 않는다. 덕분에 다른 객체와 협력하는 과정에서 항상 예측 가능한 방식으로 동작한다
❗ 불확실성을 없애는 것은 불가능하다.
- 하지만 최대한 줄일 수는 있으며 불확실성을 제거할 수 있는 부분과 안고 가야하는 부분을 나누는 것을 시작으로 시스템에서 확실한 부분을 최대한 늘려야한다.
- 이것이 바로 불변성이 추구하는 목적이다.
✅ 정리
- “값”의 특징을 소프트웨어 설계자 입장에서 보자면 “불변성”, “동등성”, “자가 검증” 이다.
- 값 객체는 객체 내부의 멤버 변수, 함수 그리고 객체 자신이 “불변”이어야 함을 뜻한다.
- 100점 짜리 값 객체보다는 불변성의 가치를 좇는 것이 중요하다.
- 값 객체를 만듬으로써 쓰레드 처리 코드와 같은 불확실성을 조금이나마 확실하게 만드는 것이 중요
2.1.2 동등성
: 동등성(equality)란 무엇이고 왜 추구해야 하는가 ?
- VO는 이러한 문제에 대해 다음과 같은 답을 내린다.
- 어떤 객체가 값이고 상태가 모두 같다면 같은 객체로 봐야한다.
: VO를 만들기 위해 자바에서는 객체 간 비교에 사용되는 equals나 hashCode를 오버라이딩할 필요가 있다.
- 오버라이딩 하지 않는다면 메모리상의 주소값을 이용해 비교한다. 이는 VO의 설계 의도와 일치하지 않는다.
: 하지만 프로그램을 개발하면서 객체를 비교해야하는 상황이 그렇게 많지가 않다.
- 그래서 “이렇게까지 해야하나?”라는 원칙의 실효성에 의문이 생긴다.
- 책의 저자가 추천하는 방식은 lombok의 Value 애너테이션을 사용하는 것이다. 그리고 Value 애너테이션이 지정된 클래스는 다음과 기능을 제공한다.
- equals, hahsCode 메서드가 객체의 상태에 따라 자동 생성
- 멤버 변수가 final로 선언됨
- 클래스가 final로 선언됨
- 또한 Value 애너테이션을 사용하면 해당 객체를 VO로 나타탤 수 있다는 점에서도 유용하다.
2.1.3 자가검증
: 자가 검증(Self Validation)이란 말 그대로 클래스 스스로 상태가 유효한지 검증할 수 있음을 의미한다
- 즉, 유효하지 않은 상태의 객체가 만들어질 수 없다는 것을 의미한다.
: 한번 생성된 VO의 멤버변수에는 이상한 값이 들어 있을 수 없다.
- VO의 목표가 신뢰할 수 있고 예측 가능한 객체를 만드는 것이라는 점을 알고 있다면 이 특징이 얼마나 중요한지 이해될 것이다.
- 자가 검증이 완벽한 객체라면 외부에서 이 객체를 사용할 때 이상태에 이상한 값이 들어 있지는 않을지 노심초사하지 않아도 된다.
- 상태 검증을 위해 if-else, try-catch 문을 사용하지 않아도 된다는 것이다.
- 따라서 VO의 생성자에는 반드시 유효한 상태 값이 들어오는지 검증하는 코드가 있어야한다.
: VO의 목적은 신뢰할 수 있고 예측 가능한 객체를 만드는 것이다.
- 따라서 자가 검증이란 특징 역시 VO를 정의하는데 필요한 조건이다.
💡개발할 때 중요한 것은 “이 객체가 VO냐 아니냐”가 아니다.
- 중요한 것은 VO의 목적을 고민해보는 과정이다. 신뢰할 수 있는 객체를 어떻게 만들지, 어떤 값을 불변으로 만들지, 어디까지 값을 보장해야할지 등을 고민하는 과정이 개발에 더욱더 도움이 된다.
- VO를 추구한다기보다 불변성, 동등성, 자가 검증, 신뢰할 수 있는 객체를 추구하기를 바란다
2.2 DTO, 데이터 전송 객체
: DTO를 만들어서 사용하는 이유는 간단하다
- 다른 객체의 메서드를 호출하거나 시스템을 호출할 때 변수를 일일히 나열하는 것이 불편하기 때문이다.
- 즉, DTO는 다른 객체나 시스템에 데이터를 구조적으로 만들어 전달하기 위한 객체이다.
: 이러한 이유로 DTO 객체라고 보기에도 애매하다
- 이름에서부터 “이 객체는 데이터 덩어리입니다”라고 어필하고 있기 때문
- DTO는 오롯이 데이터를 효과적으로 전달하는데만 집중한다. 그 밖의 능동적인 역할이나 책임을 가지고 있지 않다.
- 그러므로 DTO에는 데이터를 읽고 쓰는 것 외에 다른 비즈니스 로직이 들어가서는 안된다.
💡 정리하자면, DTO는 그저 데이터를 하나하나 일일이 나열해서 전달하는게 불편해서 데이러틀 하나로 묶어서 보내려고 만들어진 객체이다.
❗DTO를 잘못 해석한 글을 보면 꼭 다음과 같은 내용이 나오곤 한다.
- DTO는 프로세스, 계층 간 데이터 이동에 사용된다.
- 일부는 맞는 설명이긴 하지만 불충분하다.
- 이 설명에 따르면 DTO는 API 통신이나 데이터베이스 통신 같은 곳에 사용하는 객체를 의미한다. 분명히 DTO는 이 부분에 사용할 수 있지만 이것이 DTO의 목적은 아니다.
- DTO는 조금 더 단순하고 범용적인 개념이다.
- DTO의 목적은 데이터를 전달하는 것이다. 그러므로 데이터를 전달하고 싶은 상황이라면 어디서든 사용될 수 있다.
- 따라서 DTO가 어디에서 사용되느냐는 중요하지 않다. 데이터 전송이 필요한 모든 곳에서 사용할 수 있다.
- 하지만 매개변수가 너무 많아서 객체로 한번 감싸는 것은 일반적으로 추천되는 방식은 아니다. 메서드에 필요한 매개변수가 무엇인지에 관한 의존성을 감추기 때문이다.
- DTO는 게터,세터를 가지고 있다.
- 게터, 세터는 내부 데이터를 전달하기 위한 방법 중 하나일 뿐이다. 데이터를 전달하기만 위한 목적이라면 멤버 변수를 public으로 선언해서 전달해도 DTO의 목적에 부합된다.
- 멤버 변수를 public으로 선언해도 되나?
- 자바 프로젝트에서는 멤버 변수는 private으로 선언되는 것이 관행이며 이는 객체지향에서 캡슐화의 가치를 지키는 데 어느 정도 효과가 있다.
- private에 getter, setter 어노테이션을 적용한 코드는 public을 사용한 것과 다름없다.
- 자바 프로젝트에서는 멤버 변수는 private으로 선언되는 것이 관행이며 이는 객체지향에서 캡슐화의 가치를 지키는 데 어느 정도 효과가 있다.
- 멤버 변수를 public으로 선언해도 되나?
- 게터와 세터는 DTO를 정의하는데 필수 조건이 아니다. 데이터를 전달한다는 본연의 임무를 다한다면 DTO라고 볼 수 있다.
- 게터, 세터는 내부 데이터를 전달하기 위한 방법 중 하나일 뿐이다. 데이터를 전달하기만 위한 목적이라면 멤버 변수를 public으로 선언해서 전달해도 DTO의 목적에 부합된다.
- DTO는 데이터 베이스에 데이터를 저장하기 위해 사용되는 객체다.
- DTO의 D는 데이터이며 데이터라는 말은 컴퓨터 공학 어디서든 사용되는 개념이다.
- DTO는 말 그대로 “데이터를 전송하기 위한 객체”이다. 그 이상 그 이하의 의미도 없다. API 통신에 사용되는 요청 본문, 응답 본문을 받는데 사용되는 객체도 DTO, 데이터베이스에서 데이터를 불러오고 저장하는데 사용되는 객체도 DTO다.
2.3 DAO, 데이터 접근 객체
DAO는 데이터베이스 접근과 관련된 역할을 지닌 객체를 가리키는 용어다. 그래서 DAO는 다음과 같은 역할을 담당한다.
- 데이터베이스와의 연결을 관리
- 데이터베이스에 연결해 데이터에 대한 CRUD 연산을 수행
- 보안 취약성을 고려한 쿼리 작성
: DAO는 말 그대로 데이터에 접근하기 위해 만들어진 객체
- 복잡, 번거로운 데이터베이스 접근 관련 로직을 처리하기 위해 만들어진 객체이며 Repository와 같은 개념이다.
: DAO를 바라볼때는 DAO의 역할보다 DAO가 만들어진 목적을 생각하는 편이 더 바람직하다.
- DAO가 만들어진 이유는 “도메인 로직과 데이터베이스 연결 로직을 분리” 하기 위해서다.
2.4 Entity, 개체
ENTITY는 JPA의 ENTITY가 아니다. JPA의 ENTITY를 ENTITY라고 받아들이는 순간 혼란만 더 가중된다. 엔티가 무엇인지 파악해보자.
엔티티는 조금 더 보편적인 개념이다. 이 개념이 어디에서 사용되느냐에 따라 도메인,DB,JPA 엔티티가 되는 것이다.
- 즉, “엔티티”라는 용어는 소프트웨어 설계 분야에서 널리 사용되며 그 쓰임새는 문맥에 따라 다르지만 기본적인 의미는 비슷하다.
2.4.1 도메인 엔티티
: 도메인 모델 중 같은 특별한 기능을 갖고 있는 모델들을 도메인 엔티티라고 부른다. 즉, 도메인 엔티티에는 다음과 같은 특징이 있다.
- 식별 가능한 식별자를 갖는다.
- 비즈니스 로직을 갖는다.
즉, 도메인 엔티티는 다음과 같은 목적으로 만들어진 객체라고 볼수 있다.
- 식별 가능
- 비즈니스 로직을 가짐
- 조금 특별하게 관리되는 클래스
: 일반적으로 소프트웨어 개발 분야에서 말하는 엔티티는 이 “도메인 엔티티”를 뜻한다.
- 왜냐하면 소프트웨어를 개발한다는 것 자체가 어떤 비즈니스 영역에서 문제를 해결하고자 하는 것이기 때문이다.
- 따라서 소프트웨어 개발의 세계에서 “엔티티를 개발한다”라는 말은 “도메인 엔티티를 만든다”라는 의미로 볼 수 있다.
2.4.2 DB 엔티티
:DB 엔티티는 원래 관계형 데이터베이스 분야에서 어떤 유무형의 객체를 표현하는데 사용했던 용어다.
2.4.3 JPA 엔티티
- RDB에 있는 데이터를 객체로 매핑하는데 사용되는 클래스를 JPA 엔티티라고 부르는데 이떄 클래스에 “@Entity”라는 애너테이션을 지정한다.
- JPA 엔티티는 DB 엔티티에 더 가까운 개념으로 이해할 수 있다.
2.4.4 해석
:엔티티란 정확히 무엇이고 어떻게 받아들이면 좋을까?
- 엔티티는 데이터로 표현하려는 유무형의 대상
- 소프트웨어 개발 분야에서 말하는 엔티티는 도메인 엔티티
- 도메인 엔티티와 DB 엔티티는 다르다.
2.5 객체의 다양한 종류
:SO(Service Object)라는 개념도 존재한다.
- SO는 DAO와 같은 영속성 객체를 통해 도메인을 불러와 도메인에 업무를 지시하기도 하고, 비즈니스 로직이라 불리는 애플리케이션 코어 로직을 처리하는 객체를 의미한다.
: 우리는 이미 은연중에 많은 타입의 객체를 활용하고 있는 중이다.
- VO,DTO,DAO, PO, SO 같은 이름,정의를 외우고 용어에 따라 객체를 분류하고 시도하는 것은 크게 의미가 없다.
- UserService와 같은 클래스를 SO라는 개념을 배웠다고 해서 UserSO로 바꿔 부르는게 무슨 의미가 있을까?
: 역할을 칼같이 구분하는 것도 그다지 바람직한 자세가 아니다.
- Ex) VO는 DTO가 아니니까 데이터 전송에 사용될 수 없나 ?, PO는 불변성이라는 특징을 가지면 안되나?
- 개념을 외우고 엄격한 기준을 적용하는 것보다는 각 개념이 만들어진 이유와 목적을 생각하는 것이 바람직하다.
- 그저 이름을 붙이는 것이 아닌 실제로 각 개념을 이해하고 적용하는 과정에서 소프트웨어 개발 역량이 향상되리라는 점을 강조하고 싶다.
- 따라서 객체의 타입을 분류하는데 치중하기보다 위와 같은 가치들을 프로젝트에 지속적으로 적용해보길 바란다.