MSA로 팀프로젝트를 진행중인데, 여기서 한 가지 의문이 생겼었다.
멀티모듈을 도입하지 않는 이상 각 서비스마다 중복된 DTO 저장은 필연적인데 이걸 서비스 계층에서 조작하는게 맞나?
쉽게 말하면 이런 느낌이다
만약 MemberService에서 특정 로직을 수행한 후 이걸 MemberController에서 반환하는 로직이라고 가정하자.
MemberDto
public class MemberDto {
private Long memberId;
private String memberName;
}
MemberService
// 가상의 로직
MemberDto memberDto = memberService.findById(id);
MemberController
@GetMapping("/detail")
public ResponseEntity<?> logic(@RequestParam Long memberId) {
MemberDto memberDto = memberService.logic(memberId)
return ResponseEntity.ok(memberDto);
}
그리고 다른 Order 마이크로서비스는 이 로직의 결과값이 필요하다
OrderController
@FeignClient(name = "member-service", url = "http://member-service:8080")
public interface MemberClient {
@GetMapping("/api/member/detail")
MemberResponseDto getMemberDetail(@RequestParam("memberId") Long memberId);
}
이때 Order 마이크로서비스는 이 MemberReponseDto를 다음과 같이 만들었을 것이다.
MemberResponseDto
public class MemberDto {
private Long memberId;
private String memberName;
}
이때 멤버 서비스는 로직의 변경으로 인해 MemberDto에 이메일 정보를 추가했다.
public class MemberDto {
private Long memberId;
private String memberName;
private String memberEmail;
}
그럼 어떻게 될까?
이 로직 변경으로 인해 다른 마이크로서비스들은 기존의 DTO에 매핑할 수 없어, 오류가 나거나 email에 대한 정보가 누락될 것이다.
이유는 다른 마이크로서비스는 이렇게 알고있기 때문
public class MemberDto {
private Long memberId;
private String memberName;
}
그래서 생각한 구조는 Service - Controller 계층간 DTO 분리
이다.
먼저 멤버 서비스에서는 로직에 필요한 DTO와 반환에 필요한 DTO를 분리한다.
서비스에서 사용될 DTO
public class MemberDto {
private Long memberId;
private String memberName;
}
컨트롤러에서 사용될 DTO
public class MemberResponse {
private Long memberId;
private String memberName;
}
그리고 이를 ModelMapper
라이브러리를 통해 매핑한다
@GetMapping("/detail")
public ResponseEntity<?> logic(@RequestParam Long memberId) {
MemberDto memberDto = memberService.logic(memberId);
MemberResponse response = modelMapper.map(memberDto, MemberResponse.class);
return ResponseEntity.ok(response);
}
이렇게 사용한다면 장점은 다음과 같음
만약 개발과정에서 DTO를 함부로 막 건든다면 다른 마이크로서비스는 이런 에러를 만날 수 있기 때문
Exception in thread "main" org.springframework.http.converter.HttpMessageNotReadableException:
JSON parse error: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`);
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
그러나 근본적으로 DTO의 신뢰성 문제를 해결해주진 못함
A 서비스가 애초에 반환되는 DTO를 바꿨다면 B 서비스는 그걸 절대 알 수가 없기 때문
이를 해결하기 위한 방법이 바로 "멀티 모듈"
정확히는 내가 프로젝트를 하면서 체감해봐야하기 때문에 구조만을 보여주면 다음과 같다
├── common-dto-module
│ ├── src/main/java/com/example/dto/MemberDto.java
├── service-a
├── service-b
하나의 레포지토리에 여러 마이크로서비스가 존재하는 모노레포 방식의 최적화된 방법이다.