1. 서론 - DTO와 Entity 데이터 교환이 비즈니스 로직의 절반?
FAQ 고객 지원 관련 비즈니스 로직을 설계하면서, Service 로직에 아래와 같은 구성을 적용했습니다. DTO를 사용함으로써 Entity 간 데이터를 교환하는 계층이 필요해졌고, 결과적으로 비즈니스 로직에 상당한 코드 라인이 추가되었습니다. '우아한 테크코스' 프리코스를 거치며 이러한 부분에서 불편함을 느끼거나 새로운 궁금증이 생겼습니다. "어떻게 하면 DTO와 Entity 간의 변환을 효율적으로 진행할 수 있을까?"라는 질문에 대한 해결책을 찾고자 고민하게 되었습니다.
public class FAQService {
...
// DTO -> Entity 변환
private FAQ convertToEntity(FAQDTO faqDTO) {
FAQ faq = new FAQ();
faq.setId(faqDTO.getId());
faq.setQuestion(faqDTO.getQuestion());
faq.setAnswer(faqDTO.getAnswer());
return faq;
}
// Entity -> DTO 변환
private FAQDTO convertToDTO(FAQ faq) {
FAQDTO faqDTO = new FAQDTO();
faqDTO.setId(faq.getId());
faqDTO.setQuestion(faq.getQuestion());
faqDTO.setAnswer(faq.getAnswer());
return faqDTO;
}
}
2. 본론 - toEntity(), fromEntity() 사용
비즈니스 로직을 설계할 때, DTO(Response, Request)와 Entity 간의 데이터 교환이 자주 발생합니다. 매번 '. get~~', '. set~~'을 사용하게 되면 비즈니스 로직의 절반이 데이터 교환 코드로 채워질 수 있습니다. 💫
이 문제를 해결하는 여러 방안이 존재하지만, 이 글에서는 프로젝트 진행 중 적용했던 toEntity()와 fromEntity() 메서드를 소개하려고 합니다. 이를 통해 DTO에서 바로 Entity로 변환하거나, 반대로 Entity에서 DTO로 변환할 수 있어 비즈니스 로직과의 분리를 통해 코드가 더 직관적이고 가독성이 향상됩니다. 또한, 별도의 Mapper나 Converter 클래스를 사용하지 않아 간결하고 중복 코드를 방지할 수 있는 장점이 있습니다. 👍
2 - 1. toEntity()
@Entity
@Builder
... 생략
public class FAQ {
... 생략
}
Entity에 @Builder를 적용하면 로직을 더욱 간편하게 구성할 수 있습니다. (필수 사항은 아닙니다 ❌)
public record FAQRequest(
String question,
String answer,
Boolean status
) {
public FAQ toEntity() {
return FAQ.builder()
.question(question)
.answer(answer)
.status(status)
.build();
}
}
// 비즈니스 로직 (Service)
public FAQ createFAQ(FAQRequest request) {
FAQ faq = request.toEntity();
return faqRepository.save(faq);
}
위와 같이 간단하게 비즈니스 로직을 설계함으로써 로직을 최소화할 수 있습니다. 이를 통해 짧은 코드로 가독성을 높일 수 있으며, DTO 정의를 통해 언제든지 재사용 가능한 형태로 효율적으로 활용할 수 있습니다.
2 - 2. fromEntity()
public record FAQResponse(
Long id,
Contents contents
){
public record Contents(
String question,
String answer
) {
}
public static FAQResponse fromEntity(FAQ faq) {
Contents contents = new Contents(
faq.getQuestion(),
faq.getAnswer()
);
return new FAQResponse(
faq.getId(),
contents
);
}
}
여기서 들었던 궁금증은 "왜 fromEntity 메서드를 Entity가 아닌 DTO에서 처리할까?"였습니다. 여러 의견이 있지만, 제가 사용한 가장 큰 이유는 '비즈니스 로직의 분리'였습니다. DTO는 데이터 계층 간의 역할을 담당하고, Domain은 하나의 비즈니스 로직을 책임집니다. 만약 Entity에 계층 간 변환 로직이 포함된다면, 오히려 SRP(Single Responsibility Principle) 원칙을 위배하게 된다고 판단해 DTO에서 처리하는 것이 적합하다고 생각했습니다.
record 형태의 내부 클래스(inner class)로 정의했을 때도 위와 같은 방식으로 로직을 구성할 수 있습니다. 내부 클래스가 없을 경우, 더 간단한 로직으로 구성할 수 있습니다. 본 프로젝트에서 사용한 실제 코드를 예시로 들어 이를 설명해 복잡해 보일 수 있습니다. 🤣
// 비즈니스 로직 (Service)
public List<FAQResponse> getFAQs() {
List<FAQ> faqs = faqRepository.findAll();
return faqs.stream()
.filter(FAQ::isStatus)
.map(FAQResponse::fromEntity) // <- fromEntity 적용 부분
.toList();
}
3. 결론 - 이 방법의 사용이 무조건 맞을까?
이번 프로젝트에 맞춰 로직을 간소화하고 최적화하는 방향을 선택했을 때, toEntity()와 fromEntity() 메서드를 통해 여러 가지 이점을 누릴 수 있었습니다. 하지만, 이 방법이 항상 옳다고 말할 수는 없습니다. 🙅🏻
DTO와 Entity 간 데이터 교환을 위한 방법은 여러 가지가 존재합니다. 현재 DTO 클래스의 역할에 맞게 설정하여, 별도의 Mapper나 Converter 클래스를 생성하지 않았고, 외부 의존성을 사용하지 않은 간단한 로직을 구현한 것입니다. 가장 좋은 방법은 현재 진행 중인 프로젝트에 적합한지, 팀원 간의 일관성을 유지할 수 있는지 등 다양한 요건에 따라 결정하는 것입니다. 현재로서는 toEntity()와 fromEntity() 사용을 통해 큰 이점을 얻고 있어, 이를 지속적으로 사용하며 기존 프로젝트에서 적용하지 못한 부분은 차근차근 리팩토링해 나갈 계획입니다.
조건 검토 리스트 📝
- 변환 로직의 단순함
- 프로젝트의 규모
- DTO와 Entity 간의 매핑이 고정적, 일관적
'📒 Java & Spring > Java' 카테고리의 다른 글
[Java] JUnit 입출력 테스트 코드 작성 방법 (0) | 2024.08.19 |
---|---|
[Java] 증감 연산자(++n, n++) 사용의 주의점 Side Effect (0) | 2024.08.13 |
[Java] static, final, static final 키워드를 왜 사용할까? - 변수 (0) | 2024.07.28 |
[Java] 생성자(Constructor)와 this 키워드의 목적, 특징 (0) | 2024.07.26 |
[Java] 자바 코드의 메모리 영역 JVM, JMM (0) | 2024.07.11 |
Backend 개발자를 꿈꾸는 꿈나무💭 기술 블로그
꾸준함을 목표로 하는 꿈나무 개발자 택이✌️입니다. 궁금하신 점이나 잘못된 정보가 있으면 언제든지 연락주세요. 📩 함께 프로젝트 및 스터디도 언제든지 희망합니다! 📖