참고 링크 : https://wikidocs.net/book/7601
오라클DB와 intelliJ로 작업하였습니다
우리는 QuestionController에서 QuestionRepository를 직접 사용하여 질문 목록 데이터를 조회했다.
하지만 대부분의 규모있는 스프링부트 프로젝트는 컨트롤러에서 리포지터리를 직접 호출하지 않고
중간에 서비스(Service)를 두어 데이터를 처리한다.
서비스는 스프링에서 데이터 처리를 위해 작성하는 클래스이다.
서비스(Service)가 필요한 이유
모듈화
예를들어 어떤 컨트롤러가 여러개의 리포지터리를 사용하여 데이터를 조회한후 가공하여 리턴한다고 가정해 보자. 이러한 기능을 서비스로 만들어 두면 컨트롤러에서는 해당 서비스를 호출하여 사용하면 된다. 하지만 서비스로 만들지 않고 컨트롤러에서 구현하려 한다면 해당 기능을 필요로 하는 모든 컨트롤러가 동일한 기능을 중복으로 구현해야 한다. 이러한 이유로 서비스는 모듈화를 위해서 필요하다.
보안
컨트롤러는 리포지터리 없이 서비스를 통해서만 데이터베이스에 접근하도록 구현하는 것이 보안상 안전하다. 이렇게 하면 어떤 해커가 해킹을 통해 컨트롤러를 제어할 수 있게 되더라도 리포지터리에 직접 접근할 수는 없게 된다.
엔티티 객체와 DTO 객체의 변환
우리가 작성한 Question, Answer 클래스는 엔티티(Entity) 클래스이다. 엔티티 클래스는 데이터베이스와 직접 맞닿아 있는 클래스이기 때문에 컨트롤러나 타임리프 같은 템플릿 엔진에 전달하여 사용하는 것은 좋지 않다. 컨트롤러나 타임리프에서 사용하는 데이터 객체는 속성을 변경하여 비즈니스적인 요구를 처리해야 하는 경우가 많은데 엔티티를 직접 사용하여 속성을 변경한다면 테이블 컬럼이 변경되어 엉망이 될수도 있기 때문이다.
이러한 이유로 Question, Answer 같은 엔티티 클래스는 컨트롤러에서 사용할수 없게끔 설계하는 것이 좋다. 그러기 위해서는 Question, Answer 대신 사용할 DTO(Data Transfer Object) 클래스가 필요하다. 그리고 Question, Answer 등의 엔티티 객체를 DTO 객체로 변환하는 작업도 필요하다. 그러면 엔티티 객체를 DTO 객체로 변환하는 일은 어디서 처리해야 할까? 그렇다. 바로 서비스이다. 서비스는 컨트롤러와 리포지터리의 중간자적인 입장에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.
점프 투 스프링부트이 책은 간결한 설명을 위해 별도의 DTO를 만들지 않고 엔티티 객체를 컨트롤러와 타임리프에서 그대로 사용할 것이다. 하지만 실제 업무 프로그램을 작성할 때는 엔티티 클래스를 대신할 DTO 클래스를 만들어 사용하기를 권한다.
QuestionService를 작성하자.
package com.gosari.repick_project.question;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@RequiredArgsConstructor //QuestionRepository 생성자 생성해줌
@Service
public class QuestionService {
private final QuestionRepository questionRepository;
public List<Question> getList(){
return this.questionRepository.findAll();
}
}
스프링의 서비스로 만들기 위해서는 위와 같이 클래스명 위에 @Service 애너테이션을 붙이면 된다. @Controller, @Entity 등과 마찬가지로 스프링부트는 @Service 애너테이션이 붙은 클래스는 서비스로 인식한다.
questionRepository 객체는 생성자 방식으로 DI 규칙에 의해 주입된다.
그리고 질문 목록을 조회하여 리턴하는 getList 메서드를 추가했다. 이전 컨트롤러에서 리포지터리를 사용했던 부분을 그대로 옮긴 것이다.
QuestionController
그리고 QuestionController는 리포지터리 대신 서비스를 사용하도록 다음처럼 수정하자.
package com.gosari.repick_project.question;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@RequiredArgsConstructor//QuestionService 생성자 생성해줌
@Controller
public class QuestionController {
/*
수정전 코드 :
private final QuestionRepository questionRepository;
@RequestMapping("/question/list")
public String list(Model model){
List<Question> questionList = this.questionRepository.findAll();
model.addAttribute("questionList" , questionList);
return "question_list";
}*/
private final QuestionService questionService;
@RequestMapping("/question/list")
public String list(Model model){
List<Question> questionList = this.questionService.getList();
model.addAttribute("questionList", questionList);
return "question_list";
}
}
questionService 객체는 생성자 방식으로 DI 규칙에 의해 주입된다.
이전과 동일한 화면을 볼수 있다. 앞으로 작성할 컨트롤러들도 리포지터리를 직접 사용하지 않고 Controller -> Service -> Repository 구조로 데이터를 처리할 것이다.
'Follow Work > SpringbootBoard' 카테고리의 다른 글
[StringBoot] 답변등록 (8) (0) | 2022.08.12 |
---|---|
[StringBoot] 질문상세 (7) (0) | 2022.08.12 |
[StringBoot] ROOT URL (5) (0) | 2022.08.11 |
[StringBoot] 질문 목록과 템플릿 (4) (0) | 2022.08.11 |
[StringBoot] 리포지터리 (3) (0) | 2022.08.11 |