공부/Spring Boot

[Spring Boot] Service 계층과 DTO 존재 이유에 대한 생각

jihyee 2021. 12. 18. 01:28

공부할수록 어려운 스프링..

 

 

그래도 쉬운 것보단 어려운 게 공부가 되겠지..,,

 

 

 

 

٩( ö̆ )و

 

 

 

 

 

node.js 랑 비교했을 때 spring boot 가 말도 안 되게 CRUD 짜는 게 복잡하다..

 

스프링 부트 : controller - entity - service - repositoy

 

노드 : model - route

 

class 만 몇 개를 만드는 건지..

 

 

이런 생각을 했던 이유 중 하나는 스프링 부트를 처음 공부했던 몰캠 시절에 service는 도대체 왜 있냐는 팀원의 질문이 생각나서기도 하다.

 

 

그땐 spring boot 가 뭔지도 몰랐던 때라 아마 사용자 입장에서 메서드 편하게 불러오려는 거 아닐까 라는 ... 대답을 ..

 

( ⚆ _ ⚆ )

 

참 미안하게 생각한다

 

 

 

 

 

 

 

그때 생각도 나고,,  왜 때문일까 고민하다가 serivce를 없애고 controller에서 바로 repository에 접근하는 방식으로 코드를 고쳐봤다. 즉 프레젠테이션 계층에서 데이터 액세스 계층으로 직접 접근하는 방식으로 코드를 고쳐봤다. 당연히 dto도 안 쓰고 service autowired도 안 걸어도 되는 형태이다.

 

 

 

절대 안 되겠지만

 

@GetMapping()
public ApiResponse getLoginUser(@RequestParam String token) {

  UserRefreshToken byRefreshToken = userRefreshTokenRepository.findByRefreshToken(token);

  User byUserId = userRepository.findByUserId(byRefreshToken.getUserId());

  return ApiResponse.success("user", byUserId);
}

 

이렇게 하면 controller - entity - repository 만 있는 아주 깔끔한 상태가 된다.

 

코드는 깔끔하지만 사실 작동하지도 않고 좋은 코드도 아니다.

 

 

 

 

 

 

 

이유를 생각해보면,

 

 

 

  1. DAO vs DTO 차이를 이해해야 한다.

 

DAO는 Data Access Object의 약자로 생성한 entity라고 생각하면 된다. 스프링 부트의 entity 형태를 코드로 보면

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "USER")
public class User {
    @JsonIgnore
    @Id
    @Column(name = "USER_SEQ")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userSeq;

    @Column(name = "USER_ID", length = 64, unique = true)
    @NotNull
    @Size(max = 64)
    private String userId;

    @Column(name = "USERNAME", length = 100)
    @NotNull
    @Size(max = 100)
    private String username;

    @JsonIgnore
    @Column(name = "PASSWORD", length = 128)
    @NotNull
    @Size(max = 128)
    private String password;

    @Column(name = "EMAIL", length = 512, unique = true)
    @NotNull
    @Size(max = 512)
    private String email;

    @Column(name = "EMAIL_VERIFIED_YN", length = 1)
    @NotNull
    @Size(min = 1, max = 1)
    private String emailVerifiedYn;

    @Column(name = "PROFILE_IMAGE_URL", length = 512)
    @NotNull
    @Size(max = 512)
    private String profileImageUrl;

    @Column(name = "PROVIDER_TYPE", length = 20)
    @Enumerated(EnumType.STRING)
    @NotNull
    private ProviderType providerType;

    @Column(name = "IMAGE", length = 512)
    @NotNull
    @Size(max = 512)
    private String image;

    @Column(name = "USER_INFO", length = 512)
    @NotNull
    @Size(max = 512)
    private String userInfo;

    @Column(name = "ROLE_TYPE", length = 20)
    @Enumerated(EnumType.STRING)
    @NotNull
    private RoleType roleType;

    @Column(name = "CREATED_AT")
    @NotNull
    private LocalDateTime createdAt;

    @Column(name = "MODIFIED_AT")
    @NotNull
    private LocalDateTime modifiedAt;

    @OneToMany(mappedBy = "joinUser")
    private List<Join> userJoins;

    @OneToMany(mappedBy = "replyUser")
    private List<Reply> userReplys;
}

 

이와 같은데 repository에서 내장 메서드 써서 불러온 객체는 이런 상태로 저장되고 이게 DAO이다.

 

만약 이 DAO를 가지고 계층을 거치면서 데이터를 수정하고 사용하면 연관 관계 매핑 때문에 아마 코드 돌리면 양방향 연결 관계있으면 재귀로 계속 서로를 호출하고 있을 거다.

→ DAO는 Persistent 만을 위해서 사용한다.

 

 

※ 즉 DB 랑 연결되어서 도메인 entity로 받아온 데이터인 DAO는 계층을 거치면서 사용하면 절대 안 된다※

 

 

이런 DAO를 대신해서 데이터를 들고 다니려고 만드는 게 바로 DTO이며 Data Transfer Object의 약자이다.

 

 

Transfer 이랑 Access의 차이가 결국 DAO 랑 DTO의 차이라고 생각하면 된다.

 

 

@Data
public class UserDto {
    private Long userSeq;

    private String username;

    private String password;

    private String email;

    private String emailVerifiedYn;

    private String profileImageUrl;

    private ProviderType providerType;

    private String image;

    private String userInfo;

    private RoleType roleType;

    private LocalDateTime createdAt;

    private LocalDateTime modifiedAt;

    private List<Join> userJoins;
}

 

위 DAO에서 봤던 entity 랑은 다른 형태이다. 이름처럼 계층 간의 데이터 전달 목적으로만 사용되는 객체이기 때문에 사용되는 변수만 잘 연결해서 복사해준다고 생각하면 된다.

 

결론

DAO 직접 들고 다니지 말자 DTO로 바꿔서 들고 다니자

 

 

 

2. 프레젠테이션 계층 즉 controller에서 비즈니스 로직을 명시하는 것은 좋지 않다.

 

비즈니스 로직은 다른 요청 URL에서 사용해야 하는 경우가 있다. 만약 비즈니스 로직 코드가 컨트롤러에 구현되어 있는 경우, 다른 컨트롤러의 핸들러 메서드에서 똑같은 로직 코드를 구현해야 하니 중복 코드 발생하고 재사용성 줄어든다.

 

위의 예시는 그냥 repo에서 조회하는 로직이 전부였지만 (한 줄) 만약 이 로직이 굉장히 복잡하고 긴 코드로 되어있다고 가정하면 이 비즈니스 로직을 필요로 하는 다른 요청이 있는 경우 그것 또 controller에서 다시 작성해줘야한다. 근데 이걸 service 계층으로 분리를 하면 service 에서 해당 비즈니스 로직을 가져다 사용하면 되기 때문에 코드를 중복으로 작성할 필요없이 재사용이 가능하다.

 

 

결론

비즈니스 로직을 controller 에서 직접 작성하는 게 아니라 service 계층을 따로 두어 모듈화를 하자