2022.10.05

Jimin·2022년 10월 5일
0

비트캠프

목록 보기
51/60
post-thumbnail
  • board-app 프로젝트 수행
      1. DAO에서 비즈니스 로직 분리하기: Controller에서 비즈니스 로직처리
      1. Controller에서 비즈니스 로직 분리하기: 서비스 컴포넌트 도입

070. 파일을 업로드하기 2:
Serlet API로 파일 업로드 처리하기

BoardDao class <<Model>>에서 비즈니스 로직과 데이터 처리를 모두 담당

insert() 메서드로 게시글 입력첨부파일 입력을 모두 처리한다.
⇒ 만약 다른 프로젝트에서 첨부파일을 다루지 않은 경우에는 어떻게 하나?
⇒ DAO 클래스를 변경해야 한다.

문제점

업무 로직이 변경되면 DAO 코드를 변경해야한다. (고객사마다 업무처리 방식이 조금씩 다르다.)
⇒ 재사용성이 떨어진다.

해결책

DAO에서 업무로직을 분리한다.


071. DAO에서 비즈니스 로직 분리하기:
Controller에서 비즈니스 로직처리

  • DAO에서 비즈니스 로직을 분리하는 이유와 방법

1단계 - 게시글 등록과 관련된 업무 흐름을 DAO에서 분리한다.

  • com.bitcamp.board.dao.MariaDBBoardDao 클래스 변경
  • com.bitcamp.board.controller.BoardAddController로 클래스 변경

BoardDao에서 관리하던 비즈니스 로직, 수행을 BoardAddController로 이동한다.
DAO를 조금 더 재사용하기 좋게 만든다.

2단계 - 게시글 변경과 관련된 업무 흐름에서 DAO에서 분리한다.

  • com.bitcamp.board.dao.MariaDBBoardDao 클래스 변경
    • update() 변경
  • com.bitcamp.board.controller.BoardUpdateController 클래스 변경

3단계 - 게시글 삭제와 관련된 업무 흐름을 DAO에서 분리한다.

  • com.bitcamp.board.dao.MariaDBBoardDao 클래스 변경
    • delete() 변경
  • com.bitcamp.board.controller.BoardDeleteController 클래스 변경

문제점

Controller가 너무 많은 일들을 한다.
⇒ High Cohesion
⇒ 비즈니스 로직을 분리
⇒ 별도 클래스(서비스 컴포넌트)로 정의


072. Controller에서 비즈니스 로직 분리하기:
서비스 컴포넌트 도입

  • Controller에서 비즈니스 로직을 분리하는 이유
  • 서비스 컴포넌트의 역할 이해

1단계 - Controller에서 비즈니스 로직을 분리하여 Service 클래스로 옮긴다.

  • com.bitcamp.board.controller.BoardXxxController 클래스 변경
  • com.bitcamp.board.service.BoardService 클래스 생성
  • com.bitcamp.board.controller.LoginXxxController 클래스 변경
  • com.bitcamp.board.controller.MemberXxxController 클래스 변경
  • com.bitcamp.board.service.MemberService 클래스 생성
  • com.bitcamp.board.listener.ContextLoaderListener 클래스 변경

Controller - Service - DAO 의 관계

  • ContextLoaderListener class
@WebListener
public class ContextLoaderListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("공유 자원을 준비 중!!");
        try {
            Class.forName("org.mariadb.jdbc.Driver");
            Connection con = DriverManager.getConnection(
                    "jdbc:mariadb://localhost:3306/studydb","study","1111");

            ServletContext ctx = sce.getServletContext();

            BoardDao boardDao = new MariaDBBoardDao(con);
            MemberDao memberDao = new MariaDBMemberDao(con);

            ctx.setAttribute("boardService", new BoardService(boardDao));
            ctx.setAttribute("memberService", new MemberService(memberDao));
        } catch(Exception e ) {
            e.printStackTrace();
        }
    }
}
  • BoardService class
// 비즈니스 로직을 수행하는 객체
// - 메서드 이름은 업무와 관련된 이름을 사용한다.
//
public class BoardService {
    BoardDao boardDao;

    public BoardService(BoardDao boardDao) {
        this.boardDao = boardDao;
    }

    public void add(Board board) throws Exception {
        // 1) 게시글 등록
        if (boardDao.insert(board) == 0) {
            throw new Exception("게시글 등록 실패!");
        }

        // 2) 첨부파일 등록
        boardDao.insertFiles(board);
    }

    public boolean update(Board board) throws Exception {
        // 1) 게시글 변경
        if (boardDao.update(board) == 0) {
            return false;
        }
        // 2) 첨부파일 추가
        boardDao.insertFiles(board);

        return true;
    }

    public Board get(int no) throws Exception {
        // 이 메서드의 경우 하는 일이 없다.
        // 그럼에도 불구하고 이렇게 하는 이유는 일관성을 위해서다.
        // 즉 Controller는 Service 객체를 사용하고 Service 객체는 DAO를 사용하는 형식을 
        // 지키기 위함이다.
        // 사용 규칙이 동일하면 프로그래밍을 이해하기 쉬워진다.
        return boardDao.findByNo(no);
    }

    public boolean delete(int no) throws Exception {
        // 1) 첨부파일 삭제
        boardDao.deleteFiles(no);

        // 2) 게시글 삭제
        return boardDao.delete(no) > 0;
    }

    public List<Board> list() throws Exception {
        return boardDao.findAll();
    }

    public AttachedFile getAttachedFile(int fileNo) throws Exception {
        return boardDao.findFileByNo(fileNo);
    }

    public boolean deleteAttachedFile(int fileNo) throws Exception {
        return boardDao.deleteFile(fileNo) > 0;
    }

}
  • BoardUpdateController class
@MultipartConfig(maxFileSize = 1024 * 1024 * 10)
@WebServlet("/board/update")
public class BoardUpdateController extends HttpServlet {
    private static final long serialVersionUID = 1L;

    BoardService boardService;

    @Override
    public void init() {
        boardService = (BoardService) this.getServletContext().getAttribute("boardService");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            request.setCharacterEncoding("UTF-8");

            Board board = new Board();
            board.setNo(Integer.parseInt(request.getParameter("no")));
            board.setTitle(request.getParameter("title"));
            board.setContent(request.getParameter("content"));

            List<AttachedFile> attachedFiles = new ArrayList<>();
            String dirPath = this.getServletContext().getRealPath("/board/files");
            Collection<Part> parts = request.getParts();
            for(Part part:parts) {
                if(!part.getName().equals("files")  || part.getSize() == 0) continue;
                String filename = UUID.randomUUID().toString();
                part.write(dirPath + "/" + filename);
                attachedFiles.add(new AttachedFile(filename));
            }
            board.setAttachedFiles(attachedFiles);

            // 게시글 작성자인지 검사한다.
            Member loginMember = (Member) request.getSession().getAttribute("loginMember");
            if (boardService.get(board.getNo()).getWriter().getNo() != loginMember.getNo()) {
                throw new Exception("게시글 작성자가 아닙니다.");
            }

            if(!boardService.update(board)) {
                throw new Exception("게시글을 변경할 수 없습니다!");
            }

            response.sendRedirect("list");
        } catch (Exception e) {
            request.setAttribute("exception", e);
            request.getRequestDispatcher("/error.jsp").forward(request, response);
        }
    }
}

073. 서비스 컴포넌트에 인터페이스 적용:
서비스 객체를 교체하기 쉽게 만들기

  • 서비스 객체에 인터페이스를 적용하는 이유와 방법

인터페이스 적용 전의 문제점

  • Service 객체는 고객사의 업무 방식에 따라 약간씩 다를 수 있다.
  • Controller가 Service 객체를 클래스 이름으로 사용하게 되면 Service 객체를 교체할 때마다 Controller 코드를 변경해야 한다.

⇒ 유지보수를 어렵게 만든다.

인터페이스를 적용한 해결책

⇒ Controller가 직접 class 이름을 언급하지 말고 인터페이스 규칙에 따라 사용하도록 변경하면 된다.

⇒ 유지보수가 쉽다!

1단계 - BoardService 객체를 인터페이스와 구현체로 분리한다.

  • com.bitcamp.board.service.BoardService 클래스를 인터페이스로 변경
  • com.bitcamp.board.service.DefaultBoardService 클래스생성
    기존의 BoardService 클래스를 인터페이스 구현 클래스로 만든다.
  • BoardService interface
// 비즈니스 로직을 수행하는 객체의 사용 규칙(호출 규칙)
//
public interface BoardService {
    void add(Board board) throws Exception;

    boolean update(Board board) throws Exception ;

    Board get(int no) throws Exception;

    boolean delete(int no) throws Exception;

    List<Board> list() throws Exception;

    AttachedFile getAttachedFile(int fileNo) throws Exception ;

    boolean deleteAttachedFile(int fileNo) throws Exception ;
}
  • DefaultBoardService class
public class DefaultBoardService implements BoardService{
	
    ...

2단계 - MemberService 객체를 인터페이스와 구현체로 분리한다.

  • com.bitcamp.board.service.MemberService 클래스를 인터페이스로 변경
  • com.bitcamp.board.service.DefaultMemberService 클래스생성
    기존의 MemberService 클래스를 인터페이스 구현 클래스로 만든다.

3단계 - 서비스 객체를 준비할 때 새로 생성한 구현체를 사용한다.

  • com.bitcamp.board.listener.ContextLoaderListener 클래스 변경

  • ContextLoaderListener class

@WebListener
public class ContextLoaderListener implements ServletContextListener{
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("공유 자원을 준비 중!!");
        try {
            Class.forName("org.mariadb.jdbc.Driver");
            Connection con = DriverManager.getConnection(
                    "jdbc:mariadb://localhost:3306/studydb","study","1111");

            ServletContext ctx = sce.getServletContext();

            BoardDao boardDao = new MariaDBBoardDao(con);
            MemberDao memberDao = new MariaDBMemberDao(con);

            ctx.setAttribute("boardService", new DefaultBoardService(boardDao));
            ctx.setAttribute("memberService", new DefaultMemberService(memberDao));
        } catch(Exception e ) {
            e.printStackTrace();
        }
    }
}
profile
https://github.com/Dingadung

0개의 댓글