Spring 계층 별 개념 정리 (Service계층)

김용빈·2022년 7월 21일
0

Spring MVC Service계층

계층연동 : API계층과 서비스 계층을 연동한다는 의미는 API계층에서 구현한 Controller클래스가 서비스 계층의 Service클래스와 메서드 호출을 통해 상호작용한다는 것을 의미

// 회원관리 앱의 service메서드의 틀 
import java.util.List;

public class MemberService {
    public Member createMember(Member member) {
        return null;
    }

    public Member updateMember(Member member) {
        return null;
    }

    public Member findMember(long memberId) {
        return null;
    }

    public List<Member> findMembers() {
        return null;
    }

    public void deleteMember(long memberId) {

    }

Member클래스의 역할과 기능

API계층에서는 요청데이트(RequestBody)를 전달받을떄 DTO클래스를 사용했다
즉, DTO가 API계층에서 클라이언트의 RequestBody를 전달받고 클라이언트에게 되돌려줄 응답데이터를 담는 역할을 한다면,

Member클래스는 API계층에서 전달받은 요청데이터를 기반으로 서비스 계층에서 비즈니스로직을 처리하기 위해 필요한 데이터를 전달받은 후 비즈니스 로직을 처리하고, 그 결과 값을 다시 API계층으로 리턴해주는 역할을 수행

Member클래스 처럼 1) 서비스 계층에서 데이터 엑세스 계층과 연동하면서 2) 비즈니스로직을 처리하기 위해 필요한 데이터를 담는 역할을 하는 클래스를 엔티티(Entity)클래스라 부름

//Member 클래스 구현 ( API계층에서 요청받는 Controller클래스에서필드를 그대로 사용)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private long memberId;
    private String email;
    private String name;
    private String phone;
}

Controller클래스에서 Service기능 사용

  • 컨트롤러 클래스 바로 밑에 내가 사용하고 싶은 service클래스를 부른다
private final MemberService memberservice;  ( 변수 선언)
// 생성자 DI를 통해 객체사용을 가능케한다

Public MemberController(MemberService memberservice){
this.memberservice = memberservice ;}
  • 스프링 DI를 통해 Memberservice 객체 주입 받음 !
  • 질문! : 여기서 그러면 누가 이 객체를 주입해줄까 ??
  • Anwer : 바로 Spring 애플리케이션 로드시 ApplicationContext에서 자동주입
    하지만, Spring DI로 어떤 객체를 주입받기 위해서는 ' 주입받는 클래스와 주입 대상 클래스' 클래스 모두 Spring Bean이여야 한다!

그러므로 서비스 클래스에 @Service를 추가해 Spring Bean등록!

@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) {
				// (2)
        Member member = new Member();
        member.setEmail(memberDto.getEmail());
        member.setName(memberDto.getName());
        member.setPhone(memberDto.getPhone());

				// (3)
        Member response = memberService.createMember(member);

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

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

				// (4)
        Member member = new Member();
        member.setMemberId(memberPatchDto.getMemberId());
        member.setName(memberPatchDto.getName());
        member.setPhone(memberPatchDto.getPhone());

				// (5)
        Member response = memberService.updateMember(member);

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

    @GetMapping("/{member-id}")
    public ResponseEntity getMember(
            @PathVariable("member-id") @Positive long memberId) {
				// (6)
        Member response = memberService.findMember(memberId);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

현재 코드의 문제점은 MemberController에서는 핸들러 메서드가 DTO 클래스를 엔티티(Entity) 객체로 변환하는 작업까지 수행
즉, Controller가 너무 많은 역할 담당


매퍼(Mapper)를 이용한 DTO클래스와 엔티티 클래스 변환

매퍼(Mapper)란 : DTO클래스와 Entity클래스 변환을 하는것은 Mapper

@Component  // Spring bean으로 등록하기 위한 Component
public class MemberMapper {
		// MemberPostDto를 Member로 변환하는 코드 
    public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
        return new Member(0L,
                memberPostDto.getEmail(), 
                memberPostDto.getName(), 
                memberPostDto.getPhone());
    }
}

이렇게 해서 Dto클래스를 Entitiy 클래스인 Member로 변환하는 과정

But ! 개발자가 클래스마다 일일이 알맞는 Mapper클래스를 만드는것은 비효율적이다!
하지만 Spring에서는 이 매퍼클래스를 자동으로 만들어주는 MapStruct가 있다!

MapStruct : 매퍼 구현 클래스를 자동으로 생성해주는 '코드 자동 생성기'

//엔티티클래스를 다시 Dto로 보내주는 클래스
@Getter
@AllArgsConstructor
public class MemberResponseDto {
    private long memberId;

    private String email;

    private String name;

    private String phone;
}
// 의존라이브러리에 mapstruct 설정추가 
dependencies {
	...
	...
	implementation 'org.mapstruct:mapstruct:1.4.2.Final'
	annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}

MapStruct 기반의 매퍼(Mapper) 인터페이스 정의

@Mapper(componentModel = "spring")  
public interface MemberMapper {
    Member memberPostDtoToMember(MemberPostDto memberPostDto);
    Member memberPatchDtoToMember(MemberPatchDto memberPatchDto);
    MemberResponseDto memberToMemberResponseDto(Member member);
}
// (MemberpostDto ~ ) -> 데이터 시작점
// Member -> 반환점 
  • @Mapper 애너테이션을 추가함으로써 해당 인터페이스는 MapStruct의 매퍼인터페이스 정의
  • @Mapper 애터테이션의 속성으로 (ComponentModel = "Spring")을 지정해줘야 Spring Bean으로 등록되니 꼭 기억!
  • 구현클래스는 Gradle의 bulid task실행하면 자동생성

컨트롤러 클래스위에 import문으로 mapstruct위치로 알려주면 바로 mapper클래스 사용가능
import com.app.member.mapstruct.mapper.MemberMapper; // 패키지 변경 하면됨

@RestController
@RequestMapping("/application/members")
@Validated
public class MemberController {
    private final MemberService memberService;
    private final MemberMapper mapper;  //매퍼 사용하기 위한 선언

		//  MemberMapper DI
    public MemberController(MemberService memberService, MemberMapper mapper) {
        this.memberService = memberService;
        this.mapper = mapper;       // 매퍼사용하기 위한 DI 
    }

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

        Member response = memberService.createMember(member);

				//  매퍼를 이용해서 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);

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

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

Service에서 사용되는 애너테이션

@AllargsConstructor : 클래스에 추가된 모든멤버변수를 파라미터로 갖는 Member생성자 자동 생성
@NoargsConstructor : 파라미터 없는 기본생성자 자동 생성
@Service : 컨트롤러 클래스에서 서비스 클래스 사용하기 위해서는 생성자 주입을통해 사용이 가능케 하는데 스프링이 객체를 주입해주기 위해서는 BEAN등록이 필수!
Bean 등록을 하기 위해서는 서비스 클래스에 @Service 애너테이션을 추가

profile
안녕하세요!

0개의 댓글