스터디/back

12장 ~ 14장

ㅈㅣ니 2023. 12. 21.
반응형

12장 서비스 계층과 트랜잭션

 

서비스란?

컨트롤러와 리퍼지터리 사이에 위치하는 계층으로 서버의 핵심 기능(비즈니스 로직)을 처리하는 순서를 총괄한다.

전반적인 흐름

서비스 업무 처리는 트랜잭션 단위로 진행된다.

 

트랜잭션이란?

모두 성공해야 하는 일련의 과정을 의미한다. (쪼갤 수 없는 업무 처리의 최소 단위)

트랜잭션이 실패롤 돌아갈 경우 진행 초기 단계로 돌리는 것을 롤백(Rollback)이라고 한다.

 

지난번에 만든 ArticleApiController.java 에 서비스 계층을 추가하겠다.

먼저, Controller 안의 모든 코드를 주석 처리...

[ ArticleApiController.java ]

@Slf4j      // 로그 찍는 어노테이션
@RestController     // REST 컨트롤러 선언
public class ArticleApiController {
    @Autowired
    private ArticleService articleService;  // 서비스 객체 주입

}

 

service 패키지에 ArticleService 클래스를 생성해준다.

[ ArticleService.java ]

@Service    // 서비스 객체 생성
public class ArticleService {
    @Autowired
    private ArticleRepository articleRepository;    // 게시글 리포지터리 객체 주입


}

@Service 어노테이션을 붙이면 해당 클래스를 서비스로 인식해서 스프링 부트에 서비스 객체를 생성한다.

 

1) 게시글 조회 요청 개선하기

1-1) 모든 게시글 조회 요청 개선하기

 

주석처리 했던 index() 메서드에 다시 주석을 풀어주고 return 문을 articleRepository가 아닌 articleService로 변경해준다.

// GET
@GetMapping("/api/articles")
public List<Article> index() {      // index() 메서드 정의
    return articleService.index();
}

 

그리고 index() 메서드에 마우스를 올려서 Create method..... 를 클릭 후 ArticleService 에 index() 메서드가 추가될 수 있게 해준다.

[ ArticleService.java ]

public List<Article> index() {
    return articleRepository.findAll();
}

이렇게 바꾼 후 api 테스트를 해보면,,,

이렇게 데이터가 조회되는 것을 확인할 수 있다.

1-2) 단일 게시글 조회 요청 개선하기

마찬가지로 AtricleApiController에 show() 메서드 주석을 풀어준다.

@GetMapping("/api/articles/{id}")
public Article show(@PathVariable Long id) {      // show() 메서드 정의
    return articleService.show(id);
}

그리고 ArticleService 부분에 show()메서드를 추가.

public Article show(Long id) {
    return articleRepository.findById(id).orElse(null);
}

 

여기서 포인트는 컨트롤러와 리파지터리가 직접 연결되는 것이 아니라. 서비스 계층을 통해서 요청을 주고 받게 하는 것이다.

테스트 결과

2) 게시글 생성 요청 개선하기

이번에는 create() 메서드의 주석을 푼다.

[ ArticleApiController.java ]

// POST
@PostMapping("/api/articles")
public ResponseEntity<Article> create(@RequestBody ArticleForm dto) {    //create() 메서드 정의  수정할 데이터 dto 매개변수로 받아옴
    Article created = articleService.create(dto);   // 서비스로 게시글 생성
    return (created != null) ?              // 생성하면 정상, 실패하면 오류 응답
            ResponseEntity.status(HttpStatus.OK).body(created) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}

 

[ ArticleService.java ]

public Article create(ArticleForm dto) {
    Article article = dto.toEntity();           // dto -> 엔티티로 변환 후 article에 저장
    if (article.getId() != null) {
        return null;
    }
    return articleRepository.save(article);     // article을 DB에 저장..
}

 

3) 게시글 수정 요청 개선하기

update() 메서드의 주석을 해제한다.

[ ArticleApiController.java ]

// PATCH
@PatchMapping("/api/articles/{id}")
public ResponseEntity<Article> update(@PathVariable Long id, @RequestBody ArticleForm dto) {
    Article updated = articleService.update(id, dto);   // 서비스를 통해 게시글 수정
    return (updated != null) ?      // 수정되면 정상, 안되면 오류 응답
            ResponseEntity.status(HttpStatus.OK).body(updated) :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}

 

[ ArticleService.java ]

public Article update(Long id, ArticleForm dto) {
    // 1. DTO -> 엔티티 변환하기
    Article article = dto.toEntity();   //dto를 엔터티로 변환
    log.info("id: {}, article: {}", id, article.toString());        // 로그
    // 2. 타깃 조회하기
    Article target = articleRepository.findById(id).orElse(null);
    // 3. 잘못된 요청 처리하기
    if (target == null || id != article.getId()) {
        // 400번 에러 발생
        log.info("잘못된 요청! id: {}, article: {}", id, article, toString());

        return null;
    }

    // 4. 업데이트 및 정상 응답하기(200)
    target.patch(article);
    Article updated = articleRepository.save(target);      // article 엔터티 DB 저장
    return null;
}

 

4) 게시글 삭제 요청 개선하기

[ ArticleApiController.java ]

// DELETE
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Article> delete(@PathVariable Long id) {
    Article deleted = articleService.delete(id);        // 서비스를 통해 게시글 삭제
    return (deleted != null) ?
            ResponseEntity.status(HttpStatus.NO_CONTENT).build() :
            ResponseEntity.status(HttpStatus.BAD_REQUEST).build();

}

 

[ ArticleService.java ]

public Article delete(Long id) {
    // 1. 대상 찾기
    Article target = articleRepository.findById(id).orElse(null);
    // 2. 잘못된 요청 처리하기
    if (target == null) {
        return null;    // 응답은 컨트롤러가 함.
    }
    // 3. 대상 삭제하기
    articleRepository.delete(target);
    return target;  // DB에서 삭제한 대상을 컨트롤러에 반환
}

 

트랜잭션 맛보기

트랜잭션은 성공하지 못하면 롤백이 되어야한다. 다음은 롤백 실습이다. 순서는 다음과 같다.

1. 게시판에 데이터 3개를 한꺼번에 생성 요청하기

2. 데이터를 DB에 저장하는 과정에서 의도적으로 오류 발생시키기

3. 어떻게 롤백되는지 확인하기

 

[ ArticleApiController.java ]

@PostMapping("/api/transaction-test")       // 여러 게시글 생성 요청 접수
public ResponseEntity<List<Article>> transactionTest(@RequestBody List<ArticleForm> dtos){
    List<Article> createdList = articleService.createArticles(dtos);    // 서비스 호출
    return (createdList != null) ?          //생성 결과에 따라 응답 처리
    ResponseEntity.status(HttpStatus.OK).body(createdList) :
    ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}

 

[ ArticleService.java ]

@Transactional
public List<Article> createArticles(List<ArticleForm> dtos) {
    // 1. dto 묶음을 엔티티 묶음으로 변환하기
    List<Article> articleList = dtos.stream()
            .map(dto -> dto.toEntity())
            .collect(Collectors.toList());
    // 2. 엔티티 묶음을 DB에 저장하기
    articleList.stream()
            .forEach(article -> articleRepository.save(article));

    // 3. 강제 예외 발생시키기
    articleRepository.findById(-1L)     // id가 -1인 데이터 찾기
            .orElseThrow(() -> new IllegalArgumentException("결제 실패!"));
    // 4. 결과 값 반환하기
    return articleList;
}

 

 

1분 퀴즈)

2. 다음 ㉠, ㉡에 들어갈 용어를 쓰세요.

(  ㉠  )(이)란 모두 성공해야 하는 일련의 과정으로 쪼갤 수 없는 업무 처리의 최소 단위다.

(  ㉠  )이/가 선언된 코드 내부에서 만약 실행에 실패하면 변경된 데이터를 모두 이전 값으로 되돌리는데, 이를 (  ㉡  )(이)라 한다.

ㄱ: 트랜잭션

ㄴ: 롤백(rollback)

 


13장 테스트 코드 작성하기

 

테스트란?

프로그램의 품질을 검증하는 것ㅇ로, 의도대로 프로그램이 잘 동작하는지 확인하는 과정이다.

예전에는 직접 요청을 보내고 응답을 받아 일일이 확인하는 방식이었다면, 지금은 테스트 도구를 이용하여 반복적인 검증 절차를 자동화할 수 있다.

 

테스트 코드의 단계

1. 예상 데이터 작성하기

2. 실제 데이터 획득하기

3. 예상 데이터와 실제 데이터 비교 검증하기.

 

작성한 코드가 테스트를 통과하면 지속적인 리팩터링으로 코드를 개선하고,

만약 테스트를 통과하지 못하면 디버깅을 해야한다.

 

+ 테스트 케이스를 작성하여 다양한 경우를 대비하여야 한다. (성공뿐만 아니라 실패할 경우도 고려...)

테스트 주도 개발 ( TDD , Test Driven Development ) 라는 개발 방식으로 테스트 코드를 작성한 후

이를 통과하는 최소한의 코드부터 시작해 점진적으로 코드를 개선 및 확장해나가야 한다.

 

테스트 코드는 12장에서 만든 ArticleService 를 검증하는 것으로 작성해 볼 것이다.

 

 

반응형