백엔드/코드스테이츠 수강

코드스테이츠 수강_11주차_1~2일차_Spring MVC 서비스 계층, 예외처리

반 불혹 2022. 10. 25. 21:05

코드스테이츠 수강 11주차 1일차에는 Spring MVC 서비스 계층에 대해 배웟다. 

 

서비스란? 

애플리캐이션에서 서비스란 도메인 업무 영역을 구현하는 비즈니스 로직과 관련이 있다. 

비즈니스 로직을 처리하는 부분 -> 서비스 계층이라는 말인데, 서비스 계층은 대부분 도메인모델을 포함하고 있다. 

도메인 모델은 다시 빈약한 도메인 모델, 풍부한 도메인 모델로 구분되는데, 이는 차차 배우면서 알게 된다고 한다... 

그냥 "서비스" 하면 비즈니스 로직을 처리하는 서비스 클래스 라고 생각하자. 

일단 우리가 사용할 "모델" 인 멤버 클래스 

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

모댈은 전에도 언급했듯이 그냥 우리가 로직 굴릴때 사용하는 데이터라고 보면 된다.

회원의 이름, 이메일, 아이디 등등... 저장해서 이 모델을 컨트롤러에서 다루는 것이다.

위에서 주의 깊게 봐야하는 에너테이션이 있다. 

  1. @Getter/ Setter : 이 에너테이션은 lombok이라는 라이브러리에서 제공하는 에너테이션으로, 위의 맴버 변수에 해당하는 getter/ setter를 일일히 작성할 필요 없이 알아서 다 만들어준다.
    (일일히 메서드 만들어서 return 변수 ; 이렇게 생성 시켜주지 않아도 된다는 것이다.)

  2. @AllArgsConstructor : 이 에너테이션은 해당 클래스에 추가된 모든 멤버 변수를 파라미터로 갖는 생성자를 자동으로 생성 해 주는 에너테이션이다.
    (이것도 일일히 생성자 안만들어 줘도 된다 이거다. / this.변수 = 변수 일일히 안써줘도 알아서 다 만들어줌)

  3. @NoArgsConstructor : 이 에너테이션은 파라미터가 없는 기본 생성자를 자동으로 생성시켜준다 

위의 에너테이션들을 요약하면, 그냥 모델이 되는 클래스의 변수 등을 알아서 스프링이 객체로 만들게 만들어 주는에너테이션들이다. 

 

코드 예시

public class MemberService {
    public Member createMember(Member member) {
        // 이 코드는 그냥 들어온 값을 다시 반환해주는로직을 가진 서비스 이다.
        Member createdMember = member;
        return createdMember;
    }

 

서비스 계층도, 우리가 만들었던 컨트롤러, 모델 등 처럼 따로 만들어주는 것이다. 

그런데, 이게 객체로 만들어지니까, 컨트롤러에 연동 할 때 DI를 활용해서 넣으면... 

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

		//DI를 활용해서 객체를 주입 한 예시
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }
		
		...
		...
}

위와 같이 간략하게 만들 수 있다. 

public class MemberController {
    private final MemberService memberService;

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

    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
        // 들어오는 JSON타입의 모델들을 자바 객체로 만들어 준다
        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);
    }

만약 ID로 직접 객체를 주입 하지 않으면, 위 코드처럼 일일히 사용하는 모델 (Member)의 객체를 모두 만들어 줘야 한다. 

 

여기서 Member 클래스 처럼 서비스 계층에서 데이터 엑세스 계층(컨트롤러)과 연동하면서 비즈니스 로직을 처리하기 위한 데이터를 담는 역할을 하는 클래스를 도메인 엔티티(Entity) 클래스 라고 한다

위에서는 이 엔티티 클래스의 객체의 Member 클래스의 객체를 직접 컨트롤러에서 만들어 주었다. 
(JSON으로 들어온거 자바 객체로 바꿔주는거 @RequestBody 사용)

그런데 이러면 클라이언트와의 계층 분리도 안되고, 컨트롤러에 너무 많은 기능을 넣어서 아주 일이 복잡해진다. 

그래서 매퍼(Mapper) 가 있다.

매퍼는 DTO클래스와 엔티티 클래스를 서로 변환해주는 클래스이다.

뭔말인지 모르겠다고?

그냥 유효성검증 등 이것저것 처리된 거는 DTO 통해서 나온거고, 처리 안되서 그냥 날것 드래로의 데이터는 엔티티 클래스라고 생각하자. 

이거를 JSON 직렬화 역직렬화처럼 바꿔주는 것을 매퍼(Mapper)클래스 에서 하는 것이다.

코드 예시

@Component  // 스프링 빈으로 만들어서 컨테이너에 넣는다는 에너테이션
public class MemberMapper {
		//  MemberPostDto를 Member로 변환
    public Member memberPostDtoToMember(MemberPostDto memberPostDto) {
        return new Member(0L,
                memberPostDto.getEmail(), 
                memberPostDto.getName(), 
                memberPostDto.getPhone());
    }

위의 코드를 보면 Mapper가 DTO 클래스를 그냥 Member로 변환 된 것을 볼 수 있다.

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);
    }

위 코드는 컨트롤러 코드에 적용 시 이캐캐 된다는 것을 보여주는 예시

위의 매퍼도 이것저것 늘어나면 또 줄줄줄 써야되는거 아니냐? 할 수 있다.

맞다 그래서 또 이와 관련한 에너테이션이 존재한다.

바로 @Mapper 에너테이션이다. 

코드 예시

@Mapper(componentModel = "spring")  // (1)
public interface MemberMapper {
    Member memberPostDtoToMember(MemberPostDto memberPostDto);
    Member memberPatchDtoToMember(MemberPatchDto memberPatchDto);
    MemberResponseDto memberToMemberResponseDto(Member member);
}

이렇게 @Mapper  에너테이션을 사용하면 MemberMapper에서 일일히 선언 할 필요가 없다.

@Mapper 를 사용해서 componentModel = "spring" 으로 지정해주면 스프링의 빈으로 등록된다.

@Mapper 에너테이션으로 자동으로 생성된 코드는 다음과 같다.

@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;
        }
    }

그리고 이렇게 자동으로 생성된 매퍼를 컨트롤러에 추가하는것은 굉장히 간단히 만들 수 있다. 

import com.codestates.member.mapstruct.mapper.MemberMapper; // 우리가 만든 매퍼로 패키지 변경

/**
 * - DI 적용
 * - Mapstruct Mapper 적용
 */
@RestController
@RequestMapping("/v5/members") // 
@Validated
public class MemberController {
    ...
		...
		...
}

매퍼 클래스를 그냥 임포트 시켜주면 된다 .

이 방법으로 3가지 장점이 생긴다.

  1.  계층별 관심사 분리 :관심사가 불리됨으로 하나의 클래스에서 이기능 저기능 다 있는 경우를 방지 

  2. 코드 구성 단순화 : DTO 엔티티 모두 분리되기 때문에 각자 뒤섞일 일이 없어서 단순화 가능 

  3. REST API 스펙의 독립성 확보 : 독립성이 보장되기 때문에 서로 영향을 안준다. 
    (대표적으로 로그인 할때 비밀번호 +  아이디인데, 아이디는 클라이언트에 다시 보여줘도 되지만 비밀번호는 안보여주는게 보안상으로 더 좋다 -> 따로 분리(독립)시켜놔서 안보여줄 수 있다!!)