[Spring]스프링 기초 - MVC 프레임워크 따라만들기(3)

Inung_92·2023년 2월 7일
0

Spring

목록 보기
4/15
post-thumbnail

지난 게시글들을 통해 Front Controller 패턴에 대해서는 좀 익숙해졌다. 동작하는 원리를 이해했으며, MVC 모델 내에서 어떤 역할을 하는지에 대해서 예제를 통해 알아보았다. 이번 게시글에서는 요청을 위임받은 컨트롤러와 DAO사이의 중간자 역할을 하며, 비즈니스 로직을 수행하는 Service에 대해서 알아보려고한다. 그럼 시작해보자.

Service란?

📖주 업무로는 트랜잭션을 수행하고, 이를 수행하기 위해 DB연결 객체를 보유한다. 또한, 컨트롤러로부터 위임 받은 요청 처리를 위한 비즈니스 로직을 수행하는 순수한 자바 코드(POJO)를 가진 클래스를 의미

Service(이하 서비스)는 이전까지 우리가 만들었던 MVC 프레임워크에는 존재하지 않던 클래스이다. 그렇다면 왜 갑자기 등장하게 되었을까?

⚡️ 사용하는 이유

  • 서비스는 트랜잭션 처리가 필요한 비즈니스 로직에 적합
  • 컨트롤러가 요청 처리에 필요한 일부 비즈니스 로직을 수행할 경우 결합도가 높아짐
  • DB에 대한 접속 객체를 보유하고 있으며 각각의 DAO에게 쿼리수행을 지시
  • 순수한 자바 코드로 작성되어 컨트롤러로부터 데이터만 전달받기 때문에 코드의 범용성이 높아짐

위와 같은 이유가 서비스를 사용하는 대표적인 이유라고 할 수 있다.(어디까지나 공부하면서 느낀 내 생각👻) 어쨌거나 서비스가 사용됨으로써 비즈니스 로직을 일정 부분 수행하던 컨트롤러가 완전하게 요청에 대한 처리 지시만 할 수 있도록 역할이 한번 더 구분되었으며, 서비스를 포함한 Model 계층은 프로젝트의 성격이 바뀌더라도 재사용성이 확보된다.
그렇다면 서비스는 과연 MVC 프레임워크의 어느 위치에서 자신의 업무를 수행하는지 아래 그림을 통해 확인하자.

⚡️ 서비스의 업무

🖥️ 서비스 사용 전

현재 상태에서 컨트롤러가 수행하는 역할에 대해서 나열해보겠다.

  • DB 접속 정보 객체 보유(mybatis 등)
  • 트랜잭션 처리 필요 시 수행
  • DAO로부터 발생한 예외 처리(try/catch 등) 수행
  • Model 객체(DTO, VO 등)의 인스턴스를 생성하여 보유

그렇다면 서비스를 사용하는 아래의 그림을 보면서 추가적인 설명을 하겠다.

🖥️ 서비스 사용 후

서비스가 컨트롤러와 DAO사이의 중간자로 역할을 하게되면 얻게되는 효과가 무엇인지 나열해보겠다.

  • Model 계층내에서 트랜잭션 처리 가능 : 컨트롤러는 지시하고 결과만 반환받게됨
  • 사용하는 DB 정보가 변경될 경우 서비스의 접속 객체만 수정 : 결합도가 낮아짐
  • 순수한 자바 코드로 범용성이 높아 코드의 재사용성이 향상됨
  • Model 계층에서만 로직을 수행하며, V(view)/C(controller)와의 응집도가 낮아짐

위와 같은 효과들을 우리가 규모가 큰 프로그램을 구축하면 할수록 중요한 부분이 될 것이다. 실생활의 예를 들어보자. 스마트폰을 고치러 서비스 센터에 도착했는데 안내데스크 직원이 엔지니어에게 직접 스마트폰을 분리하여 어디가 잘못되었으니 고치고 확인을 받으라고 한다면? 어색하지 않은가? 어색한 것보다 이상하지 않은가?
만약 그 정도로 스마트폰에 대한 지식이 많은 직원이라면 엔지니어가 되어야 마땅하다. 안내직원(dispatcherServlet)은 고객의 요청사항을 접수해서 해당 부서(controller)로 연결해주고, 해당 부서(controller)에서는 스마트폰을 가장 잘 다룰 수 있는 엔지니어(service)에게 확인해달라고 지시를하면된다. 또한, 엔지니어(service)는 스마트폰을 고치는데 필요한 부품이나 장비를 보관함(DAO, DB)에서 찾아서 꺼내서 쓰면되는 것이다.

MVC 프레임워크는 바로 이러한 업무수행 절차를 기반으로 만들어진 것이다. 여기서 로직을 수행하는 것은 엔지니어와 엔지니어를 도와주는 장비이다. 즉, MVC 모델은 M에 해당하는 Model의 재사용성을 높이고 직접적인 접근을 방지하여 결합도를 낮추기 위해 사용한다는 것이다.
이쯤하면 개념적인 이야기는 어느정도 이해가 되었으니 예제 코드를 통해서 기존 컨트롤러에서 서비스로 어떤 코드가 넘어갔는지 확인해보자.


서비스 사용 예제

⚡️ 서비스 사용 전

🖥️ 컨트롤러

사원을 등록하는 컨트롤러가 있다고 가정하고 코드를 작성해보았다. 사원이 등록되기 전 부서가 먼저 등록이 되고 해당 부서의 PK를 FK로 참조하여 등록이 완료되어진다. 트랜잭션 처리가 필요한 예시를 만들기 위해 작성된 코드이니 어색하더라도 이해해주기 바란다.

import 생략...

//등록 요청을 담당하는 컨트롤러
public class RegistController implements Controller{
	//DB 관련 객체 보유
	Mybatis mybatis = mybatis.getInstance();
    
    //DAO 멤버 보유
    DeptDAO deptDAO = new DeptDAO();
    EmpDAO empDAO = new EmpDAO();
    
    @Override
    public void execute(HttpServletRequset request, HttpServletResponse response){
    	//SqlSession 인스턴스 등록
        SqlSession sqlSession = mybatis.getSqlSession();
        //세션 주입
        deptDAO.setSqlSession(sqlSession);
        empDAO.setSqlSession(sqlSession);
    
    	//요청 파라미터로 empty 객체 세팅
        Dept dept = new Dept(); //부서
        dept.setDname(request.getParameter("dname"));
        
        Emp emp = new Emp(); //사원
        emp.setEname(request.getParameter("ename"));
        emp.setSal(Integer.parseInt(request.getParameter("sal")));
        
        emp.setDept(dept); //부서 객체 주입
        
        //쿼리수행을 위한 예외처리
        try{
        	deptDAO.regist(dept); //등록과 동시에 <selectKey>를 통해 PK 획득
            empDAO.regist(emp);
            
            //등록이 완료되면 커밋
            sqlSession.commit();
        } catch(DeptException e){
        	sqlSession.rollback();
        } catch(EmpException e){
        	sqlSession.rollback();
        } finally{
        	mybatis.release();
        }
    }
    
    ...아래코드 생략
}

컨트롤러를 제외한 코드는 생략하겠다. 기본적인 쿼리문 수행과 XML설정이니 말이다. 혹시라도 해당 코드의 원본이 필요하다면 여기를 클릭해서 확인하길 바란다.

자 그렇다면 위 코드에서 아쉬운 부분들을 찾아보자.

  • 첫번째, 트랜잭션 처리를 위한 DB 관련 객체를 직접 보유하고, 트랜잭션을 처리한다.
  • 두번째, DAO에게 직접 쿼리문 수행 지시를 하며, 세션 주입까지 한다.
  • 세번째, 모든 트랜잭션이 종료되고 세션을 반납하여 비즈니스 로직의 일부를 수행한다.

위와 같은 역할을 컨트롤러가 했을 경우 로직이 수정되면 컨트롤러도 수정되어야하고, 일부 DB 정보가 바뀐다면 또 다시 수정을 해줘야하는 일이 빈번하게 발생한다. 이러한 부분은 로직수행에 필요한 제반사항이자 필수 요소들이므로 해당 요청을 전문적으로 수행할 수 있는 Model이 보유하면서 컨트롤러와는 결합도가 낮아지게 하는 것이 이식성과 코드의 재사용성 측면에서 더욱 올바르게 작용할 것이다.

이러한 효과를 달성하기 위해서 이제 서비스에 대한 코드를 확인해보자.

⚡️ 서비스 사용 후

컨트롤러와 서비스의 코드를 잘 살펴보면서 어떤 부분이 달라지고 개선되었는지 알아보자.

🖥️ 컨트롤러

import 생략...

//등록 요청을 담당하는 컨트롤러
public class RegistController implements Controller{
	//서비스 객체 보유
	EmpService empService;
    
    @Override
    public void execute(HttpServletRequset request, HttpServletResponse response){
    	//인스턴스 생성
    	empService = new EmpService();
    
    	//요청 파라미터로 empty 객체 세팅
        Dept dept = new Dept(); //부서
        dept.setDname(request.getParameter("dname"));
        
        Emp emp = new Emp(); //사원
        emp.setEname(request.getParameter("ename"));
        emp.setSal(Integer.parseInt(request.getParameter("sal")));
        
        emp.setDept(dept); //부서 객체 주입
        
        //서비스 메서드만 호출
        empService.regist(emp);
    }
    
    ...아래코드 생략
}

🖥️ 서비스

import 생략...

public class EmpService(){
	//DB 관련 객체 보유
	Mybatis mybatis = mybatis.getInstance();
    
    //DAO 멤버 보유
    DeptDAO deptDAO;
    EmpDAO empDAO;
    
    //생성자 메서드
    public EmpService(){
	    deptDAO = new DeptDAO();
    	empDAO = new EmpDAO();
    }
    
    //매개변수로 데이터만 전달받음
    public void regist(Emp emp){
    	//SqlSession 인스턴스 등록
        SqlSession sqlSession = mybatis.getSqlSession();
        //세션 주입
        deptDAO.setSqlSession(sqlSession);
        empDAO.setSqlSession(sqlSession);
        
        //쿼리수행을 위한 예외처리
        try{
        	deptDAO.regist(emp.getDept()); //매개변수로 전달받은 객체에서 추출
            empDAO.regist(emp);
            
            //등록이 완료되면 커밋
            sqlSession.commit();
        } catch(DeptException e){
        	sqlSession.rollback();
        } catch(EmpException e){
        	sqlSession.rollback();
        } finally{
        	mybatis.release();
        }
    }
}

위의 코드를보면 컨트롤러의 역할이 확실히 달라졌음을 알 수 있을 것이다. 요청 객체로부터 넘어온 데이터만 가공하여 서비스에게 전달하며, 서비스의 메서드를 추상적(단순)으로 사용하여 내부 로직에 대한 깊은 이해가 없더라도 역할을 잘 수행하고 있는 것이다.

그렇다면 서비스를 한번 봐보자. 해당 코드를 웹 프로그램이 아닌 Swing기반의 독립실행형으로 쓴다고 하더라도 Model의 코드는 재사용이 가능한 상태가 된 것이다. 또한 서비스 내에서 트랜잭션을 수행함으로써 Model 계층에서 로직의 완벽한 구현이 가능한 것이다.

여기까지 알아봤으니 다시 한번 간단하게 정리를 해보자.

  • 컨트롤러 : 클라이언트로부터 요청을 받으면 요청을 분석하고 해당 요청에 적합한 비즈니스 로직 수행을 지시한다.
  • 서비스 : 컨트롤러로부터 비즈니스 로직 수행 지시를 받으면 해당 로직을 수행하며 DB에 자원을 저장 또는 반환받아 요청을 처리하고 결과가 있는 경우 결과를 반환한다.

마무리

이번 게시글에서는 MVC 프레임워크에서 중요한 역할을 수행하는 서비스에 대해서 알아보았다. 막상 공부를 하면서도 컨트롤러가 일부 비즈니스 로직을 수행하는 것을 제대로 알지 못했다. 하지만 서비스를 사용하게 되면서 비즈니스 로직은 Model 계층에 완전히 분리되어 결합도를 낮추고 서로 간섭을 최대한 안받는 것이 효율적인 프로그램을 개발하는 방법이라는 것을 알게 되었다.

느낀점

  • 추상화를 통해 요청 처리에 대한 업무지시는 단순하게하며, 비즈니스 로직 수행에 대한 역할은 Model이 전담하여 처리하는 절차에 대하여 알게되었음.
  • 트랜잭션 처리에 필요한 클래스 구성에 대해서 알 수 있었음.
  • 서비스를 포함하는 Model 계층은 순수한 자바 코드로 작성되어 이식성이 높으며 코드의 재사용성도 높아서 유지보수가 편리하고, 프로그램의 성격이 바뀌더라도 코드의 수정을 최소화 할 수 있음.

이제 본격적으로 스프링을 사용하면서 MVC 프레임워크 만들기를 지속하려한다. 조금 기대도 되지만 어려울까봐 걱정도 된다. 그래도 일단 하자.

그럼 이만.👊🏽

🖥️예제 전체코드 : https://github.com/HwangInUng/MVC-study/tree/master/MVCApplication

profile
서핑하는 개발자🏄🏽

0개의 댓글