inblog logo
|
하쎄의 기술 일기장
    springboot

    @Service의 이해와 사용법

    11. Service 의 역할
    하세연's avatar
    하세연
    Aug 22, 2024
    @Service의 이해와 사용법
    Contents
    * Service를 이용한 로그인 사용자 인증 활용 예시
    💡
    Service는 소프트웨어 아키텍처에서 중요한 역할을 담당하는 계층으로, 주로 비즈니스 로직을 처리하고, Controller와 Repository 사이에서 중간자 역할을 합니다. 이 계층의 주요 목적은 애플리케이션의 비즈니스 규칙을 캡슐화하고, 데이터 처리나 외부 서비스와의 통신을 포함한 다양한 작업을 수행하는 것입니다.
    만약 회원 가입 기능이 있다고 가정한다면,
    • Controller: 사용자의 요청을 받고, 해당 요청을 Service에 전달하며, Service로부터 받은 결과를 클라이언트에 반환한다.
    • Service: 사용자 데이터의 유효성을 검사하고, 비밀번호를 암호화한 후 Repository에 저장합니다. 비즈니스 규칙에 따라 추가적인 처리를 할 수도 있다.
    • Repository: 데이터베이스와 상호작용하여 사용자 정보를 저장한다.
    이런 구조를 통해 코드의 재사용성을 높이고, 각 계층의 역할을 명확하게 분리할 수 있다.
     
    notion image
     

    * Service를 이용한 로그인 사용자 인증 활용 예시

    전체적인 과정은 로그인을 한 사용자의 정보를 session에서 받아와 Service 에서 데이터를 검토하고 적절한 데이터 값을 Controller로 넘길 것이다. 이를, 페이지에서는 로그인한 사용자가 게시물을 작성한 사용자와 동일한 ID값을 가지고 있다면 수정과 삭제가 가능하도록 버튼이 활성화 되도록 만들 것이다.

    1. Repository 생성

    public Board findById(int id) { Query query = em.createQuery("select b from Board b join fetch b.user where b.id = :id", Board.class); query.setParameter("id", id); try { Board board = (Board) query.getSingleResult(); System.out.println("----------"); System.out.println(board); return board; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("게시글 id를 찾을 수 없습니다."); }
    Board의 id를 받아와 게시물에 관한 데이터를 받아온다.
     

    2. Response 생성

    board 데이터를 바탕으로 사용자 session의 정보와 작성자의 id 가 일치하는지 그리고 그에 관한 나머지 데이터가 담길 수 있도록 DTO를 생성해 준다.
    package shop.mtcoding.blog.board; import lombok.Data; import shop.mtcoding.blog.user.User; public class BoardResponse { @Data public static class DetailDTOV2 { private Integer id; private String title; private String content; private Boolean isOwner; private UserDTO user; public DetailDTOV2(Board board, User sessionUser) { this.id = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.isOwner = false; if (board.getUser().getId() == sessionUser.getId()) { isOwner = true; } this.user = new UserDTO(board.getUser()); } @Data public class UserDTO { private Integer id; private String username; public UserDTO(User user) { this.id = user.getId(); this.username = user.getUsername(); } } } @Data public static class DetailDTO { private Integer boardId; private String title; private String content; private Boolean isOwner; private Integer userId; private String username; public DetailDTO(Board board, User sessionUser) { this.boardId = board.getId(); this.title = board.getTitle(); this.content = board.getContent(); this.isOwner = false; if (board.getUser().getId() == sessionUser.getId()) { isOwner = true; } this.userId = board.getUser().getId(); this.username = board.getUser().getUsername(); } } }
    DTOV2의 방식으로 할 경우 UserDto로 한번 더 묶어서 데이터를 깔끔하게 묶을 수 있다.
    그리고 isOwner 라는 boolean값을 생성하여 그 안에 로그인한 사용자와 게시물 작성자의 id를 비교하고 같다면 true 다르다면 false 값을 담도록 필드를 생성해준다. 이렇게 DTO로 미리 값을 담아 놓는다면 Controller에서는 데이터를 추가로 가공하지 않고 값만 넘겨서 쉽게 view로 넘길 수 있다.
     

    3. Service 생성

    package shop.mtcoding.blog.board; // C -> S -> R import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Service public class BoardService { private final BoardRepository boardRepository; public BoardResponse.DetailDTO 상세보기(int id, User sessionUser) { Board board = boardRepository.findById(id); // 조인 (Board - User) return new BoardResponse.DetailDTO(board, sessionUser); } }
    💡
    Service 에서는 기능을 중심으로 다루기 때문에 메서드의 이름은 기능을 사용하는 것이 좋다!!
    앞에서 만든 Response를 Service에 가져와 board와 session 의 id를 담아 기능을 수행할 수 있도록 작성해준다.
    💡
    @RequiredArgsConstructor 는 Lombok 라이브러리에서 제공하는 어노테이션으로, 클래스의 final 필드나 @NonNull이 지정된 필드를 인자로 받는 생성자를 자동으로 생성해주는 기능을 합니다. 이 어노테이션을 사용하면, 코드에서 불필요한 보일러플레이트(boilerplate) 코드를 줄일 수 있습니다. 코드 간결화와 유지보수성을 높이기 위해 사용되며, 불변 객체를 만들 때도 유용합니다.
     

    4. Controller 생성

    @GetMapping("/board/{id}") public String detail(@PathVariable("id") Integer id, HttpServletRequest request) { // Board board = boardRepository.findById(id); // request.setAttribute("model", board); // request.setAttribute("isOwner", false); User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DetailDTO detailDTO = boardService.상세보기(id, sessionUser); request.setAttribute("model", detailDTO); return "board/detail";
    service에서 생성된 DTO를 받아와 model에 담고 이를 view로 뿌려준다.
     

    5. detail.mustache

    {{>layout/header}} <div class="container p-5"> <!-- 수정삭제버튼 --> {{#isOwner}} <div class="d-flex justify-content-end"> <a href="/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a> <form action="/board/{{model.id}}/delete" method="post"> <button class="btn btn-danger">삭제</button> </form> </div> {{/isOwner}} <div class="d-flex justify-content-end"> <b>작성자</b> : {{model.user.username}} </div> <!-- 게시글내용 --> <div> <h2><b>{{model.title}}</b></h2> <hr/> <div class="m-4 p-2"> {{model.content}} </div> </div> </div> {{>layout/footer}}
    ✨
    ** mustache 문법
    1. {{key}} → 출력
    2. for문 if문 (연산X)
    컬렉션      {{#컬렉션}} {{/컬렉션}} → 반복문
    오브젝트   {{#오브젝트}} {{/오브젝트}} → 존재하면 실행 , 아니면 실행안함
    Boolean    {{#Boolean}} {{/Boolean}} → true 실행 false 실행안함
    {{#model.user.id == sessionUser.id}} ← 불가
    3. 컬렉션 타입이 단순 자료형
    컬렉션<String> {{#컬렉션}}  {{.}}    {{/컬렉션}}
    로그인한 사용자의 id와 작성자의 id가 같다면 isOwner 가 true로 반환되어 수정 삭제 버튼이 보이게 되고, 만약 다르다면 보이지 않게 된다.
    Share article

    하쎄의 기술 일기장

    RSS·Powered by Inblog