안녕하세요. 이번 게시글에선 자바/스프링 실용주의 프로그래밍 2부 - 객체의 종류를 읽고 정리해보았습니다.
또한 현재 개인 프로젝트로 DTO로 Record로 사용하는 것이 어떤 의의가 있는지도 알아보겠습니다.
2부 객체의 종류
VO - 값 객체
값 객체 예시 코드 (엄밀히 말해서 Color 클래스는 객체가 아님. 해당 클래스로 만들어진 객체가 VO)
public class Color {
public final int r;
public final int g;
public final int b;
public Class(int r, int g, int b) {
//.. 검증 로직
this.r = r;
this.g = g;
this.b = b;
}
@Override
public boolean equals(Object o) {
// 동등성 비교
}
@Override
public int hashCode() {
return Objects.hash(r, g, b);
}
}
위의 Color는 '객체'임과 동시에 '값'임
소프트웨어에서의 값이란?
- 불변성, 동등성, 자가검증을 추구해야함.
불변성 : 값은 변하지 않는다. 숫자 1은 영원히 숫자 1이다.
동등성 : 값의 가치는 항상 같다. 숫자 1은 위치나 시간에 관계없이 숫자 1 이다.
자가검증 : 값은 그 자체로 올바르다. 1은 사실 1.01 이지 않을까 같은 고민을 할 필요가 없다.
VO 조건 1 - 불변성
불변성은 소프트웨어 복잡도를 크게 낮출 수 있음.
믿을 수 있는 코드는 항상 변하지 않고 똑같은 결과와 똑같은 값만 돌려주는 코드임.
불변성 1 - 값이 변하지 않아야한다. (final 키워드)
VO는 불변이어야하므로 객체가 생성된 이후 내재된 값이 변경되면 안됨. -> 모든 멤버변수를 final로 선언하기
주의) 불변 객체 안의 참조 객체가 불변이 아니라면 그 객체는 불변이 아님!
불변성 2 - 함수 또한 불변이어야한다. (순수함수)
순수함수는 입력값이 같을 때 항상 같은 값을 반환하는 함수 (불변성이 적용된 함수)
불변성 3 - 상속으로 변경되면 안된다. (final 클래스)
불변으로 설계한 클래스를 상속한 클래스가 불변이 아니라면 불변성이 깨지게 된다.
불변성을 다 지키는 것이 중요한가? -> 신뢰라는 가치를 추구하기
-> 불변성을 100% 충족시키기는 어려움
중요한 것은 불변성이 지닌 가치를 좇는 것!
불변성이 지닌 가치는 신뢰할 수 있는 객체를 만드는 것이다.
신뢰하는 객체를 만들어야 다른 객체와 협력하는 과정에서 항상 예측 가능한 방식으로 동작함.
- 가변 객체는 다른 스레드 때문에 결과과 계속 변할 가능성이 존재하므로 메서드 호출 결과를 예측할 수 없음
(final로 멤버변수 선언하고, 기존 일반 세터 메서드를 변경 시 새로운 객체를 반환하는 메서드로 변경하는 방법도 있음)
내 생각 주의)
객체지향 프로그래밍에서 VO를 고민하는 이유 -> 객체의 신뢰를 확보하는 방법 중 하나
이전 게시글에서 객체지향의 주요 사항 중 하나로 신뢰라는 개념을 알아보았었습니다.
객체지향 프로그래밍에선 각 객체가 역할을 책임으로써 구현하고, 캡슐화되어 서로 상호작용을 하는 과정 중
신뢰를 기반으로 작동한다고 하였습니다.
이전 게시글에선 테스트 코드로 신뢰를 확보다는 것도 알아보았습니다.
이번에 알게되는 VO의 특성을 통해 VO는 객체를 신뢰하는 또 하나의 방법임을 알 수 있었습니다.
저번에도 등장한 객체지향 그림(?)
원래 신뢰 항목에 테스트 코드의 필요성만 작성하였는데 이 파트를 읽고 VO 사용을 추가했습니다..
VO 조건 2 - 동등성
위의 Color 클래스가 equals()와 hashCode()를 오버라이딩하고 있는 이유 -> 값이 갖고 있는 동등성이라는 가치
어떤 객체가 값이고 상태가 모두 같다면 같은 객체로 봐야한다. -> 예측 불가능성을 제거
하지만 실무에서 객체를 비교하는 상황이 많지 않아서 동등성 비교를 항상 지키지는 않는다.
-> 결국 신뢰할 수 있는 객체를 만든다는 목표가 중요한 것이므로
cf) 동등성과 식별자는 서로 충돌하는 개념이다.
AccountInfo account1 = new AccountInfo(1, 20000);
AccountInfo account2 = account1.withMileage(70000);
System.out.println(account1 == account2);
위 코드는 식별자를 기준으로 true이지만 동등성을 기준으로 false이다.
VO 조건 3 - 자가검증
자가검증은 클래스 스스로 상태가 유효한지 검증할 수 있음을 의미.
유요하지 않은 상태의 객체가 만들어지면 안된다.
자가검증은 VO뿐 아니라 다른 객체에도 적용되면 객체의 신뢰성을 확보할 수 있다.
* 객체 생성 시 검증을 해야한다.
@Valid를 통해 검증을 하는 방식, 생성자를 통한 검증 방식은 자가검증 방법들로 이해할 수 있다.
객체 생성 시 검증을 하지 않고, 생성된 객체에 접근할 때마다 일일이 if 체크 등의 검증 코드가 있다는 것은
신뢰하는 객체를 만들지 못하고 있다는 뜻으로 객체지향 프로그래밍을 추구하기 어렵게 만든다.
VO 정리)
객체가 VO가 아니냐 가 중요한 게 아니라 VO의 목적을 고민하는 것이 중요하다.
-> 불변성, 동등성, 자가검증을 고민하여 신뢰할 수 있는 객체를 만들기
-> 객체지향 프로그래밍을 위한 신뢰할 수 있는 객체를 만드는 것에 집중 !
DTO (Data Transfer Object)
DTO는 그저 데이터를 하나로 묶어서 전달하려고 만든 객체에 불과함.
하지만 DTO에 대한 오해들이 많다.
DTO 오해 1 - 프로세스, 계층 간 데이터 이동에 사용된다.
데이터를 전달하고 싶은 상황이라면 어디서든 사용 가능함.
데이터 전송이 필요한 모든 곳에서 사요할 수 있음.
DTO 오해 2 - DTO는 게터, 세터를 갖고 있다.
게터, 세터는 DTO의 필수조건이 아님. 데이터를 전달한다는 임무만 충족하면 DTO
DTO 오해 3 - DTO는 데이터베이스에 데이터를 저장하는 데 사용되는 객체다.
API든, DB든 데이터를 주고 받는 객체는 모두 DTO임.
DAO (Data Access Object)
DAO가 만들어진 목적은 도메인 로직과 데이터베이스 연결 로직을 분리하기 위해서이다.
DAO는 애플리케이션의 핵심이 아니다. 핵심은 비즈니스 로직 (도메인)
엔티티 (도메인 엔티티, DB 엔티티, JPA 엔티티)
도메인 엔티티
도메인 모델은 어떤 도메인 문제를 해결하고자 만들어진 클래스 모델
도메인 모델 중 식별자와 비즈니스 로직을 갖는 객체가 도메인 엔티티
도메인 엔티티
- 식별 가능한 식별자를 갖는다
- 비즈니스 로직을 갖는다
cf) 도메인 모델
- 도메인 모델에는 도메인 엔티티, 도메인 VO, 도메인 DTO, 도메인 DAO 등이 포함될 수 있다.
DB 엔티티
도메인 엔티티와는 상관없이 관계형 데이터베이스 분야에서 어떤 유무형의 객체를 표현하는데 사용했던 용어
프로그래밍 언어와 데이터베이스 분야에서는 표현하고 싶은 유무형의 자산정보를 '엔티티'라는 용어를 사용함.
객체지향 진영에서는 엔티티를 표현하는데 '클래스'를 사용했고
데이터베이스 진영에서는 '테이블'을 사용해 데이터를 표현함
객체지향 진영에서는 온전한 객체와 객체 사이의 협력을 추구하는 반면
데이터베이스에서는 데이터의 정합성, 중복 제거와 같은 부분을 신경씀
JPA 엔티티
관계형 데이터베이스에 있는 데이터를 객체로 매핑하는데 사용되는 클래스를 JPA 엔티티라고 부름.
영속성 객체(persistent object)라고 보는 것이 알맞다.
주의)
엔티티를 JPA 엔티티라고 인식하는 개발자는 관계형 데이터베이스에 종속되는 프로그램을 만들 확률이 높음.
2장을 읽고 DTO를 Java Record로 사용할 때의 이점에 대해 고민해보기
현재 개인 프로젝트에서 DTO를 레코드로 사용하고 있었습니다.
사실 이 책을 읽기 전에 레코드에 대해 고민을 해보지 않고 프로젝트에 적용했던 것 같습니다..
2장을 읽고 DTO를 레코드로 사용한다는 것에 어떤 의의가 있을지 고민해보았습니다.
레코드는 final 클래스이며 필드를 final로 만들어주고 equals, hashCode, toString, 게터 메서드가 자동으로 만들어져 VO의 특징을 갖게 됩니다.
(레코드도 인터페이스를 implemention할 수 있다는 것은 레코드 또한 객체이며 역할을 추상화하고 책임을 구현한다는 객체지향 사상을 기본으로 갖는다고 이해할 수 있습니다..! 부끄럽게도 이 책을 읽기 전엔 왜 레코드는 상속 불가인데 impl은 되지? 라는 막연한 궁금증만 갖고 있었습니다... 😅)
엄밀히 말해 필드가 VO가 아닌 참조 변수를 갖고 있거나, 순수함수가 아닌 메서드를 갖고 있다면 VO가 아니게 되지만, 책에서 언급한 것과 같이 레코드를 통해서 신뢰할 수 있는 객체가 만들어지는가 중요한 포인트 같습니다.
일단 레코드를 사용했다고 무적권 신뢰할 수 있는 객체라고 보긴 어려울 것 같습니다.
가장 먼저, @Valid 애노테이션 사용, 생성자 검증 등으로 자가검증을 진행해야합니다.
자가검증이 되지 못했다면 레코드를 사용했더라도 신뢰하기 어려운 객체가 되어버릴 수 있습니다.
(자바에서 레코드에 컴팩트 생성자라는 새로운 기능을 추가해준 것도 자가검증 측면이라고 생각이 됩니다..!)
그리고, 참조 변수에 대해 주의하여야합니다. 참조 변수가 불변 객체가 아니면 신뢰하기 어려운 객체가 될 수 있습니다.
(필드로 JPA 엔티티를 갖는 경우라면..?)
DTO가 필드로 JPA 엔티티를 갖고 있다면 신뢰할 수 있는 객체라고 볼 수 있을까..?
-> 개인적인 생각에 DTO 필드로 JPA 엔티티를 가지고 있어도 신뢰할 수 있는 객체로 볼 수 있을 것 같다고 생각합니다.
엔티티는 영속성 컨텍스트(트랜잭션) 내에서 엔티티의 생명주기를 갖기 때문입니다.
JPA는 영속성 컨텍스트를 통해 DB설정과 관계없이 어플리케이션 수준에서 REPEATABLE READ를 보장하므로
다른 스레드에 의해 값이 변경되거나 하는 경우가 없습니다.
이 외에도 동등성 비교, 순수 함수에 대해 주의해야겠지만 DTO로 사용하는 경우에 한하면 중요도는 비교적 떨어질 수 있을 것 같습니다.
이러한 사항들에 유의를 했다면 DTO로 레코드 사용은 좋은 선택지라고 생각합니다.
DTO를 레코드로 사용하는 것은 DTO를 VO로 사용하겠다는 것과 같은 의미입니다.
때문에 먼저 입출력 객체에 대한 신뢰성을 확보할 수 있다는 것을 가장 큰 이점으로 생각할 수 있겠습니다.
또한 레코드는 VO에 대한 제약 사항을 기본적으로 내재하고 있기 때문에 코드 작성이 간편해지고
추가적으로 필드값 변경 및 상속이 불가능하기 때문에 비즈니스 로직 개발 시 클린한 코드 작성을 유도하는 효과가 있을 것 같습니다. (객체 생성한 뒤 세터 등이 덕지덕지 붙는..등의 배드스멜 코드 방지..)
'컴퓨터 과학 > [책] 자바스프링 개발자를 위한 실용주의 프로그래밍' 카테고리의 다른 글
자바/스프링 개발자를 위한 실용주의 프로그래밍 7장 - 서비스 (0) | 2024.10.07 |
---|---|
자바/스프링 개발자를 위한 실용주의 프로그래밍 5장 - 순환참조 (1) | 2024.09.24 |
자바/스프링 개발자를 위한 실용주의 프로그래밍 4장 - SOLID (0) | 2024.07.19 |
자바/스프링 개발자를 위한 실용주의 프로그래밍 3장 - 행동 (0) | 2024.07.18 |
자바/스프링 개발자를 위한 실용주의 프로그래밍 1장 - 절차지향과 비교하기 (0) | 2024.07.08 |