Home > Troubleshooting > πŸ”[Troubleshooting] πŸš€ DTO 뢄리: μš©λ„λ³„ μ „μš© DTO 섀계

πŸ”[Troubleshooting] πŸš€ DTO 뢄리: μš©λ„λ³„ μ „μš© DTO 섀계
Troubleshooting Backend Development Spring Boot

πŸš€ DTO 뢄리: μš©λ„λ³„ μ „μš© DTO 섀계


🧐 무엇이 λ¬Έμ œμΈκ°€μš”?

ν˜„μž¬ ChapterRequestDtoλŠ” μ—¬λŸ¬ κ°€μ§€ 역할을 ν•œ λ²ˆμ— μˆ˜ν–‰ν•˜λ €λŠ” β€˜λ§ŒλŠ₯ DTO’ κ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ΄λŠ” API μ„€κ³„μ˜ λͺ…확성을 μ‹¬κ°ν•˜κ²Œ ν•΄μΉ˜λŠ” μ•ˆν‹°νŒ¨ν„΄(Anti-Pattern) μž…λ‹ˆλ‹€.

1. API의 μ˜λ„κ°€ λΆˆλΆ„λͺ…ν•΄μ§‘λ‹ˆλ‹€

searchChapter λ©”μ„œλ“œλŠ” 이름 κ·ΈλŒ€λ‘œ β€˜κ²€μƒ‰β€™μ„ μœ„ν•œ κΈ°λŠ₯μž…λ‹ˆλ‹€.

그런데 νŒŒλΌλ―Έν„°λ‘œ λ°›λŠ” ChapterRequestDtoμ—λŠ” 검색과 λ¬΄κ΄€ν•œ chapterNumberλ‚˜ detailChapter 같은 ν•„λ“œλ“€μ΄ ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

APIλ₯Ό μ‚¬μš©ν•˜λŠ” κ°œλ°œμžλŠ” β€œκ²€μƒ‰ν•˜λŠ”λ° 이 ν•„λ“œλ“€μ€ μ™œ ν•„μš”ν•˜μ§€? null둜 보내도 λ˜λ‚˜?” λΌλŠ” ν˜Όλž€μ— λΉ μ§€κ²Œ λ©λ‹ˆλ‹€.

2. 잘λͺ»λœ 데이터가 전달될 수 μžˆμŠ΅λ‹ˆλ‹€

λ‹€λ₯Έ κ°œλ°œμžκ°€ μ˜€ν•΄ν•˜μ—¬ 검색 μ‹œ chapterTitle뿐만 μ•„λ‹ˆλΌ λΆˆν•„μš”ν•œ detailChapter μ •λ³΄κΉŒμ§€ μ±„μ›Œμ„œ 보낼 수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λŠ” λ„€νŠΈμ›Œν¬ 낭비일 뿐만 μ•„λ‹ˆλΌ, μ„œλ²„ μΈ‘μ—μ„œ μ˜λ„μΉ˜ μ•Šμ€ λ™μž‘μ„ μœ λ°œν•  μˆ˜λ„ μžˆλŠ” 잠재적 λ²„κ·Έμ˜ 원인이 λ©λ‹ˆλ‹€.

3. 단일 μ±…μž„ 원칙(SRP) μœ„λ°°

ν˜„μž¬ ChapterRequestDtoλŠ” β€˜μ±•ν„° μƒμ„±μš© 데이터’와 β€˜μ±•ν„° κ²€μƒ‰μš© λ°μ΄ν„°β€™λΌλŠ” 두 κ°€μ§€ μ±…μž„μ„ λ™μ‹œμ— μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€.

쒋은 μ†Œν”„νŠΈμ›¨μ–΄ μ„€κ³„λŠ” 각 ν΄λž˜μŠ€κ°€ 단 ν•˜λ‚˜μ˜ μ±…μž„λ§Œ 갖도둝 ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.


βœ… ν•΄κ²° λ°©μ•ˆ: 역할에 따라 DTOλ₯Ό λΆ„λ¦¬ν•˜μ„Έμš”

κ°€μž₯ 쒋은 해결책은 β€˜μš©λ„μ— λ§žλŠ” μ „μš© DTO’ λ₯Ό 각각 λ§Œλ“€μ–΄ μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

1. 검색 μ „μš© DTO 생성

검색에 ν•„μš”ν•œ chapterName ν•„λ“œλ§Œ κ°€μ§„ ChapterSearchRequestDtoλ₯Ό μƒˆλ‘œ λ§Œλ“­λ‹ˆλ‹€.

클래슀 μ΄λ¦„λ§Œ 봐도 이 DTO의 역할이 무엇인지 λͺ…ν™•ν•˜κ²Œ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ“„ dto/request/ChapterSearchRequestDto.java (μ‹ κ·œ 생성)

package com.kobe.koreahistory.dto.request;

import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * '챕터 검색'μ΄λΌλŠ” μ—­ν• λ§Œ μˆ˜ν–‰ν•˜λŠ” μ „μš© DTO
 */
@Getter
@NoArgsConstructor
public class ChapterSearchRequestDto {
    private String chapterName;
}

2. 생성/μˆ˜μ •μš© DTO 이름 λ³€κ²½ (μ„ νƒμ‚¬ν•­μ΄μ§€λ§Œ ꢌμž₯)

κΈ°μ‘΄ ChapterRequestDtoλŠ” μ΄λ¦„λ§Œ λ΄μ„œλŠ” 역할을 μ•ŒκΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€.

β€˜μ±•ν„° 생성’에 μ‚¬μš©λœλ‹€λ©΄ ChapterCreateRequestDto와 같이 더 λͺ…ν™•ν•œ μ΄λ¦„μœΌλ‘œ λ³€κ²½ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

πŸ“„ dto/request/ChapterCreateRequestDto.java (이름 λ³€κ²½)

package com.kobe.koreahistory.dto.request;

import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;

/**
 * '챕터 생성'에 ν•„μš”ν•œ λͺ¨λ“  정보λ₯Ό λ‹΄λŠ” DTO
 */
@Getter
@NoArgsConstructor
public class ChapterCreateRequestDto {
    private int chapterNumber;
    private String chapterTitle;
    private List<DetailChapterRequestDto> detailChapters;
}

3. Controller에 μ˜¬λ°”λ₯Έ DTO 적용

이제 KoreanHistoryController의 searchChapter λ©”μ„œλ“œλŠ” 검색 μ „μš© DTO인 ChapterSearchRequestDtoλ₯Ό μ‚¬μš©ν•˜λ„λ‘ μˆ˜μ •ν•©λ‹ˆλ‹€.

πŸ“„ μˆ˜μ •λœ KoreanHistoryController.java

package com.kobe.koreahistory.controller;

import com.kobe.koreahistory.dto.request.ChapterSearchRequestDto;
import com.kobe.koreahistory.dto.response.ChapterResponseDto;
import com.kobe.koreahistory.service.ChapterService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class KoreanHistoryController {

    private final ChapterService chapterService;

    @PostMapping("/search/chapters")
    public ResponseEntity<ChapterResponseDto> searchChapters(
        @RequestBody ChapterSearchRequestDto requestDto
    ) {
        return ResponseEntity.ok(
            chapterService.findChapterWithDetails(requestDto.getChapterName())
        );
    }
}

✨ κ°œμ„  ν›„μ˜ μž₯점

API λͺ…μ„Έμ˜ λͺ…ν™•μ„±

이제 searchChapters APIλ₯Ό λ³΄λŠ” κ°œλ°œμžλŠ” μš”μ²­ Body에 chapterName ν•˜λ‚˜λ§Œ λ„£μœΌλ©΄ λœλ‹€λŠ” 사싀을 DTO의 이름과 ν•„λ“œλ§Œ 보고도 μ¦‰μ‹œ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

ν˜Όλ™μ˜ μ—¬μ§€κ°€ μ‚¬λΌμ§‘λ‹ˆλ‹€.

μœ μ§€λ³΄μˆ˜ μš©μ΄μ„±

λ‚˜μ€‘μ— 검색 쑰건이 μΆ”κ°€λ˜λ©΄ ChapterSearchRequestDto만 μˆ˜μ •ν•˜λ©΄ λ©λ‹ˆλ‹€.

생성 λ‘œμ§μ— μ•„λ¬΄λŸ° 영ν–₯을 μ£Όμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

μ•ˆμ •μ„±

λΆˆν•„μš”ν•œ 데이터가 μ„œλ²„λ‘œ 전달될 κ°€λŠ₯성을 μ›μ²œμ μœΌλ‘œ μ°¨λ‹¨ν•˜μ—¬ 더 κ²¬κ³ ν•œ μ½”λ“œκ°€ λ©λ‹ˆλ‹€.


πŸ’‘ κ²°λ‘ 

β€œν•˜λ‚˜μ˜ DTOλ₯Ό μ—¬λŸ¬ APIμ—μ„œ λŒλ €μ“°μ§€ 말고, 각 API의 역할에 λ§žλŠ” μ „μš© DTOλ₯Ό λ§Œλ“€λΌβ€

DTO 섀계 원칙

  • βœ… μš©λ„λ³„ 뢄리: μƒμ„±μš©, μˆ˜μ •μš©, κ²€μƒ‰μš© DTOλ₯Ό 각각 λ§Œλ“€κΈ°
  • βœ… λͺ…ν™•ν•œ 넀이밍: DTO μ΄λ¦„λ§Œ 봐도 μš©λ„λ₯Ό μ•Œ 수 있게
  • βœ… 단일 μ±…μž„: ν•˜λ‚˜μ˜ DTOλŠ” ν•˜λ‚˜μ˜ μ—­ν• λ§Œ
  • βœ… ν•„μš”ν•œ ν•„λ“œλ§Œ: λΆˆν•„μš”ν•œ ν•„λ“œλŠ” ν¬ν•¨ν•˜μ§€ μ•ŠκΈ°