본문 바로가기
카테고리 없음

Section3-4

by 티코딩 2023. 2. 4.

ㅇ Service

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

애플리케이션에서 Service 라 하믄, 도메인 업무 영역을 구현하는 비지니스 로직과 관련있음.

비지니스 로직을 처리하는 서비스계층은 도메인 모델을 포함한다.

 

ㅇ Member 클래스, MemberService 클래스 구현!

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

Member클래스의 애너테이션들은 Lombok에서 제공하는 아주 편리한 애너테이션으로 저 애너테이션을 사용하면 보이진 않지만 게터세터,생성자를 추가해준다. 개꿀

 

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

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

    public Member findMember(long memberId) {
        // TODO member 객체는 나중에 DB에서 조회 하는 것으로 변경 필요.
        Member member = 
                new Member(memberId, "hgd@gmail.com", "홍길동", "010-1234-5678");
        return member;
    }

    public List<Member> findMembers() {
        // TODO member 객체는 나중에 DB에서 조회하는 것으로 변경 필요.
        List<Member> members = List.of(
                new Member(1, "hgd@gmail.com", "홍길동", "010-1234-5678"),
                new Member(2, "lml@gmail.com", "이몽룡", "010-1111-2222")
        );
        return members;
    }

    public void deleteMember(long memberId) {
        // TODO should business logic
    }
}

createMember(), updateMember()메서드는 일단은 Member객체를 그대로 반환하지만, 나중에 수정함^^

findMember(),findMembers() 는 Stub데이터를 넘겨줄거다.

 

ㅇ DI를 통한 비지니스 계층과 API 계층 연동(느슨한 결합)

먼저 MemberControllerdptj DI기능을 적용해boja.

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

		// DI기능을 이용함. MemberService의 객체를 생성자 파라미터로 주입받음!
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
		
		...
		...
}

Spring이 친절하게 DI 주입해준다.

MemberController 클래스는 @RestController이 붙어서 Spring Bean임.

Service도 SpringBean이 되기 위해

클래스레벨에 @Service 애너테이션 추가함!!

 

**문제발생**

이렇게 되면 MemberController의 핸들러 메서드가 DTO클래스를 Entity클래스로 변환하는 작업을한다.

-> Mapper클래스로 DTO클래스를 엔티티 클래스로 변환해주면된다.

 

엔티티클래스의 객체를 클라이언트의 응답으로 전송함으로 계층간의 역할 분리가 필요하다.

->클라이언트 응답으로 엔티티 클래스를 전송하지 않고 엔티티클래스의 객체를 DTO클래스의 객체로 다시 바꿔준다.

 

ㅇ Mapper 클래스 구현

@Component  // (1)Spring Bean으로 등록하기 위해 붙힘! 이 Bean은 이제 Controller에서 사용
public class MemberMapper {
		// (2) MemberPostDto를 Member로 변환
    public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
        return new Member(0L,
                memberPostDto.getEmail(), 
                memberPostDto.getName(), 
                memberPostDto.getPhone());
    }

		// (3) MemberPatchDto를 Member로 변환
    public Member memberPatchDtoToMember(MemberPatchDto memberPatchDto) {
        return new Member(memberPatchDto.getMemberId(),
                null, 
                memberPatchDto.getName(), 
                memberPatchDto.getPhone());
    }

    // (4) Member를 MemberResponseDto로 변환
    public MemberResponseDto memberToMemberResponseDto(Member member) {
        return new MemberResponseDto(member.getMemberId(),
                member.getEmail(), 
                member.getName(), 
                member.getPhone());
    }
}

MemberController에서 사용하는 DTO 클래스와 Member간 서로 타입을 변환해주는 매퍼클래스다.

 

ㅇ MemberResponseDto클래스

응답 데이터의 역할을 하는 Dto클래스

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

    private String email;

    private String name;

    private String phone;
}

 

ㅇ Controller 핸들러 메서드에 Mapper 적용하기

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

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

    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
				// (2) 매퍼를 이용해서 MemberPostDto를 Member로 변환
        Member member = mapper.memberPostDtoToMember(memberDto);

        Member response = memberService.createMember(member);

				// (3) 매퍼를 이용해서 Member를 MemberResponseDto로 변환
        return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), 
                HttpStatus.CREATED);
    }

    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(
            @PathVariable("member-id") @Positive long memberId,
            @Valid @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);

				// (4) 매퍼를 이용해서 MemberPatchDto를 Member로 변환
        Member response = 
              memberService.updateMember(mapper.memberPatchDtoToMember(memberPatchDto));

        // (5) 매퍼를 이용해서 Member를 MemberResponseDto로 변환
        return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), 
                HttpStatus.OK);
    }

    @GetMapping("/{member-id}")
    public ResponseEntity getMember(
            @PathVariable("member-id") @Positive long memberId) {
        Member response = memberService.findMember(memberId);

				// (6) 매퍼를 이용해서 Member를 MemberResponseDto로 변환
        return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), 
                HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity getMembers() {
        List<Member> members = memberService.findMembers();

				// (7) 매퍼를 이용해서 List<Member>를 MemberResponseDto로 변환
        List<MemberResponseDto> response =
                members.stream()
                        .map(member -> mapper.memberToMemberResponseDto(member))
                        .collect(Collectors.toList());

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

    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(
            @PathVariable("member-id") @Positive long memberId) {
        System.out.println("# delete member");
        memberService.deleteMember(memberId);

        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
}

 

 ㅇ MapStruct를 이용해 Mapper 자동생성하기

MapStruct는 자동으로 mapper클래스를 자동으로 구현해준다. 

dependencies에

implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'

추가해준뒤

 

@Mapper(componentModel = "spring")  //MapStruct의 인터페이스로 정의됨.
//componentModel = "spring"은 Spring의 Bean으로 등록
public interface MemberMapper {
    Member memberPostDtoToMember(MemberPostDto memberPostDto);
    Member memberPatchDtoToMember(MemberPatchDto memberPatchDto);
    MemberResponseDto memberToMemberResponseDto(Member member);
}

gradle의 build task하면 구현해준 클래스를 볼 수 있다.

 

ㅇ Controller에 MapStruct 적용

import com.codestates.member.mapstruct.mapper.MemberMapper; // 추가해줌

@RestController
@RequestMapping
@Validated
public class MemberController {
    ...
		...
		...
}

 

오늘은 여기까지

다음편은 예외처리를 araboza.

5편에 계속!