본문 바로가기
SpringBoot/이론

[ Spring Boot ] 서비스 레이어와 인터페이스의 장점

by Hwanii_ 2023. 9. 21.
728x90

참고 : https://hwanii96.tistory.com/329

 

레이어 개념 정리

[ 결론 ] 비즈니스 로직 & 서비스 계층은 applicationContext.xml 에서 객체화 설정 작업을 한다. 즉, DAO, Service 류의 객체화는 applicationContext.xml 에서 작업 한다. 이때, DAO, Service 류는 서버가 시작할 때 "

hwanii96.tistory.com

 

루트 컨테이너 란 ?

 
가장 먼저 생성 되어야만 한다면 해당 설정을 해주기 위한 설정 파일을 루트 컨테이너 라고 한다.
 
어떤게 먼저 생성 되어야 할까 ?
DAO (Model) 는 가장 먼저 생성 되어야 한다.
 
이것을 서비스 레이어 라고 하며,
컨테이너 1개 == 1 레이어 == 1 계층 이라고 할 수 있다.
 
DispatcherServlet 이라는 이름으로 web.xml 이 서블릿 파일을 생성 하고,
이 DispatcherServlet 이라는 이름을 가진 서블릿에 대한 의존 주입 등 관련 설정을 하기 위한,
설정 파일을 DispatcherServlet-servlet 이라는 이름으로 생성을 했었는데, ( 서블릿 컨테이너 == web.xml )
이, DispatcherServlet-servlet은 컨테이너 1개 라고 볼 수 있다. ( 스프링 컨테이너 )
즉, 컨테이너 1개 == 1 레이어 == 1 계층 이고,
 
이렇게 컨테이너 2개 == 2 레이어 == 2 계층 으로 두개의 레이어를 통합한 방식을
"2-Layerd 아키텍처 스타일" 이라 부른다.
 
[ 참고 ]
 
SpringBoot는 자동으로 n개의 레이어드 아키텍처 스타일이 구현 될 수 있도록 이미 설정이 되어 있다.
이때, 이 설정은 인터페이스를 기반으로 설정 되어 있기 때문에,
n개의 레이어드 아키텍처 스타일을 구현 하고 싶으면,
인터페이스를 반드시 작성해줘야 자동으로 인식 할 수 있게 된다.
 
즉, 별도의 루트 컨테이너 설정이 필요 없게 된다.
 

DAO 류 객체를 사용하는 서비스 레이어 또는 컨트롤러 에서 DAO 류 객체를 참조할 때,
해당 빈 (객체) 이 없으면 NPE 가 발생 하게 된다.
이것은 DAO 류 객체가 주입 되지 않아서 발생 하는 문제인데,
그래서 반드시 DAO 류 객체는 가장 먼저 객체화가 되어야 하기 때문에,
루트 컨테이너 설정이 필수가 될 수 밖에 없는 개념 이다.

 

서비스 레이어 ?

서비스 레이어를 왜 사용 할까 ?
서비스 레이어는 비즈니스 로직을 분리 하기 때문에,
코드의 유지 보수 향상
코드의 확장성 향상
코드의 결합도 낮추기
가 가능 해진다.
 
암튼,
아래의 서비스 레이어는 DAO 류를 사용하는 주체가 되는 클래스 이다.
 
기존에 DAO 류 클래스를 생성 할 때, 인터페이스를 구현 받지 않고 단일 클래스를 생성 했었다.
 
그래서,
만약,
ProductDAO 라는 클래스가 있었는데,
변경이 발생 해서,
ProductDAO2 라는 클래스를 새로 생성했다면,
ProductDAO2 를 사용하는 주체가 되는 ProductService 클래스 에서 딱 1개만 코드를 수정 하면 됬었다.
 

 
의존 주입이 되어 있는 private ProductDAO 를
 

 
위와 같이 의존 주입할 변경된 멤버의 이름만 수정해주면 됬었다.
 
하지만,
이것조차도 결국 수정을 해야 한다는 단점이 분명 하고,
 
무엇보다도 변경된 DAO 클래스의 이름이 무엇인지 알지 못하더라도 상관이 없는 개발을 할 수 있도록
DAO 클래스의 인터페이스를 생성해 보려고 한다.
>> 코드의 유지보수가 용이해지는 장점을 갖게 된다.
 
 

인터페이스 생성 및 인터페이스를 구현 하기

 
BoardDAO 클래스는 CRUD == 비즈니스 메서드 가 작성 되어 있는 클래스 이다.
따라서, 메서드 시그니쳐가 정해져 있으므로 인터페이스를 작성해 보려고 한다.
 

인터페이스 내부의 메서드는 추상 메서드 이므로 메서드 바디를 가질 수 없다는 특징을 가진다.

 
 
인터페이스를 생성 했으니, 인터페이스를 구현하는 클래스를 작성 해보자.
 

 
InterfaceBoardDAO 인터페이스를 구현 하고, @Repository 어노테이션을 명시하면 된다.
 
다음으로, BoardDAO 를 사용하는 주체가 되는 Service 클래스를 작성 해보자.
 
마찬가지로,
사용하는 비즈니스 메서드의 메서드 시그니쳐가 고정 되어 있기 때문에 인터페이스를 먼저 작성 한다.
 

 
그리고, InterfaceBoardService 인터페이스를 구현하는 클래스를 생성 한다.
 

 
바로 이곳에서 DAO 인터페이스 자체를 @Autowired 어노테이션을 통해 의존 주입 하는것이 핵심 이다.
 
실제로 @Repository 어노테이션으로 객체화 되어 있는것은 BoardDAO 이지만,
 

 
이 BoardDAO는 InterfaceBoardDAO 라는 이름을 가진 인터페이스를 구현한 클래스 이기 때문에,
 

 
위와 같이 인터페이스 타입으로 의존 주입을 한것은, 인터페이스의 다형성이 실현 되었다 라고 할 수 있다.
 
코드를 유연 하게 사용 하기 위해서, 다형성 개념을 활용 하고, 이는 코드의 유지 보수와 확장성을 향상 시키게 된다.
 
 
 

예시를 통해 확인 하기

 
위와 같이 인터페이스의 다형성을 실현 시켜서, 코드를 유연 하게 만들었기 때문에 확인 할 수 있는 장점은 아래와 같다.
 

 
가령,
기존 BoardDAO 라는 클래스에 수정이 필요하거나 변경이 필요 해서 BoardDAO2 라는 클래스를 새로 생성 했으면,
 

 
기존에 사용 했던 BoardDAO 클래스를 객체화 하기 위해 작성된
@Repository 어노테이션을 제거 하거나 주석 처리 하고,
 

 
새로 생성한 BoardDAO2 클래스 위에 @Repository 어노테이션을 명시하면 된다.
 
이러면, 이 클래스를 사용하는 주체가 되는 서비스 클래스 에서는 코드를 변경할 필요가 없게 된다.
 
왜 ?
변경된건 DAO 류 클래스 일 뿐이다.
변경되는 DAO 류 클래스의 인터페이스 타입은 변경 되지 않고 동일 하기 때문 이다.
 

 
인터페이스를 생성 하지 않았더라면 ?
 

 
이렇게 했었어야 한다.
 
변경된 DAO 클래스 이름을 알아야만 하며, 어찌됬던지간에 코드를 1개라도 수정 했어야만 했다.
 
 
 

전체 코드 확인 하기

[ BoardDTO ]
 

package com.hwan.app.model;

import lombok.Data;

@Data
public class BoardDTO {
	
	private int bid;	//	PK
	private String mid;	//	FK
	private String content;
	
	private String sk;

}	//	BoardVO

 
[ InterfaceBoardDAO ]
 

package com.hwan.app.model;

import java.util.List;

public interface InterfaceBoardDAO {
	
	List<BoardDTO> selectAll(BoardDTO boardDTO);
	BoardDTO selectOne(BoardDTO boardDTO);
	boolean insert(BoardDTO boardDTO);
	boolean update(BoardDTO boardDTO);
	boolean delete(BoardDTO boardDTO);

}	//	InterfaceBoardDAO

 
[ BoardDAO ]
 

package com.hwan.app.model;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("BoardDAO")
public class BoardDAO implements InterfaceBoardDAO {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	private final String INSERT = "INSERT INTO BOARD (MID, CONTENT) VALUES (?, ?)";
	private final String SELECTALL_BOARD = "SELECT * FROM BOARD";
	private final String SELECTALL_MYPAGE = "SELECT * FROM BOARD WHERE MID = ?";
	private final String SELECTONE = "SELECT * FROM BOARD WHERE BID = ?";
	private final String UPDATE = "UPDATE BOARD SET CONTENT = ? WHERE BID = ?";
	private final String DELETE = "DELETE FROM BOARD WHERE BID = ?";

	public boolean insert(BoardDTO bDTO) {	//	글 생성.

		int rs = jdbcTemplate.update(INSERT, bDTO.getMid(), bDTO.getContent());

		return rs > 0 ? true : false;	//	삼항 연산식으로 표현 하기.
	}

	public List<BoardDTO> selectAll(BoardDTO bDTO) { 

		if(bDTO.getSk().equals("BOARD")) {	//	글 전체 목록 출력.

			return jdbcTemplate.query(SELECTALL_BOARD, new BeanPropertyRowMapper<BoardDTO>(BoardDTO.class));
		}
		else if(bDTO.getSk().equals("MYPAGE")) {	//	특정 회원이 작성한 글 전체 목록 출력.

			Object[] args = { bDTO.getMid() };

			return jdbcTemplate.query(SELECTALL_MYPAGE, args, new BeanPropertyRowMapper<BoardDTO>(BoardDTO.class));
		}
		return null;
	}

	public BoardDTO selectOne(BoardDTO bDTO) {	//	글 한개 출력.

		Object[] args = { bDTO.getBid() };

		try {
			return jdbcTemplate.queryForObject(SELECTONE, args, new BeanPropertyRowMapper<BoardDTO>(BoardDTO.class));
			
		} catch (Exception e) {	//	EmptyResultDataAccessException 예외 캐치.
			e.printStackTrace();
			return null;
		}
	}

	public boolean update(BoardDTO bDTO) {	//	글 내용 수정.

		int rs = jdbcTemplate.update(UPDATE, bDTO.getContent(), bDTO.getBid());

		return rs > 0 ? true : false;	//	삼항 연산식으로 표현 하기.
	}

	public boolean delete(BoardDTO bDTO) {	//	글 삭제.

		int rs = jdbcTemplate.update(DELETE, bDTO.getBid());

		return rs > 0 ? true : false;	//	삼항 연산식으로 표현 하기.
	}

}	//	BoardDAO

 
[ InterfaceBoardService ]
 

package com.hwan.app.service;

import java.util.List;

import com.hwan.app.model.BoardDTO;

public interface InterfaceBoardService {
	
	List<BoardDTO> selectAll(BoardDTO boardDTO);
	BoardDTO selectOne(BoardDTO boardDTO);
	boolean insert(BoardDTO boardDTO);
	boolean update(BoardDTO boardDTO);
	boolean delete(BoardDTO boardDTO);

}	//	InterfaceBoardService

 
[ BoardService ]
 

package com.hwan.app.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.hwan.app.model.BoardDTO;
import com.hwan.app.model.InterfaceBoardDAO;

@Service
public class BoardService implements InterfaceBoardService {
	
	@Autowired
	private InterfaceBoardDAO interfaceBoardDAO;

	@Override
	public List<BoardDTO> selectAll(BoardDTO boardDTO) {
		return interfaceBoardDAO.selectAll(boardDTO);
	}

	@Override
	public BoardDTO selectOne(BoardDTO boardDTO) {
		return interfaceBoardDAO.selectOne(boardDTO);
	}

	@Override
	public boolean insert(BoardDTO boardDTO) {
		return interfaceBoardDAO.insert(boardDTO);
	}

	@Override
	public boolean update(BoardDTO boardDTO) {
		return interfaceBoardDAO.update(boardDTO);
	}

	@Override
	public boolean delete(BoardDTO boardDTO) {
		return interfaceBoardDAO.delete(boardDTO);
	}

}	//	BoardService
반응형