Skip to main content

Command Palette

Search for a command to run...

Pragmetic Programming Summary - 객체의 종류

Updated
•6 min read•View as Markdown

1] VO(Value Object: 값 객체)

1. 값의 특징

1) 불변성

변하지 않는 특성이다.

불변성이 중요한 이유는 소프트웨어는 불확실성으로 가득하나 이러한 특징으로 소프트웨어 중 일부를 예측할 수 있고 신뢰할 수 있게 만들어 주기 때문이다.
ex) 호출 시점에 따라 다른 결과를 반환하는 메서드라면, 이 메서드를 신뢰할 수 없다.

따라서 VO로 선언된 모든 멤버 변수는 **불변(final)**으로 선언돼 있어야 한다.
또한, VO 안의 모든 함수는 순수 함수여야 한다.
또한, 불변 객체 안의 참조 객체가 불변이 아니라면 안된다.
또한, VO 클래스는 final 클래스로 선언돼야 한다.(상속으로 불변이 깨질 수 있으므로)

@AllArgsConstructor
public class Color {
    public final int r; // final로 선언
    public final int g;
    public final int b;
    public final Shape shape;

    public Color randomColor() { // 매번 다른 결괏값을 반환한다.
        Random random = new RandomQ;
        int r = random.nextlnt(256);
        int g = random.nextlnt(256);
        int b = random.nextlnt(256);
        return new Color(r, g, b);
    }
}

@AllArgsConstructor
class Shape {
    private int width; // Shape의 멤버 변수가 불변이 아니다.
    private int height;
}

class NewColor extends Color {
    public int a; // 상속된 클래스의 멤버 변수가 불변이 아니다.
}

단, 객체를 완벽하게 불변으로 만드는 것이 중요한 게 아니라, 불변성이 지닌 가치를 좇아야 한다.(에너지 낭비) -> 불확실성을 줄여야 한다.(신뢰성을 높여 예측 가능하도록 바꾼다.)

예를 들어 객체를 다른 메서드의 매개변수로 넘겨주기도 하고 값을 돌려 받을 때 객체의 상태가 계속 변한다면 나중에는 이 객체에 어떤 값이 어떻게 변경됐는지 알 수 없어지게 되고 다양한 메서드를 호출한 이후에 객체의 상태가 여전히 그대로인지 확인하는 코드를 만들게 된다. if 문을 통해 일일이 코드를 검증하는 것이다.
객체가 불변이면, 이런 불안함을 겪지 않아도 된다.

2) 동등성

값이 같으면 같아야 한다.

Color green1 = new Color(0, 1, 0),
Color green2 = new Color(0, 1, 0),
System.out.println(greenl == green2);

다른 참조 값이니 달라야 할까? 아니면 값이(상태가) 같으니 같아야 할까?
-> 불확실성이 생긴다.

따라서 VO는 equals나 hashCode를 오버라이딩하여 값이 같으면 같게 만들어야 한다.

식별자

또한 VO에는 식별자를 넣어서는 안된다.

@AllArgsConstructor
public class Person {
    private final long id;
    private final String name;
}

Person p1 = new Person(1, "John");
Person p2 = new Person(1, "Smith");
System.out.println(p1 == p2);

식별자(id)가 같으니 같은 객체인가? 아니면 값이(상태가) 다르므로 다른 객체인가?
-> 불확실성이 생긴다.

참고사항

(1) @Value(Lombok)

class에 @Value를 사용하면 다음과 같은 기능이 추가된다.

  1. equals와 hashCode 메서드가 객체의 상태에 따라 비교하는 메서드로 자동 생성

  2. 멤버 변수가 final로 선언

  3. 클래스가 final로 선언

해당 객체를 명시적으로 VO로 나타낼 수 있다는 장점도 있다.

3) 자가 검증

한번 생성된 VO의 멤버 변수에는 이상한 값이 들어 있을 수 없다.

VO의 목표가 신뢰할 수 있고 예측 가능한 객체를 만드는 것이므로, 자가 검증을 시켜 상태 검증을 위해 if문, try문을 사용할 필요가 없게 하여야 한다.

@EqualsAndHashCode
@RequiredArgsConstructor
public class Color {
    public final int r;
    public final int g;
    public final int b;

    public String toHex() {
        return String.format("#%02x%02x%02x", r, g, b);
    }
}

System.out.println(new Color(300, 0, 0).toHex()); // #12c0000

color의 r, g, b에는 0 ~ 255 사이의 숫자가 들어가야 하는데, 자가 검증이 없어 300이 그대로 들어가고 있다.

결론은, VO의 목적은 신뢰할 수 있는 객체를 만들자 이다.

2] DTO(Data Transfer Object: 데이터 전송 객체)

데이터를 하나하나 일일이 나열해서 전달하는 게 불편해서 데이터를 하나로 묶어서 보내려고 만들어진 객체이다.

1. DTO의 오해

1) DTO는 프로세스, 계층 간 데이터 이동에 사용된다.

DTO는 API 통신이나 DB 통신에 사용할 수 있지만, 좀 더 범용적으로, 데이터를 전달하고 싶은 상황이라면 어디서든 사용될 수 있다.
(단, 매개변수가 너무 많아서 객체로 한번 감싸는 것은 일반적으로 추천되는 방식은 아니다. 메서드에 필요한 매개변수가 무엇인지에 관한 의존성을 감추기 때문이다.)

2) DTO는 Getter, Setter를 갖고 있다.

필요에 따라 멤버 변수에 public 선언을 활용할 수도 있고, 일부만 Getter를 제공할 수도 있다.
데이터를 전달한다는 본연의 임무를 다한다면 DTO이다.

class UserCreateRequest {
    public final String username;
    public final String password;
    public final String email;
    public final String address;
    public final String gender;
    public final int age;

    @Builder
    public UserCreateRequest(
            @JsonProperty("username") String username,
            @JsonProperty("password") String password,
            @JsonProperty("email") String email,
            @JsonProperty("address") String address,
            @JsonProperty("gender") String gender,
            @JsonProperty("age") int age) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.address = address;
        this.gender = gender;
        this.age = age;
    }
}

데이터가 할당된 이후의 변경을 불가능하게 해서 데이터를 보호 중이다.

단, 멤버 변수를 public으로 지정하라고 권장하는 것은 아니다.
캡슐화의 주요 목표 중 하나인 정보 은닉을 달성하기 위해 모든 멤버 변수는 private으로 선언하되 일부만 Getter를 제공하는 방향으로 가는 것이 유리하다.

3) DTO는 DB에 데이터를 저장하는 데 사용되는 객체다.

위에서 설명했듯이, DTO는 데이터를 전송하기 위한 객체 그 이상, 그 이하의 의미도 없다.

참고사항

(1) @Data(Lombok)

@Getter, @Setter, @ToString, @EqualsAndHashCode가 한 번에 지정된다.

@Data는 DTO를 정의하는 데 사용할 수 있는 annotation이지만, '이 객체는 말 그대로 객체가 아니라 그저 데이터 덩어리다'라고 선언하는 것과 같아 개발자들이 데이터 위주의 사고를 하도록 만들어 행동 위주의 사고를 추구하는 객체지향과 멀어지게 되므로 주의해야 한다.

3] DAO(Data Access Object: 데이터 접근 객체)

데이터베이스 접근과 관련된 역할을 지닌 객체이다.
DAO의 목적은 도메인 로직과 데이터베이스 연결 로직을 분리하기 위해서이다.

1. DAO의 역할

  1. 데이터베이스와의 연결을 관리

  2. 데이터베이스에 연결해 데이터에 대한 CRUD 연산을 수행

  3. 보안 취약성을 고려한 쿼리 작성

2. 도메인 로직과 DB 관련 로직 분리의 체크 사항

  1. 서비스 컴포넌트(@Service가 지정된 컴포넌트)에서 SQL 쿼리를 만든다.

  2. 서비스 컴포넌트에서 LIKE 검색을 위해 "%" 문자열을 앞뒤로 붙인다.

  3. 서비스 컴포넌트에서 EntityManager를 활용해 어떤 로직을 처리한다.

  4. 서비스 컴포넌트에서 JPA 관련 클래스나 인터페이스를 임포트한다.

  5. 주 데이터베이스로 관계형 데이터베이스를 사용하고 있을 경우 서비스 컴포넌트의 코드를 변경해야만 도큐먼트 데이터베이스(ex. MongoDB)로 전환할 수 있다. (없다면 체크)

4] Entity(개체)

Entity에는 세가지 종류가 있다.

  1. 도메인 Entity

  2. DB Entity

  3. JPA Entity

1. 도메인 Entity

도메인은 비즈니스 영역이라고 생각하면 된다.
따라서 은행에서 사용하는 소프트웨어를 만들기로 했다면 은행이 도메인이다.

1) 도메인 모델

어떤 도메인 문제를 해결하고자 만들어진 클래스 모델

은행 소프트웨어는 Account, Transaction, Money 같은 개념이 사용될 수 있고, 이 개념을 클래스로 만들 수 있다. 이렇게 만들어진 개념 모델들을 도메인 모델이라고 부른다.

2) 도메인 Entity

이러한 도메인 모델 중에는 Account나 Transaction 같은 식별자가 존재할 수 있고, 도메인 모델에 걸맞는 조금 특화된 비즈니스 로직을 가질 수 있는 특별한 모델이 있다.
이러한 특별한 기능을 갖고 있는 모델들을 도메인 Entity라고 부른다.

따라서도메인 Entity는 식별 가능하고 비즈니스 로직을 갖고 있으며, 조금 특별하게 괸리되는 클래스로 만들어진 객체이다.

2. DB Entity

도메인 Entity의 개념과 상관없이 관계형 데이터베이스 분야에서 어떤 유무형의 객체를 표현하는 데 사용했던 용어이다.

3. JPA Entity

관계형 데이터베이스에 있는 데이터를 객체로 매핑하는 데 사용되는 클래스

JPA는 관계형 데이터베이스에 뿌리를 두고 있어, JPA Entity는 도메인 Entity보다 DB Entity에 더 가까운 개념으로 이해할 수 있다.

추가하자면, 다음과 같다.

  1. 관계형 데이터베이스의 Entity를 지칭

  2. JPA의 @Entity가 적용된 객체는 '영속성 객체(PO: persistent obejct)'다.

  3. JPA의 @Entity는 영속성 객체를 만들기 위한 도구일 뿐이다.

4. Entity

정리하면, Entity는 데이터로 표현하려는 유무형의 대상이라고 할 수 있다.

참고사항

(1) JPA와 Hibernate

Hibernate는 JPA(Java Persistence API)에서 정의한 @Id, @Column, @Entity 같은 annotation을 참조해서 이를 기준으로 쿼리를 생성하고 데이터를 가져오는 실제 구현체 역할을 한다.

이렇게 기술 명세와 구현체를 따로 둬서, 구현체를 언제든 바꿀 수 있다.

즉, 역할과 구현을 분리해서 구현체에 종속되는 상황을 피한 것이다.
역할과 구현의 분리, 이는 객체지향에서만 적용되는 원리가 아니고 시스템 설계에서도 적용된다.

5] 객체의 다양한 종류

객체의 종류에는 VO, DTO, DAO, Entity 뿐 아니라, PO(Persistent Object), SO(Service Object, 비즈니스 로직을 처리하는 스프링의 서비스(@Service) 컴포넌트와 같은 개념) 등이 있다.

이런 객체의 다양한 종류들을 외우고 분류하는 것이 중요한 것이 아니라,
각 개념이 만들어진 이유와 목적을 생각하여 다음과 같은 가치를 추구해야 한다.

  1. 불변성

  2. 예측 가능성

  3. 역할의 분리

  4. 향상성

참고사항

(1) record

Java 16부터 정식으로 사용할 수 있는 record 키워드를 사용해서 만들어진 객체는 VO의 특징을 갖는다.

레코드를 이요해 만들어진 객체의 변수는 final 선언을 하지 않아도 final을 선언한 것과 같이 동작한다. 또한 equals, hashCode, toString 같은 메서드가 자동으로 만들어진다.

More from this blog

RFC 9110 HTTP Semantics - TRACE

1] TRACE trace는 요청 메시지의 원격 application-level의 loop-back을 요청한다. 요청의 최종 수신자(final recipient)는 민감한 데이터를 포함한 필드를 제외하고 수신한 메시지를 200(OK) 응답의 내용으로 클라이언트에 다시 반영해야 합니다.(should) message/http 형식은 이를 수행하기 위한 한 가지 방법이다. 최종 수신자는 Origin Server이거나 요청에서 Max-Forwards ...

Nov 28, 20241 min read

RFC 9110 HTTP Semantics - POST

1] POST POST 메서드는 target resource가 리소스 자체의 특정한 의미에 따라 요청에 포함된 representation을 처리하도록 요청한다. 예를 들어, 다음의 기능을 수행한다. HTML form에 입력된 필드같은 데이터 블록을 데이터 처리 프로세스에 제공 게시판, 뉴스 그룹, 메일링 리스트, 블로그, 또는 비슷한 기사 그룹에 메시지 게시(posting) origin server에서 식별되지 않은 새로운 리소스 생성 ...

Nov 2, 20242 min read

RFC 9110 HTTP Semantics Method Definitions GET

1] GET GET Method는 대상 리소스에 대한 현재 선택된 representation의 전송을 요청한다. 성공적인 응답은 대상 URI가 식별한 동일성의 품질을 반영한다.(계속 요청하면 항상 동일한 응답을 줌) 따라서 HTTP를 통해 식별 가능한 정보를 검색하는 것은 일반적으로 GET 요청을 통해 수행되며 200(OK) 응답을 준다. 리소스 식별자를 원격 파일 시스템 경로명으로, representation을 해당 파일 콘텐츠의 복사본으로 ...

Oct 9, 20243 min read

Spring Data JPA Dynamic Projections

1] 기존 Projections 특정한 Entity의 컬럼을 전부 가져오고 싶지 않을 때, 기존의 경우는 메서드마다 record를 생성하였다. 이때, 출처에서 적힌 것처럼, 여러 메서드가 하나의 record를 쓰는 경우가 생기게 된다. 예를 들어, 컬럼이 id, name, genre, 등등이 있을 때 기존에 id와 name만을 가져오는 record가 있을 경우 id, name, genre를 가져오는 메서드를 만들 경우 기존의 record에 ge...

Sep 25, 20242 min read

Jong-Dae's Coding Docs

17 posts