Development
DTO - ์ด๋ ๊ฒ ์ฌ์ฉํ์ง ๋ง์ธ์
3/6/2025

DTOData Transfer Object๋ ๊ณ์ธต ๊ฐ ๊ตํ์ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ๊ฐ์ฒด์
๋๋ค. ๊ฐ๋ ํจ๊ป ๊ณต๋ถํ๋ ๊ฐ๋ฐ์๋ค์ ์ฝ๋๋ฅผ ๋ณผ ๋, '์ด๋ฐ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ด ๋ฐ๋์์ ์ข๊ฒ ๋ค'๋ผ๋ ์๊ฐ์ด ๋ค ๋๊ฐ ์์ด ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด ๋ณด์์ต๋๋ค.
๊ณ์ธต์ ๋ฌด๋ ฅํํ๋ DTO
DTO์ ์๋ชป๋ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ดํด๋ณด๊ณ , ๊ทธ๋ก ์ธํด ๋ฐ์ํ๋ ๋ฌธ์ ์ ์ ์์๋ณด๊ฒ ์ต๋๋ค.
์์ ๋ก ์ฌ์ฉํ Article ๋๋ฉ์ธ์ ๊ฒ์ํ์ ๊ฒ์๊ธ์ ๋ํ๋ด๋ฉฐ, title, content, authorName ์ ๊ฐ๋๋ค๊ณ ๊ฐ์ ํฉ๋๋ค.
๊ฒ์๊ธ์ ์์ฑํ๋ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ๊ธฐ ์ํด, ์ฌ์ฉ์๋ก๋ถํฐ ๋ฐ์์ผํ๋ DTO ๊ฐ์ฒด๋ฅผ ์ ์ํ๊ณ ์ปจํธ๋กค๋ฌ๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค. ๋ํ, content๋ 50์ ์ด์, 200์ ์ดํ๋ผ๋ ์ ์ฝ์ ๋ฃ์ด์ฃผ์์ต๋๋ค.
@Getter
public class ArticleCreateRequest {
private String title;
@Size(min = 50, max = 200)
private String content;
private String authorName;
}@RestController
@RequiredArgsConstructor
@RequestMapping("/api/articles")
public class ArticleController {
@PostMapping
public void createArticle(
@Valid @RequestBody ArticleCreateRequest articleCreateRequest
)
{
//todo call article service
}
}๊ณ์ธต์ ๊ตฌ๋ถํ๋ ๊ฒ์ ์ค์ํ๋ Article์ ์์ฑํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์๋น์ค ๋ ์ด์ด์์ ์ฒ๋ฆฌํ๋๋ก ํฉ๋๋ค. ์๋น์ค ๋ฉ์๋์์ ํ์ํ ์ ๋ณด๋ title, content์ authorName์ผ๋ก, ๊ณต๊ต๋กญ๊ฒ๋ DTO์ ๋ชจ๋ ํฌํจ๋์ด์์ผ๋ฏ๋ก ๊ทธ๋๋ก ์๋น์ค ๋ ์ด์ด์ ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ฒ ์ต๋๋ค.
@Service
public class ArticleService {
private final ArticleRepository articleRepository;
@Transactional
public void createArticle(ArticleCreateRequest req) {
Article article = new Article(
req.getTitle(), req.getContent(), req.getAuthorName()
);
articleRepository.save(article);
}
}์๋ฃ๋์์ต๋๋ค! ํ ์คํธ๋ฅผ ํด๋ณด๋ ์ ๋์ํ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ์ด์ PR ๋ ๋ฆฌ๊ณ ๋ฐ๋ป๊ณ ๋์์ ๋ฆฌ๋ทฐ๋ฅผ ๊ธฐ๋ค๋ฆด ์ฐจ๋ก์ ๋๋ค.
๋ฌธ์ ์
ํ๋ ์ด๋ฌํ ๊ตฌ์กฐ๋ ๋ช๊ฐ์ง ๋ฌธ์ ์ ์ ๊ฐ์ต๋๋ค.
1. ์น ๊ณ์ธต์ ๋ณ๊ฒฝ์ด ๋๋ฉ์ธ ์๋น์ค ๋ก์ง์ ์ํฅ์ ์ค๋ค.
์ด์ ์ธ์ฆ ์์คํ ์ด ์ถ๊ฐ๋์ด์ ์ฌ์ฉ์๋ก๋ถํฐ authorName์ ๋ฐ์ง ์์ต๋๋ค. aurhorName์ ์ฌ์ฉ์ ํ ํฐ์์ ์ถ์ถํ๋ค๊ณ ๊ฐ์ ํฉ์๋ค.
//ArticleCreateRequest.java
public ArticleCreateRequest {
private String title;
@Size(min = 50, max = 200)
private String content;
}
//ArticleController.java
public class ArticleController {
public void createArticle(
@Login String username,
@RequestBody ArticleCreateRequest articleCreateRequest
) {
articleService.createArticle(articleCreateRequest); //???
}
}ArticleCreateRequest์์๋ ๋ ์ด์ authorName์ ํฌํจํ์ง ์์ผ๋ฉฐ, ์ปจํธ๋กค๋ฌ์์ @Login ์ ๋ ธํ ์ด์ ์ ํตํด ์ถ์ถํ ์ ์์ต๋๋ค. ์น ๊ณ์ธต์์ ์ฌ์ฉ์์ ์ด๋ฆ์ ์ป๋ ํํ๋ง ๋ฐ๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง DTO๊ฐ ๋๋ฉ์ธ ์๋น์ค ๋ก์ง๊น์ง ์นจํฌํ ๊ฒฐ๊ณผ, ์ปจํธ๋กค๋ฌ ๋ฟ๋ง ์๋๋ผ ์๋น์ค ๋ฉ์๋๊น์ง ๋ณ๊ฒฝํด์ผํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
//method parameter ๋ณ๊ฒฝ
public void createArticle(String authorName, ArticleCreateRequest req) {
//article ์์ฑ ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ
Article article = new Article(
req.getTitle(), req.getContent(), authorName
);
articleRepository.save(article);
}์๋น์ค ๋ฉ์๋์์ ์ ํ๊ฐ ๋๋์ ๋คํ์ด์ง, ๋ง์ฝ Repository์๋ ๋์ผํ DTO๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ณ๊ฒฝ์ ์ ํ๋ ์์์ฑ ๊ณ์ธต๊น์ง ํผ์ก์ ๊ฒ์ ๋๋ค.
๋๋ฉ์ธ ์๋น์ค๊ฐ ํ์๋ก ํ๋ ๊ฐ ์์ฒด์๋ ๋ณํจ์ด ์์ง๋ง, DTO๋ฅผ ๊ทธ๋๋ก ๋๊ฒจ๋ฐ์๊ธฐ์ ๋ณ๊ฒฝ์ด ํ์ํ์ต๋๋ค. ๊ฒฐ๊ตญ DTO๋ฅผ ๋๊ธฐ๋ ํ์๋ ์๋น์ค ๋ฉ์๋๋ฅผ ๋ณ๊ฒฝํด์ผํ๋ ์ด์ ๋ฅผ ๋๋ฉ์ธ ๋ก์ง์ ๋ณ๊ฒฝ ๋ฟ๋ง ์๋ ์น ๊ณ์ธต์ ๋ณ๊ฒฝ๊น์ง๋ก ํ์ฅํ๋ ๋ฌธ์ ๋ฅผ ๋ณ์ต๋๋ค. 'ํ๋์ ํด๋์ค๋ ๋ณ๊ฒฝํด์ผํ๋ ์ด์ ๊ฐ ๋จ ํ๋์ฌ์ผ ํ๋ค'๋ผ๋ SRP์ ์์น์ ์๋ฐ์ ๋๋ค.
2. ์๋น์ค ๋ฉ์๋์ ์ฌ์ฌ์ฉ์ด ์ด๋ ต๋ค.
ArticleService์ createArticle์ ๋ค๋ฅธ ์๋น์ค์์ ์ฌ์ฉํด์ผํ๋ ์ผ์ด ์๊ฒผ๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค.
@RequiredArgsConstructor
public class AnotherService {
private final ArticleService articleService;
public void someMehtod(...) {
...
ArticleCreateRequest req = new ArticleCreateRequest(...);
articleService.createArticle(req);
}
}์ ์ฝ๋์์ ๋ด์ฉ์ด 50์ ์ด์ 200์ ์ดํ์ฌ์ผ ํ๋ค๋ ์ ๋ ฅ ์ ํจ์ฑ์ ์ง์ผ์ง ์ ์์ต๋๋ค. ์ปจํธ๋กค๋ฌ์์๋ง ๊ฒ์ฆ์ด ์ํ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฐ ๊ฒฝ์ฐ์๋ ๋๊ตฐ๊ฐ๋ ๊ฒ์ฆ์ ์ํํด์ผ ํฉ๋๋ค.
์ฒซ๋ฒ์งธ ๋ฐฉ๋ฒ์ createArticle์ ํธ์ถํ๋ ์ธก์์ ๊ฒ์ฆํ๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ๋๋ค๋ฅธ ๊ณณ์์ createArticle์ด ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ ๊ฒ์ฆ ๋ก์ง์ ์ค๋ณต์ ์ด๋ํ๋ฉฐ, ์ค์๋ก ๊ฒ์ฆ ์์ด ์๋น์ค ๋ฉ์๋๋ฅผ ํธ์ถํ ๊ฐ๋ฅ์ฑ์ ๋ง๋ค๊ฒ ๋ฉ๋๋ค.
์๋ฌด๋๋ ์ ๋ฐฉ๋ฒ์ ์๋น์ค ๋ฉ์๋์ ์ ์ฅ์์ ์ ๋ ฅ ์ ํจ์ฑ์ ์ ๋ขฐํ ์ ์๋ ๋ฐฉ๋ฒ์ธ ๊ฒ ๊ฐ์ต๋๋ค. ๊ทธ๋ผ ์๋น์ค ๋ฉ์๋๊ฐ ์ง์ ๊ฒ์ฆ์ ์ํํ๋ฉด ํด๊ฒฐ๋ ๊น์? ์ด ๊ฒฝ์ฐ์๋ ์์ํ ๋น์ฆ๋์ค ๊ท์น๋ง์ ์ง์ผ์ผ ํ ๋๋ฉ์ธ ์๋น์ค ๋ก์ง์ด ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฆ ์ฝ๋๋ก ์ค์ผ๋์ด ์ฑ ์์ด ๊ณผ๋คํด์ง๋ค๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. (๋ด์ฉ์ ๊ธธ์ด๋ฅผ ๊ฒ์ฆํ๋ ๊ฒ์ด ์ ๋ ฅ ์ ํจ์ฑ์ธ์ง ๋น์ฆ๋์ค ๊ท์น์ธ์ง๋ ๋ ผ์ ๋์์ด์ง๋ง, ์ด๋ ๋์ค์ ๋ค๋ฅธ ํ ํฝ์ผ๋ก ๋ค๋ฃจ๋๋ก ํ๊ฒ ์ต๋๋ค.)
๊ฒฐ๊ตญ ์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก๋ ์๋น์ค ๋ฉ์๋๋ฅผ ์ฌ์ฌ์ฉํ๊ธฐ์๋ ์กฐ๊ธ์ฉ ๊ป๋๋ฌ์ด ์ ์ด ์๊น๋๋ค. ์ด ์ญ์ ์ปจํธ๋กค๋ฌ ๋ ๋ฒจ์ DTO๋ฅผ ์๋น์ค ๋ฉ์๋์์ ์ฌ์ฉํ๊ธฐ์ ์๊ธฐ๋ ๋ฌธ์ ๋ก ๋ณผ ์ ์์ต๋๋ค.
ํด๊ฒฐ์ฑ
DTO ๋ฅผ ๋๊ธฐ์ง ์๊ธฐ
์ฒซ๋ฒ์งธ ๋ฌธ์ ์๋ ์น ๊ณ์ธต์ ๋ณ๊ฒฝ์ด ๋๋ฉ์ธ ๋ก์ง์ ์ํฅ์ ์ฃผ์ง ์๊ฒ ํ๋ ๋ฐฉ๋ฒ์, ๊ทธ์ DTO๋ฅผ ๋ฐ์ง ์๋ ๋ฐฉ๋ฒ์ ๋๋ค.
public class ArticleService {
public void createArticle(String title, String content, String authorName) {..}
}๋ง์ฝ createArticle ๋ฉ์๋๊ฐ DTO๊ฐ ์๋ ๊ฐ๊ฐ์ ๊ฐ๋ค์ ๋ช ํํ ์ ๋ฌ๋ฐ์๋ค๋ฉด, ์ปจํธ๋กค๋ฌ๊ฐ ๊ฐ์ ์ด๋์ ์ด๋ป๊ฒ ๊ฐ์ ธ์ค๋ ๊ทธ๊ฒ์ ๋ ์ด์ ์๋น์ค ๋ฉ์๋์ ๊ด์ฌ์ฌ๊ฐ ์๋๋๋ค. ๊ทธ์ ํ์ํ ๊ฐ์ ๋ฐ๊ณ ์ฌ์ฉํ ๋ฟ์ ๋๋ค. ๋ง์ฝ ๋ฉ์๋๊ฐ ์์ ๊ฐ์ด ์์ฑ๋์๋ค๋ฉด ์ฒซ๋ฒ์งธ ๋ฌธ์ ๋ ๋ฐ์ํ์ง ์์์ ๊ฒ์ด๊ณ , ๋ณ๊ฒฝ์ ์ค์ง ์ปจํธ๋กค๋ฌ์์๋ง ๋ฐ์ํ์ ๊ฒ์ ๋๋ค.
ํ์ง๋ง ์ด ํด๊ฒฐ์ฑ ์ ๋๋ฒ์งธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ง ์์ต๋๋ค. ์ฌ์ ํ ๊ฒ์ฆ์ ๋ํ ์ฑ ์์ ๋ถ์ฌํ๊ธฐ๊ฐ ์ ๋งคํ ์ํฉ์ ๋๋ค.
๋ณ๋์ DTO ์ฌ์ฉํ๊ธฐ
๋๋ฒ์งธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ผ๋ก๋ ์๋น์ค ๊ณ์ธต์ ๋ณ๋์ DTO๋ฅผ ์ ์ํ๋ ๊ฒ์ ๋๋ค. ์๋น์ค ๊ณ์ธต์ DTO๋ ์๋น์ค ๋ฉ์๋๊ฐ ํ์๋ก ํ๋ ์ ๋ ฅ ๋ชจ๋ธ์ ์ ๊ณตํ๋ฉฐ, ์น ๊ณ์ธต์ DTO์ ๋ถ๋ฆฌ๋ ๋ณ๋์ ํด๋์ค๋ก ์์ฑ๋์ด์ผ ํฉ๋๋ค
@Getter
public class ArticleCreateCommand {
public ArticleCreateCommand(String title, String content, String authorName) {
if (content.size() < 50 && content.size() > 200) {
throw ValidationError("Content size not valid");
}
this.title = title;
this.content = content;
this.authorName = authorName;
}
private final String title;
private final String content;
private final String authorName;
}ArticleCreateCommand๋ Article์ ์์ฑํ๋๋ฐ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๋ด๊ณ ์์ต๋๋ค. ๋ํ ์์ฑ์์์ ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ํํ์ฌ ์์ฑ๋ ์ดํ์๋ ๊ฐ์ ๋ณ๊ฒฝ์ด ์ ํ๋๊ธฐ ๋๋ฌธ์, ์์คํ ๋ด์ ์ ํจํ์ง ์์ ArticleCreateCommand ๊ฐ์ฒด๋ ์กด์ฌํ ์ ์์ต๋๋ค. ์ด ๊ฐ์ฒด๋ฅผ createArticle์ด ๋ฐ๋๋ก ๋ง๋ค๋ฉด ๋ฉ๋๋ค.
public class ArticleService {
public void createArticle(ArticleCreateCommand command) {
Article article = new Article(
command.getTitle(),
command.getContent(),
command.getAuthorName()
);
// save
}
}์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ ๋์๋ ์ ๋ ฅ ๋ชจ๋ธ์๋ง ๋ง์ถ์ด ์๋น์ค ๋ฉ์๋๋ฅผ ํธ์ถํด์ค๋ค๋ฉด ์น ๊ณ์ธต์ด ๋ณ๊ฒฝ๋๋๋ผ๋ ์๋น์ค ๋ฉ์๋๋ ๋ณ๊ฒฝ๋ ์ด์ ๊ฐ ์์ต๋๋ค. ์ฒซ๋ฒ์งธ ๋ฌธ์ ๋ ํด๊ฒฐ๋์์ต๋๋ค.
์ด ๋ฐฉ๋ฒ์ ๋ ๋ฒ์งธ ๋ฌธ์ ์ญ์ ํด๊ฒฐํฉ๋๋ค. ๋ค๋ฅธ ์๋น์ค์์ createArticle์ ํธ์ถํ ๋๋, ํด๋น ์๋น์ค ๋ฉ์๋์ ๋ณ๋์ ๊ฒ์ฆ ๋ก์ง์ ์์ฑํ ํ์๊ฐ ์์ต๋๋ค. createArticle์ ์ปค๋งจ๋ ๊ฐ์ฒด๋ฅผ ์ ๋ขฐํ ์ ์์ผ๋ฉฐ ๋น์ฆ๋์ค ๊ท์น์๋ง ์ง์คํ ์ ์์ต๋๋ค. ๊ฒ์ฆ ๋ก์ง์ ์ค๋ณต๋์ง ์์ ๊ฒ์ด๋ฉฐ, ๊ฒ์ฆ ๋์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณ ์๋ ํด๋์ค์ ํจ๊ป ์กด์ฌํ๊ธฐ์ ๋ฐ๋์งํ๊ฒ ์ฑ ์์ด ๋ถ๋ฐฐ๋์์ต๋๋ค.
๋ฌผ๋ก ๋จ์ ๋ ์์ด์
์ด ๋ฐฉ๋ฒ์ ๊ฐ๊ฐ์ ๋ ์ด์ด๋ง๋ค DTO๋ฅผ ๋๊ธฐ ๋๋ฌธ์ ์์คํ ๋ด์์ ๊ด๋ฆฌํด์ผํ๋ ํด๋์ค์ ์๊ฐ ๋ถ์ฉ ๋์ด๋๊ฒ ๋ฉ๋๋ค. ์ง๊ธ ์์์๋ createArticle์ด๋ผ๋ ๋ฉ์๋ ํ๋๋ง ๋ค๋ฃจ์์ง๋ง, ํด๋์ค์ ์๋ ์๋น์ค ๋ฉ์๋์ ๊ฐ์๋งํผ ๋ฐ์ํ ๊ฒ์ ๋๋ค. ์ด๋ ๋ณต์ก๋๋ฅผ ๋์ผ ์๋ ์์ต๋๋ค๋ง, ๊ณ์ธต์ ๋ถ๋ฆฌ์ ์ํคํ ์ฒ๋ฅผ ์ํด์๋ ๊ฐ์ํ ์ ์๋ ์ผ์ด๋ผ๋ ์๊ฐ์ด ๋ฆ๋๋ค.
๋ํ ์ด์ ๋ ์ปจํธ๋กค๋ฌ์์๋ ์น DTO์ ์๋น์ค DTO๊ฐ ๋ณํ์ ํด์ฃผ๋ ๋ก์ง์ด ํ์ํ๊ธฐ ๋๋ฌธ์, ์ด์ฉ๋ฉด ํ ์ค๋ก ๋๋ ์ ์์๋ ์ปจํธ๋กค๋ฌ์ ์ฝ๋๊ฐ ๋์ด๋๊ฒ ๋๋ ๋ฌธ์ ๊ฐ ์๊น๋๋ค.
๋ง๋ฌด๋ฆฌ
3๋ ์ ์ ๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ ๊ฐ์๋ฅผ ๋ค์์ ๋์ ๊ต์๋์ ๋ง์์ด ๋ ์ค๋ฆ ๋๋ค.
์ค๊ณ๋ผ๋ ๊ฒ์ ๋ณต์กํจ์ ํตํด์ ์ ์ฐํจ์ ์ถ๊ตฌํ๋ ํ์์ด๋ค.
๋ณต์กํ ์ฝ๋๋ ์ ์ฐํ๊ณ , ๋จ์ํ๊ณ ๋ณด๊ธฐ์ข์ ์ฝ๋๋ ๋ณ๊ฒฝ์ด ์ด๋ ต๊ธฐ์ ์ฝ๋์ ์ ๋ต์ ์๋ค๋ ์๊ฐ์ด ๋ญ๋๋ค. ์๋น์ค ๋ ์ด์ด์ DTO๋ฅผ ๋๋ ๊ฒ ์ญ์ ์ ๋ต์ ์๋๋๋ค. ๊ทธ๋ผ์๋ ์ด๋ฌํ ๋ ผ์ ํน์ ๋ฌธ์ ์ ์ ๋ชจ๋ฅด๋ ์ํ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ๊ณผ, ์๊ณ ์์์๋ ๋์ผํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋ถ๋ช ํ ์ฐจ์ด๊ฐ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์๋ฌด์ชผ๋ก ๋์์ด ๋์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค.