본문 바로가기

SpringBoot

TypeChange를 통해 Service코드를 간결하게 해보자!

애플리케이션 개발에서 DTO(Data Transfer Object)와 Entity 간의 변환은 매우 중요한 역할을 한다.

DTO는 클라이언트와 서버 간 데이터 전송을 담당하며, Entity는 데이터베이스와의 상호작용을 담당한다.

이 두 계층 사이의 변환을 깔끔하고 일관성 있게 처리하기 위해 TypeChange라는 헬퍼 클래스를 활용해보려고 한다.

 

쉽게 이해하기 위해 회원가입 로직을 바탕으로 설명을 해보겠다.

package com.swyp.playground.domain.parent.service;

@Service
@RequiredArgsConstructor
public class ParentService {
    private final ParentRepository parentRepository;
    private final TypeChange typeChange;
    private final PasswordEncoder passwordEncoder;

    public ParentCreateResDto signUp(ParentCreateReqDto request) {
        String encodedPassword = passwordEncoder.encode(request.getPassword());
        Parent parent = typeChange.parentCreateReqDtoToParent(request, encodedPassword);
        Parent savedParent = parentRepository.save(parent);

        return typeChange.parentToParentCreateResDto(savedParent);
    }
}

 

엇... 뭔가 상당히 간단하지 않은가??

깔끔그자체

 

TypeChange 클래스란?

TypeChange는 DTO를 Entity로, 또는 Entity를 DTO로 변환하는 작업을 담당하는 컴포넌트 클래스이다.

이 클래스를 사용하면 DTO와 Entity 간의 변환 로직을 재사용 가능하게 만들고, 컨트롤러나 서비스 코드에서 변환 작업을 분리하여 가독성과 유지보수성을 높일 수 있다.

 

 

아래 코드를 보면서 살펴보자.

package com.swyp.playground.common.domain;

import com.swyp.playground.domain.parent.domain.Parent;
import com.swyp.playground.domain.parent.dto.req.ParentCreateReqDto;
import com.swyp.playground.domain.parent.dto.res.ParentCreateResDto;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Component
public class TypeChange {

    public Parent parentCreateReqDtoToParent(ParentCreateReqDto dto, String encodedPassword) {
        return Parent.builder()
                .name(dto.getName())
                .email(dto.getEmail())
                .password(encodedPassword)
                .nickname(dto.getNickname())
                .address(dto.getAddress())
                .role(dto.getRole())
                .birthDate(dto.getBirthDate())
                .phoneNumber(dto.getPhoneNumber())
                .childCount(dto.getChildCount())
                .introduce(dto.getIntroduce())
                .joinedAt(LocalDateTime.now())
                .mannerTemp(BigDecimal.valueOf(50.0))
                .build();
    }

    // Parent 엔티티를 ParentCreateResDto로 변환
    public ParentCreateResDto parentToParentCreateResDto(Parent parent) {
        return ParentCreateResDto.builder()
                .id(parent.getParentId())
                .name(parent.getName())
                .email(parent.getEmail())
                .nickname(parent.getNickname())
                .build();
    }
}

 

 

 

 

1. ParentCreateReqDto → Parent (DTO → Entity)
클라이언트로부터 전달받은 ParentCreateReqDto 객체를 데이터베이스에 저장 가능한 Parent 엔티티로 변환하는 작업

  • Builder 패턴: Builder를 활용해 가독성을 높이고, 필드 초기화를 안전하게 처리.

 

2. Parent → ParentCreateResDto (Entity → DTO)
데이터베이스에서 조회한 Parent 엔티티를 클라이언트로 반환하기 위한 ParentCreateResDto로 변환한다.

 

 

TypeChange 클래스를 활용하는 이유

  1. 코드 재사용성 증가
    변환 로직을 하나의 클래스로 관리함으로써, 다양한 서비스 계층에서 동일한 로직을 중복 구현하지 않아도 됨
  2. 가독성과 유지보수성 향상
    변환 로직이 분리되어 있어, 컨트롤러와 서비스 계층의 코드를 간결하고 읽기 쉽게 만들어줌
  3. 일관성 유지
    DTO와 Entity 간 변환 규칙을 중앙 집중적으로 관리하여, 변환 작업에 일관성을 제공한다.
  4. 테스트 용이성
    변환 로직을 독립적으로 테스트할 수 있으므로, 예외 처리나 예상치 못한 입력 데이터에 대한 검증이 간편하다.

 

만약 typechange의 방법을 사용하지 않았다면, service의 코드는 아래와 같이 적을 수 있겠다.

@Service
public class ParentService {

    private final ParentRepository parentRepository;
    private final PasswordEncoder passwordEncoder;

    public ParentService(ParentRepository parentRepository, PasswordEncoder passwordEncoder) {
        this.parentRepository = parentRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public ParentCreateResDto signUp(ParentCreateReqDto request) {
        String encodedPassword = passwordEncoder.encode(request.getPassword());

        
        Parent parent = Parent.builder()
                .name(request.getName())
                .email(request.getEmail())
                .password(encodedPassword)
                .nickname(request.getNickname())
                .address(request.getAddress())
                .role(request.getRole())
                .birthDate(request.getBirthDate())
                .phoneNumber(request.getPhoneNumber())
                .childCount(request.getChildCount())
                .introduce(request.getIntroduce())
                .joinedAt(LocalDateTime.now())
                .mannerTemp(BigDecimal.valueOf(50.0))
                .build();

        
        Parent savedParent = parentRepository.save(parent);

        
        return ParentCreateResDto.builder()
                .id(savedParent.getParentId())
                .name(savedParent.getName())
                .email(savedParent.getEmail())
                .nickname(savedParent.getNickname())
                .build();
    }
}