기타

DTO에서 왜 엔티티를 만들어요? (ToEntity)

wonow_ 2024. 9. 4. 01:34

 

DTO에서 왜 엔티티를 만들어요?

 

DTO에서 엔티티를 왜 만들어요..?

 

돌아오는 답변은 이렇다.

 

1. 엔티티는 더욱 추상적인 모델이라서, 상대적으로 더 낮은 레벨인 DTO가 변하면 엔티티의 생성자를 변경해야한다.

그러니까 즉 의존성이 생겨버린 다는 것이다.

 

https://wonowdaily.tistory.com/97

 

TIL 2024-01-07 엔티티에서 Dto 종속성을 갖게 하는 게 맞을까?

오늘 깊게한 고민이다. 개요 팀 프로젝트 진행 중, Entity에 대한 생성을 어떻게 할 것인지 고민을 많이 했다. 우선 생성 방식은 정적 팩토리 메서드를 사용하기로 했다. 이유는 다음과 같다. 생성

wonowdaily.tistory.com

 

내가 예전에 썼던 글이다.

나도 이때는 그렇게 생각하고 있었다. 엔티티가 DTO에 대해 의존성이 생기는 거 아닌가?

나는 이때와 생각이 많이 바뀌었다.

왜 바뀌었을까?는 이후에 설명을 들어보자

 

2. 코드가 깔끔해져서요

코드가 깔끔해진다는 것은 장점이 아니다

객체지향은 객체만 봤을 때 설명을 할 수 있어야한다.

적어도 나는 그렇게 생각한다.

왜 깔끔해진 걸까? 를 생각하면 그리 좋은 코드가 아니라는 것을 알 수 있다.

 

빈혈 객체

빈혈 객체는 자신에 대한 설명이 텅 비어있는 객체를 얘기한다.

 

 

위와 같이 유저에 대한 Validation을 서비스 코드에 정의해버리면 User 클래스를 봤을 때 무슨 역할을 하는 지 하나도 모른다.

 

User에 대한 설명을 User Class 가 아닌 UserService 가 해버리는 것이다.

 

하지만 Validation 코드를 User 클래스에 적어준다면?

 

어떤 행동을 하는 객체인지 클래스 파일만 봐도 이해 할 수 있다.

 

생각이 드는 게 있는가?

Dto에서 엔티티를 만들면, 해당 엔티티만 봤을 때 어떻게 생성되는지를 모른다.

 

Entity를 생성하는 DTO가 여러개 있다고 생각해보자.

 

극단적인 예시긴하나, 이렇게 많이 있다고 하자..

 

그러면 Entity는 이렇게 속이 텅 비어버린 객체가 된다.

 

어떻게 생성 되는지 이 Entity만 봐서 알 수 있는가?

 

위의 Entity를 생성하는 것은 다른 클래스인 DTO를 봐야 이해할 수 있다.

 

그런데 여기서 DTO 1, 2, 3, 4, 5, 6 마다 ToEntity에 대한 규칙이 다르면 어떻게 되는 걸까?

 

public class DTO1 {

    private String name;
    private String description;
    
    // 생성자 생략
    
    public Entity ToEntity() {
    	
        if (name.length() < 3) {
        	name = name + "세글자";
        }
        
        return Entity.builder()
                     .name(this.name)
                     .description(this.description)
                     .build();
    }
}

public class DTO2 {

    private String name;
    private String description;
    
    // 생성자 생략
    
    public Entity ToEntity() {
    	
        if (name.length() < 2) {
        	name = name + "두글";
        }
        
        return Entity.builder()
                     .name(this.name)
                     .description(this.description)
                     .build();
    }
}

 

엔티티에 대한 생성 규칙을 DTO를 봐야한다.

 

Entity에 대한 설명을 다른 클래스를 봐야 된다는게 어떤 단점이 생기는지 모르겠는가?

 

이제 알 거 같지 않은가?

 

 

엔티티는 DTO에 대해 의존성이 생기는 게 아니다.

 

엔티티는 DTO 에 대해 의존성이 생기는 게 아니라는 말을 정말 전하고 싶다.

 

우선 DTO가 뭔지부터 정확히 알아야한다.

 

DTO는 행동이 없는 클래스다.

객체도 아니다 사실

DTO는 데이터 이동만을 위한 클래스다.

그러니까 행동이 있으면 안된다.

 

DTO에 특별한 행동을 넣은 적이 있는가?

 

Validation은 예외다. 그건 DTO 생성에 대한 규칙이니

 

public class DTO1 {

    private String name;
    private String description;
    
    // 생성자 생략
    
    public void entitySet(Entity entity) {
    	
        entity.setName(name);
        entity.setDescription(description);
    }
}

 

이런 코드를 본적이 있는가?

어? 한 번도 본적없다.

 

왜 본 적이 없을까?

 

DTO는 정말로 레이어간 데이터 이동만을 위한 클래스다.

 

내가 왜 객체가 아니라고 계~속 얘기할까?

 

객체는 행동이 정의가 돼 있어야 객체다.

행동이 정의가 안 돼 있으면 그건 자료구조쪽에 더욱 가깝다.

 

DTO는 원시타입 및 자료구조를 모아놓는 자료구조 형식을 띄고 있다.

 

C언어를 공부했으면 Struct 라는 것을 알 것이다.

 

DTO는 Struct 형식을 띄고있다.

 

출처: https://makerejoicegames.tistory.com/104

 

위의 Struct 클래스를 보자 그냥 진짜 자료형이다.

 

자료형만을 모아두는 클래스다.

 

진짜진짜 사실 클래스도 아니다 구조체다.

 

DTO는 구조체로 생각해야한다.

그러니까 무엇을 만든다거나 그런 행동이 있으면 안된다는 거다.

 

의존성

 

DTO가 변하면, Entity가 변하는 말을 한다.

나도 이건 동의한다.

DTO의 필드 이름등이 바뀌면 Entity 의 내용을 수정해야한다.

 

public record DTO (
    String name,
    String descrition
){

}

@Builder(access = PRIVATE)
public class Entity {

    private String name;
    private String description;
    
    // 생성자 등 생략
    
    public Entity from(DTO dto) {
    
    	return Entity.builder().
                     .name(dto.name())
                     .description(dto.description())
                     .build();
    }
}

 

위의 코드를 보자.

 

저기서 만약 DTO의 Description 을 지운다면

 

public record DTO (
    String name
){

}

@Builder(access = PRIVATE)
public class Entity {

    private String name;
    private String description;
    
    // 생성자 등 생략
    
    public static Entity from(DTO dto) {
    
    	return Entity.builder().
                     .name(dto.name())
                     // .description(dto.description()) 오류 발생!!
                     .build();
    }
}

 

이렇게 오류가 생겨버린다.

 

근데 여기서 문제가 뭔지 아는가?

 

Create 성 DTO 의 변경은 DTO 의 변경이 아닌 Entity의 생성 규칙을 바꾸는 것이다.

 

DTO가 변경이 되면.. Entity의 생성 규칙이 바뀐다는 것이다.

 

지금 from 메서드는 생성자 역할을 대신해주는 정적 팩토리 메서드다.

그러니까 Entity 생성하는 것을 Dto 로만 생성하겠다고 규칙을 정해둔 것이다.

 

DTO의 변경은

Entity의 생성 규칙을 바꾸는 것이다.

 

그러면 Mapper 를 사용해보는 게 어때요?

내가 맨 위에서 말했다.

빈혈 객체

빈혈 객체가 생긴다.

 

1. Entity의 생성 규칙이 외부로 분리된다.

2. Entity Class 만 봤을 때 생성 규칙에 대한 이해가 안된다.

 

위의 두개만 봐도 Mapper를 사용할 이유가 사라진다.

 

예외는 있다.

 

객체에 대한 생성이 아주 복잡할 때

Mapper 를 쓰지 않고 팩토리 클래스를 만든다.

 

나는 사실 이 팩토리 클래스도 싫어한다.

 

1번과 2번 둘 다 해당 되기 때문이다.

 

팀에서 Entity에 대한 생성 규칙을 팩토리 클래스로만 생성하자고 정한다고 생각해보자.

사실 여기서 부터가 잘못된 것이다.

객체는 사람끼리 서로 규칙을 정하는 게 아니라

객체에 써있는 규칙으로 생성되고 행동해야지 진짜 객체지향적인 것이라고 생각한다.

 

사람끼리 서로 규칙을 정하면 어떻게 될까?

휴먼에러가 생긴다.

누군가 팩토리 클래스로 생성하지 않고 일반적인 생성자를 사용해서 객체 생성을 할 수가 있다.

그러면 이건 개발자가 잘못한 게 아닌, 설계를 잘못한거다.

 

안 그런가?

 

정말로 객체에 대해 잘 이해를 하고 코드를 작성하면, 주석도 필요없다.

 

인터페이스, 클래스, 메서드 등만 봐도 이해가 잘 되기 때문이다.

 

결론

toEntity등 과 같은 메서드

Entity 생성자 내에서 Dto를 받냐 안받냐는

논쟁거리가 되지 않는다고 생각한다.

그런데 검색을 해보니, 각종 블로그에서 이게 좋다고 코드 깔끔해지니까 좋은 코드라고 칭송하는 글이 많았다.

그리고 주위를 둘러보니 그런 방법으로 코드를 적는 사람들이 많았다.

위의 방법은 객체지향 설계 원칙을 훼손하는 방법이다.

Entity가 자신에 대한 생성 규칙을 관리하도록 설계하는 것이 더욱 객체지향 적인 코드다.

그래야 일관성이 생기고 유지보수적인 측면에서도 올라가게 된다.