[codestates] mapper 를 통한 service 와 controller 분리
Mapper 클래스
Mapper 클래스는 DTO 와 객체를 서로 변환해주는 클래스입니다. 해당 클래스는 컨트롤러와 서비스 레이어의 강한 결합을 분리하고 종속적이지 않게 하지 위해 사용합니다. 예를 들어 아래와 같은 컨트롤러가 있다고 보겠습니다.
@PostMapping
public ResponseEntity<Void> createMember(MemberPostDto memberPostDto) {
MemberResponseDto = memberService.create(memberPostDto);
return ResponseEntity.created(URI.create(String.format("/members/%s", responseDto.getId()))).build();
}
memberService 는 memberPostDto 를 그대로 받아서 MemberResponseDto 를 반환하고 있습니다. 위의 경우 문제는 다음과 같습니다.
- Service가 받고 싶은 포맷(Parameter)이 Controller에 종속적이게 됩니다. 즉, Service가 Controller 패키지에 의존하게 됩니다.
- Service 레이어가 모듈로 분리되는 경우 해당 Type을 사용할 수 없게 됩니다.
대신 아래와 같이 변경할 수 있습니다.
@PostMapping
public ResponseEntity<Void> createMember(MemberPostDto memberPostDto) {
MemberResponseDto = memberService.create(memberPostDto.toServiceDto());
return ResponseEntity.created(URI.create(String.format("/members/%s", responseDto.getId()))).build();
}
memberPostDto.toServiceDto()
를 통해 service 가 원하는 형태로 변경해서 인자로 넘깁니다. 이 부분을 ServiceDto 로 변경해도 되고, 엔티티로 변경해도 됩니다. 만약 엔티티로 변경하게 되면 불완전한 엔티티를 Service 파라미터로 받게 되고, Service
메소드별로 원하는 포맷이 달라지는 경우 결국 DTO로 분리될 것이기 때문에 잘 생각해보는 게 좋을 듯합니다.
Custom Mapper 클래스 만들기
DTO 클래스 안에서가 아닌 Mapper 클래스를 직접만들어서 Dto 를 변환한다면 아래와 같이 만들 수 있습니다.
@Component
public class MemberMapper {
//MemberPostDto를 Member로 변환
public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
return new Member(0L,
memberPostDto.getEmail(),
memberPostDto.getName(),
memberPostDto.getPhone());
}
//MemberPatchDto를 Member로 변환
public Member memberPatchDtoToMember(MemberPatchDto memberPatchDto) {
return new Member(memberPatchDto.getMemberId(),
null,
memberPatchDto.getName(),
memberPatchDto.getPhone());
}
//Member를 MemberResponseDto로 변환
public MemberResponseDto memberToMemberResponseDto(Member member) {
return new MemberResponseDto(member.getMemberId(),
member.getEmail(),
member.getName(),
member.getPhone());
}
}
이 때 스프링빈으로 등록해서 객체가 여러 번 생성되는 일을 방지하도록 합니다.
MapStruct를 이용한 Mapper 자동 생성
MapStruct 를 통해서 Mapper 클래스를 깔끔하게 만들 수 있습니다. 먼저 아래와 같이 의존성을 추가해줍니다.
dependencies {
...
...
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
그리고 MemberController에서 사용하는 DTO 클래스(MemberPostDto, MemberPatchDto, MemberResponseDto)와 Member 클래스 간의 변환 기능을 제공하는 매퍼(Mapper) 인터페이스를 정의해야 합니다.
@Mapper(componentModel = "spring")
public interface MemberMapper {
Member memberPostDtoToMember(MemberPostDto memberPostDto);
Member memberPatchDtoToMember(MemberPatchDto memberPatchDto);
MemberResponseDto memberToMemberResponseDto(Member member);
}
이제 MemberMapper 인터페이스의 구현 클래스는 Gradle의 build task를 실행하면 자동으로 생성됩니다. 아래와 같이 구현됩니다.
@Component
public class MemberMapperImpl implements MemberMapper {
public MemberMapperImpl() {
}
public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
if (memberPostDto == null) {
return null;
} else {
Member member = new Member();
member.setEmail(memberPostDto.getEmail());
member.setName(memberPostDto.getName());
member.setPhone(memberPostDto.getPhone());
return member;
}
}
public Member memberPatchDtoToMember(MemberPatchDto memberPatchDto) {
if (memberPatchDto == null) {
return null;
} else {
Member member = new Member();
member.setMemberId(memberPatchDto.getMemberId());
member.setName(memberPatchDto.getName());
member.setPhone(memberPatchDto.getPhone());
return member;
}
}
public MemberResponseDto memberToMemberResponseDto(Member member) {
if (member == null) {
return null;
} else {
long memberId = 0L;
String email = null;
String name = null;
String phone = null;
memberId = member.getMemberId();
email = member.getEmail();
name = member.getName();
phone = member.getPhone();
MemberResponseDto memberResponseDto = new MemberResponseDto(memberId, email, name, phone);
return memberResponseDto;
}
}
}
댓글남기기