Service - 책 등록하기

jihan kong·2022년 9월 8일
0

JUnit5

목록 보기
11/25
post-thumbnail

본 시리즈는 메타 코딩님의 Junit 강의를 학습한 내용을 바탕으로 정리하였습니다.

지난 포스팅을 끝으로 BookRepository의 Test 코드 개발을 모두 끝냈다. 이제 실제로 Service layer를 통해 비즈니스 로직에 관해 설계해보자.


💡 서비스 레이어에 대해 읽어보면 좋은 글
https://wckhg89.tistory.com/13


우리가 프로젝트의 뼈대를 구축할 때 만들었던 service 패키지와 BookService 자바 파일을 건드려보자.

BookService.java

package site.metacoding.junitproject.service;

@Service
public class BookService {

	// 1. 책 등록
    
    // 2. 책 목록보기
    
    // 3. 책 한건보기
    
    // 4. 책 삭제
    
    // 5. 책 수정

}

BookServiceBookRepositoryTest 와 마찬가지로 5개의 기능을 가지고 개발할 계획이다. 이 중에서 책 등록에 관한 부분을 먼저 시작할 것이다.

BookSaveReqDto

일단 책을 등록하기 위해서는 Dto가 있어야한다. Book Entity의 필드 값을 DTO 즉, 데이터를 오브젝트로 변환해서 책 등록하기 메서드에 전달할 것이기 때문이다.
이 역시 프로젝트를 구축할 때, 파일을 만들어두었다. (Web > Dto 하위에 BookSaveReqDto) 이제 이것을 구현하자.


BookSaveReqDto

package site.metacoding.junitproject.web.dto;

import lombok.Getter;
import lombok.Setter;
import site.metacoding.junitproject.domain.Book;

@Getter
@Setter  // Controller에서 Setter가 호출되면서 Dto에 값이 채워짐.
public class BookSaveReqDto {
    private String title;
    private String author;
    
    public Book toEntity() {
        return Book.builder()
                .title(title)
                .author(author)
                .build();
    }
}

이제 DTO도 완성되었으니 Service layer로 넘어가자.


책 등록 (미완)

BookService.java

package site.metacoding.junitproject.service;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import site.metacoding.junitproject.domain.Book;
import site.metacoding.junitproject.domain.BookRepository;
import site.metacoding.junitproject.web.dto.BookRespDto;
import site.metacoding.junitproject.web.dto.BookSaveReqDto;

@RequiredArgsConstructor              //  1.
@Service
public class BookService {

    private final BookRepository bookRepository;
    
    public Book 책등록하기(BookSaveReqDto dto) {       //  2.
        Book bookPS = bookRepository.save(dto.toEntity());
			return bookPS;
        }
    }
  1. 책 등록을 위해선 bookRepository 가 필요하다. 그러나 이를 final 로 선언하여 생성자를 생성하게 되면 오류가 발생하는데 이는 우리가 bookRepository 을 아직 구현하지 않은 상태이기 때문이다.
    이 때 final 이 붙은 필드의 생성자를 자동으로 생성해주는 롬복 어노테이션이 바로 @RequiredArgsConstructor 이다.

  2. BookSaveReqDto dto를 책 등록하기 메서드에 주입시킨다.


이렇게해서 책 등록 구현이 끝나면 좋겠지만 아쉽게도 고려해야할 부분들이 있다. 위의 책 등록하기에서의 bookPS 는 Persistence Context에 저장된 영속화된 객체를 의미한다. 즉, bookRepository.save 를 통해 DB에 저장되고 난 후의 Book 이 되는 것이다.

이 영속화된 Entity인 bookPS 를 클라이언트가 요청을 하게 될 때, DB에서 그대로 가져오게 되면 어떻게 될까?

우선 이 해답을 얻기 위해서 스프링이 어떻게 요청과 응답을 처리하는지 흐름을 알 필요가 있다.


다음을 살펴보자.


스프링의 동작구조

스프링은 보통 위와 같은 플로우를 가지고 동작한다.

1. Request 흐름

스프링의 동작 흐름을 이해하기 쉽게 넘버링을 했다.

  1. 클라이언트 의 요청에 따라 book Entity가 생성된다. MIME 타입은 보통 JSON일 것이다.

  2. Dispatcher Servlet 에서는 이를 토대로 주소를 분석한다.
    (ex. url /book에 POST 방식으로 저장)

  3. Controller 에서는 validation(유효성)을 체크하거나 값을 파싱하는 등의 작업을 수행하고, book Entity를 bookSaveReqDto 와 같은 DTO로 변환한다.

  4. Service layer에서는 DTO를 Repository에 저장할 수 있게끔 다시 Entity로 변환한다.

  5. 변환된 book Entity를 저장한다.

  6. Persistence Context 에 의해 book Entity가 있는지 없는지 체크된다.

  7. book Entity를 DB에 저장한다.
    ex. {ID:1, title:Junit, author:메타코딩}


2. Response 흐름


1. DB 에 저장된 book Entity는 영구적으로 저장 (영속화) 된다.

2.Persistence Context 에 의해 bookPS 로 변환된다.

3~4. RepositoryServicebookPS 를 넘겨준다.
마찬가지로 Service 에서 Controller 로 영속화된 객체를 넘겨준다.

5.Service 단에서 트랜잭션이 시작되고 비즈니스와 관련된 모든 로직이 끝나면 트랜잭션이 종료된다. 여기서부터는 DB 에 쓰기(write)가 불가능해진다. 또한, DB Session이 닫히기 때문에 역시 쓰기는 불가능하고 select만 가능해진다. 마지막으로 Controller 에선 Dispatcher Servlet 으로 bookPS 를 넘겨준다.

6.Dispatcher Servlet 에서 클라이언트 로 응답 데이터를 전달한다.

7.클라이언트 가 데이터를 받아보게 된다.


Entity❓ DTO❗

여기까지가 요청과 응답에 대한 흐름이다. 그러나 우리는 마지막 8번에 주목해보자.

만약 client가 bookPS 와 관련된 사항을 요청했다고 가정하자. 예를 들어, bookPS.getUser() 와 같은 내용이다. get 요청이기 때문에 DB에서부터 bookPS 의 내용을 select하는 작업이 진행될 것이다.

bookPS 는 하나의 Entity이다. ServicebookPS 엔티티를 받아 Controller 에 전달할 것이다.

이렇게 되면 client 입장에서는 book 의 User 정보에 대한 것만을 요청했는데 영속화된 객체인 bookPS 의 getter를 통해 필요없는 다른 속성들까지 모두 넘어오게 된다. 따라서 필요이상으로 메모리를 잡아먹고 속도가 느려지게 된다.

이 외에도 Entity를 넘기기 되면 @NotEmpty 와 같은 검증 로직이 필요, 같은 엔티티에 대해 여러 로직이 필요 등 여러 문제 상황이 발생하게 된다.


그렇다면 어떻게 해야할까?

💡 bookPS 엔티티를 Controller 로 바로 넘기는 것이 아니라 Dto 로 변환시켜서 넘겨야한다.

따라서 BookSaveDto 처럼 BookRespDto 도 생성해서 Controller 는 오직 Dto 만 다룰 수 있게끔 해주어야 한다.

요청과 응답으로 Entity 대신 Dto 를 사용했을 때의 장점들을 알고 싶다면 이 블로그를 참고하면 더 자세히 나와있다.
(https://tecoble.techcourse.co.kr/post/2020-08-31-dto-vs-entity/)

BookRespDto

BookRespDto.java

package site.metacoding.junitproject.web.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;
import site.metacoding.junitproject.domain.Book;

@NoArgsConstructor
@Getter
public class BookRespDto {
    private Long id;
    private String title;
    private String author;

    public BookRespDto toDto(Book bookPS) {		// Dto로 변환하는 메서드
        this.id = bookPS.getId();				
        this.title = bookPS.getTitle();
        this.author = bookPS.getAuthor();
        return this;
    }
}

위의 Dto 들을 토대로 책 등록 코드를 완성시켜보자.

Service layer - 책 등록 (완료)

BookService.java

package site.metacoding.junitproject.service;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import javax.transaction.Transactional;
import site.metacoding.junitproject.domain.Book;
import site.metacoding.junitproject.domain.BookRepository;
import site.metacoding.junitproject.web.dto.BookRespDto;
import site.metacoding.junitproject.web.dto.BookSaveReqDto;

@RequiredArgsConstructor              
@Service
public class BookService {

    private final BookRepository bookRepository;
    
    @Transactional(rollbackOn = RuntimeException.class)    // 1.
    public Book 책등록하기(BookSaveReqDto dto) {       
        Book bookPS = bookRepository.save(dto.toEntity());
			return new BookRespDto().toDto(bookPS);
        }
    }
profile
학습하며 도전하는 것을 즐기는 개발자

0개의 댓글