본문 바로가기
Back-End/Spring MVC

서비스 계층에서의 DI

by 달의 조각 2022. 8. 23.

 

DI를 통한 서비스 계층과 API 계층의 연동

 

 

API 계층에서 구현한 Controller 클래스와 서비스 계층의 Service 클래스의 상호작용

Service: 도메인 업무 영역을 구현하는 비즈니스 로직과 관련을 가진다.
도메인 모델: 빈약한 도메인 모델과 풍부한 도메인 모델로 구분하며 DDD(도메인 주도 설계)와 관련이 깊다.

 

 

 

매퍼를 이용한 DTO 클래스와 엔티티 클래스 매핑

 

 

📄 Member 도메인 엔티티

DTO의 역할: 클라이언트의 요청 데이터 ↔ Controller의 핸들러 메서드 (@ReqeustBody)
Member 클래스의 역할: API 계층 ↔ 서비스 계층

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private long memberId;
    private String email;
    private String name;
    private String phone;
}

 

📄 MemberController

@RestController
@RequestMapping("/v2/members")
@Validated
public class MemberController {
    private final MemberService memberService;

    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {

        Member member = new Member();
        member.setEmail(memberDto.getEmail());
        member.setName(memberDto.getName());
        member.setPhone(memberDto.getPhone());

        Member response = memberService.createMember(member);

        return new ResponseEntity<>(response, HttpStatus.CREATED);
    }

    // . . .
}

 

📄 MemberService

Cotroller의 핸들러 메서드와 Service의 메서드가 1대1로 매치된다.

public class MemberService {
    public Member createMember(Member member) {
        // TODO should business logic

        // TODO member 객체는 나중에 DB에 저장 후, 되돌려 받는 것으로 변경 필요.
        Member createdMember = member;
        return createdMember;
    }

    public Member updateMember(Member member) {
        // TODO should business logic

        // member 객체는 나중에 DB에 업데이트 후, 되돌려 받는 것으로 변경 필요.
        Member updatedMember = member;
        return updatedMember;
    }
    
    // . . .
}

 

💡 문제점

1

Controller의 핸들러 메서드는 클라이언트의 요청 데이터를 Service에 전달하고 응답 데이터를 클라이언트에 전달하는 단순한 역할만 해야 한다!

위의 코드를 보면 DTO 클래스를 엔티티 객체로 변환하는 작업까지 하고 있다.

2

엔티티 객체를 클라이언트 응답으로 전송하고 있다.
엔티티 클래스는 서비스 계층에서만 데이터 처리 역할을 해야 한다.

 

 

Mapper 클래스 구현

 

 

DTO 클래스와 엔티티 클래스를 변환하는 작업을 핸들러 메서드가 아닌 다른 클래스에 위임한다.

📄 MemberMapper

@Component
public class MemberMapper {

    public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
        return new Member(0L,
                memberPostDto.getEmail(), 
                memberPostDto.getName(), 
                memberPostDto.getPhone());
    }
    
    // . . .
    
    public MemberResponseDto memberToMemberResponseDto(Member member) {
        return new MemberResponseDto(member.getMemberId(),
                member.getEmail(), 
                member.getName(), 
                member.getPhone());
    }
}

 

📄 MemberResponseDto

@Getter
@AllArgsConstructor
public class MemberResponseDto {
    private long memberId;

    private String email;

    private String name;

    private String phone;
}

위와 같이 직접 만들 수도 있다.

 

MapStruct를 이용한 Mapper 자동 생성

dependencies {
	...
	...
	implementation 'org.mapstruct:mapstruct:1.4.2.Final'
	annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}

 

📄 MemberMapper

MapStruct가 자동으로 구현 클래스를 만들어 준다.

@Mapper(componentModel = "spring") //스프링 빈으로 등록해 준다
public interface MemberMapper {
    Member memberPostDtoToMember(MemberPostDto memberPostDto);
    Member memberPatchDtoToMember(MemberPatchDto memberPatchDto);
    MemberResponseDto memberToMemberResponseDto(Member member);
}

MemberMapperImpl 클래스는 언제, 어떻게 생성될까요? IntelliJ IDE의 오른쪽 상단의 [Gradle] 탭을 클릭한 후, [프로젝트 명 > Tasks 디렉토리 > build 디렉토리 > build task]를 더블 클릭하면 MapStruct로 정의된 인터페이스의 구현 클래스가 생성됩니다.

MemberMapperImpl 클래스는 어디에 생성될까요? IntelliJ IDE의 좌측에서 [Project 탭 > 프로젝트명 > build] 디렉토리내의 MemberMapper 인터페이스가 위치한 패키지 안에 생성됩니다.

 

📄 MemberController 적용

@RestController
@RequestMapping("/v5/members")
@Validated
public class MemberController {
    private final MemberService memberService;
    private final MemberMapper mapper;

    public MemberController(MemberService memberService, MemberMapper mapper) {
        this.memberService = memberService;
        this.mapper = mapper;
    }

    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {

        Member member = mapper.memberPostDtoToMember(memberDto);
        Member response = memberService.createMember(member);

        return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), 
                HttpStatus.CREATED);
    }
    
    // . . .
}

댓글