1. 기본적인 스프링의 구조
앞에서 Controller와 Service, Repository, Domain 에 대해 정리했었다.
이제 위 그림을 보면 사이사이 DTO라는 단어가 보인다.
DTO는 위 그림과 같이 구조 사이 사이 데이터를 전달할 때 사용하며 계층을 나누어 관리한다.
2. DTO 란?
DTO(Data Transfer Object) 단어 그대로 데이터 전달 객체라는 뜻이며, 데이터를 전달하기 위한 객체이다.
보통 DTO는 두가지로 나뉜다.
Client가 요청을 보낼 때 사용하는 RequestDto, 다시 Client에게 응답을 해주는 ResponseDto이다.
DTO는 오직 데이터를 전달하기 위한 객체로 getter와 setter만을 가진다.
하지만 setter는 데이터의 변경을 위해 존재하기 때문에 데이터 전달이 목표인 DTO는 주로 getter만 사용한다.
3. 왜 사용할까?
1. 보안문제
만약 Controller에서 Domain인 Member 객체를 넘겨주게 되면 어떤 문제가 발생할 수 있을까?
만약 Member에서 노출이 되면 안되는 중요한 정보가 있는데 이를 다 넘겨주게 된다면 보안 문제가 발생할 수 있다.
쉽게 말해, 딱 전해주고 싶은 데이터만 넘겨주기 위해 사용하는 것이다.
2. 과한 의존 방지
추가로, Domain의 모든 속성이 중요하지 않다고 하더라도 Dto를 사용하는게 좋다.
Controller와 Service 사이의 강한 의존을 방지하는 것이 좋기 때문이다.
Controller에서 Service를 호출하고 이를 이용해 핵심 로직을 구현한다. 하지만 이를 바로 연결해버린다면 Service가 받고 싶어하는 파라미터들을 Controller에서 처리해 보내주게 된다. 이렇게 된다면 Service는 Controller에 너무 종속적이게 되고 Controller 상의 코드가 지저분해 질 수 있다.
다시 말해, 필요한 파라미터들을 Contoller 상에서 처리하기보단 Dto를 이용하는 것이 좋다.
3. 유연한 코드
보통 프로젝트를 진행하게 된다면 필요한 파라미터들이 바뀌는 경우가 많다.
만약 Dto를 사용하지 않는다면 많은 파라미터들과 Controller의 코드를 바꾸어야 하지만.
Dto를 사용해 분리해서 관리한다면 더욱 쉽게 변경할 수 있다.
예를 들어보자,
Domain
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Service
public class MemberService {
private final MemberRepository memberRepository = new
MemoryMemberRepository();
/**
* 회원가입
*/
public Long join(Member member) {
validateDuplicateMember(member); //중복 회원 검증
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
memberRepository.findByName(member.getName())
.ifPresent(m -> {
throw new IllegalStateException("이미 존재하는 회원입니다.");
});
}
/**
* 전체 회원 조회
*/
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Optional<Member> findOne(MemberRequestDto memberrequestDto) {
return memberRepository.findByName(memberrequestDto.getName());
}
}
Controller
@Controller
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@GetMapping(value = "/members/new")
public String createForm() {
return "members/createMemberForm";
}
@PostMapping(value = "/members/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
//---------------------DTO 이용 추가------------------------
@PostMapping("/member/new")
public String save(@RequestBody MemberRequestDto memberrequestDto) {
return memberService.findOne(memberrequestDto);
}
@GetMapping(value = "/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
}
위 코드를 보면 Dto를 이용하는 코드가 추가되어있다. Dto를 객체로 사용해 이를 이용해 회원을 추가하도록 하는 것이다.
Dto
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class MemberRequestDto {
private String name;
}
위와 같은 코드가 있다고 하자
위 Dto 코드에서는 Domain과 달리 name 변수만 가지는 것을 볼 수 있다.
이는 Domamin모델인 Member 객체에서 id 데이터는 노출시키지 않고 name의 대한 정보만 전달하고 싶다는 이야기이다.
Dto Class를 Controller의 파라미터로 사용해 이름만 들어있는 데이터를 전달한다.
이렇게 한다면 Member의 id에 대한 노출 문제가 줄어든다.
만약 name이 아니라 id만 사용하도록 바뀐다면 RequestDto의 구성요소를 id로 바꾸면 된다.
코드가 복잡해 질수록 코드에 대한 유연성이 증가한다.
물론 Dto에서 name을 가져오는 것은 Controller가 아니라 Service에서도 할 수 있다.
어떤 방법이 더 맞는 방법인지는 알아봐야할 것 같다.
'개념 정리 > Spring' 카테고리의 다른 글
[Spring] ShedLock을 통해 서버간 스케줄 중복 수행 방지 (0) | 2023.07.29 |
---|---|
[Spring] @Scheduled 이용해 특정 시간마다 함수 호출하기 (0) | 2023.07.24 |
[Spring Security] 기본 로그인 화면 제거 (0) | 2023.07.23 |
[Spring] UUID와 increment PK를 언제 사용할까? (0) | 2023.07.19 |
[Spring] 스프링 Controller / Service / Repository / Domain (0) | 2023.07.02 |