비즈니스 컴포넌트 개발에서 가장 중요한 두 가지 원칙은 낮은 결합도와 높은 응집도를 유지하는 것입니다.
스프링의 의존성 주입 (Dependency Injection)을 이용하면 비즈니스 컴포넌트를 구성하는 객체들의 결합도를 떨어트릴 수 있어서 의존관계를 쉽게 변경할 수 있습니다.
스프링의 IoC가 결합도와 관련된 기능이라면 AOP(Aspect Oriented Programming)는 응집도와 관련된 기능입니다.
businessMethod() {
Logging....
BusinessLogic(3~5라인)
Exception Handle...
Transaction Handle...
Logging....
}
엔터프라이즈 어플리케이션 메서드들은 대부분 위와 같이 복잡한 코드들로 구성되어있고
핵심 비즈니스 로직은 몇줄 안 되고, 주로 로깅이나 예외, 트랜잭션 처리같은 부가적인 코드가 대부분입니다.
코드로 인해 비즈니스 메서드의 복잡도는 증가합니다. 그렇다고 코드들을 삭제하거나 소홀히 해서는 안되는데 이 부가적인 코드 역시 엔터프라이즈 어플리케이션 비즈니스 로직만큼이나 중요한 기능들이 있기 때문입니다.
중요한점은 이런 부가적인 코드들을 매번 반복해야 한다는 것입니다.
따라서 새로운 메서드를 구현하는 가장 일반적인 방법은 기존에 잘 만들어진 메서드를 복사해서 구현하는 것이도
그렇게 되면 결국 비즈니스 메서드에 부가적인 코드들이 반복해서 등장합니다. 따라 코드 분석과 유지보수를 어렵게 만듭니다.
AOP는 이런 부가적인 공통 코드들을 효율적으로 관리하는데 주목합니다.
AOP를 이해하는 데에 가장 중요한 핵심 개념이 관심 분리(Separation of Concerns)입니다.
AOP에선 메서드 마다 공통으로 등장하는 로깅이나 예외, 트랙잭션 처리 같은 코드들을 횡단 관심(Crosscutting Concerns)이라고 합니다.
요청에 따라 실제로 수행되는 핵심 비즈니스 로직을 핵심 관심(Core Concerns)이라고 합니다.
만약 이 두 관심을 완벽하게 분리가 가능하면 우리가 구현하는 메서드에는 실제 비즈니스 로직만으로 구성할 수 있으므로 더욱 간결하고 응집도 높은 코드를 유지할 수 있습니다. 문제는 기본의 OOP(Object-Oriented Prigramming)언어에서는 횡단 관심에 해당하는 공통 코드를 완벽하게 독립적인 모듈로 분리해내기가 어렵다는 것입니다.
OOP 언어에서 완벽한 관심 분리가 어려운지 책으로보고 실습해보겠습니다.
모든 비즈니스 메서드가 실행되기 직전에 공통으로 처리할 로직을
LogAdvice 클래스에 pringtLog()메서드로 구현
package com.springbook.biz.common;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
@Aspect // Aspect = Pointcut + Advice
public class LogAdvice {
@Before("PointcutCommon.allPointcut()")
public void printLog(){
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
이렇게 구현된 LogAdvice 클래스의 printLog() 메서드를
BoardService 컴포넌트에서 사용할 수 있도록 BoardServiceImple 클래스를 수정합니다.
package com.springbook.biz.board.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springbook.biz.board.BoardService;
import com.springbook.biz.board.BoardVO;
import com.springbook.biz.common.LogAdvice;
@Service("boardService")
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
private LogAdvice log;
public BoardServiceImpl() {
log = new LogAdvice();
}
public void insertBoard(BoardVO vo) {
// if (vo.getSeq() == 0) {
// throw new IllegalArgumentException("0번 글은 등록할 수 없습니다.");
// }
log.printLog();
boardDAO.insertBoard(vo);
}
public void updateBoard(BoardVO vo) {
log.printLog();
boardDAO.updateBoard(vo);
}
public void deleteBoard(BoardVO vo) {
log.printLog();
boardDAO.deleteBoard(vo);
}
public BoardVO getBoard(BoardVO vo) {
log.printLog();
return boardDAO.getBoard(vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
log.printLog();
return boardDAO.getBoardList(vo);
}
}
BOardServiceImpl 객체가 생성될 때, 생성자에서 LogAdvice 객체도 같이 생성합니다. 그리고 각 비즈니스 메서드에서 비즈니스 로직을 수행하기 전에 LogAdvice 의 printLog() 메서드를 호출하기만 하면 됩니다.
이후에 공통 기능을 수정할 때는 LogAdvice 클래스의 printLog()메서드만 수정하면 되므로 관리가 편해졌다고 할 수 있습니다.
하지만 이렇게 작성된 프로그램은 BoardServiceImpl클래스와 LogAdvice객체가 소스코드에서 강하게 결합되어 있어서, LogAdvice클래스를 다른 클래스로 변경해야 하거나 공통 기능에 해당하는 printLog()메서드의 시그니처가 변경되는 상황에서는 유연하게 대처할 수 없다.기존에 사용하던 LogAdvice 클래스의 성능이 떨어져서 이를 대체할 Log4jAdvice 클래스를 만듭니다. 메서드 이름도 printLogging()으로 변경합니다.
package com.springbook.biz.common;
public class Log4jAdvice {
public void printLogging() {
System.out.println("[공통 로그-Log4j] 비즈니스 로직 수행 전 동작");
}
}
이제 BoardServiceImpl 클래싀 모든 메서드는 Log4jAdvice를 이용하도록 수정해야 합니다.
import com.springbook.biz.common.LogAdvice; ==>> import com.springbook.biz.common.Log4jAdvice;
private LogAdvice log; ==>> private Log4jAdvice log;
log.printLog(); ==>> log.printLoggingging();
package com.springbook.biz.board.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springbook.biz.board.BoardService;
import com.springbook.biz.board.BoardVO;
import com.springbook.biz.common.Log4jAdvice;
@Service("boardService")
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAO boardDAO;
private Log4jAdvice log;
public BoardServiceImpl() {
log = new Log4jAdvice();
}
public void insertBoard(BoardVO vo) {
// if (vo.getSeq() == 0) {
// throw new IllegalArgumentException("0번 글은 등록할 수 없습니다.");
// }
log.printLogging();
boardDAO.insertBoard(vo);
}
public void updateBoard(BoardVO vo) {
log.printLogging();
boardDAO.updateBoard(vo);
}
public void deleteBoard(BoardVO vo) {
log.printLogging();
boardDAO.deleteBoard(vo);
}
public BoardVO getBoard(BoardVO vo) {
log.printLogging();
return boardDAO.getBoard(vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
log.printLogging();
return boardDAO.getBoardList(vo);
}
}
결국 Advice 클래스가 LogAdvice 에서 Log4jAdvice로 바뀌는 순간 BoardServiceImpl 클래스의 생성자를수정해야 합니다.
그리고 printLog() 가 printLogging() 메서드로 변경되었으므로 printLog()를 호출했던 모든 메서드를 수정해야 합니다.
정리하면 OOP 처럼 모듈화가 뛰어난 언어를 사용하여 개발하더라도 공통 모듈에 해당하는 Advice 클래스 객체를 생성하거나 생성하고 공통 메서드를 호출하는 코드가 비즈니스 메서드에 있다면, 핵심 관심(BoardServiceImpl)과 횡돤 관심 (LogAdvice)를 완벽하게 분리할 수는 없습니다. 하지만 스프링의 AOP는 이런 OOP의 한계를 극복할 수 있도록 도와줍니다.
'Spring' 카테고리의 다른 글
어노테이션 기반 설정 (0) | 2022.08.05 |
---|---|
List 타입 매핑 (0) | 2022.08.05 |
log4j2 (0) | 2022.07.27 |
제어의 역행(역전) : IoC(DI) (0) | 2022.07.25 |
STS IoC컨테이너 (0) | 2022.07.25 |