문제상황
- 게시판 목록 조회 API
- 게시판 유형 별로 다른 테이블을 조회해야 했다.
- API를 각각 분리하거나, 하나의 게시판 조회 API에서 Type에 따라 if/else 분기에 따라 다른 요소를 조회해야 했다.
- 예) 일반게시글, 비밀게시글, 인기글A, 인기글B
- 기존 레거시 시스템에서는 이러한 유형의 데이터들을 if/else로 Service 레이어에서 각각 다른 Repository (여기선 Mapper)를 사용해서 로직을 처리하였다.
@Service
@RequiredArgsConstructor
public class CommunityBoardService {
private final CommunityContentsFactoryService communityContentsFactoryService;
@Transactional(readOnly = true)
public List<CommunityBoardContentsDTO> selectCommunityBoardContents() {
if (isPersonalBoard) {
Long optionalUserId = tokenProvider.getUserIdByToken(optionalToken);
this.validateAccessPersonalContents(optionalUserId, boardSeq);
results = communityPersonalBoardSlaveMapper.selectPersonalBoardContents(boardSeq, pagingForm);
} else {
ContentsDTO contentsDTO = new ContentsDTO(userId, boardSeq, boardCategoryCd, pagingForm);
results = communitySlaveMapper.selectCommunityBoardContents();
} ....
for (CommunityBoardContentsDTO contents : results) {
contents.setContentsThumbnail();
}
return results;
}
}
- 이는 Repository 계층과의 결합도가 높은 상태로, 매번 다른 유형의 게시판이 추가될 때마다 Service 계층까지 수정해야 한다는 문제가 발생되었다.
해결방안
팩토리메서드 패턴은 아래와 같은 정의, 장점, 단점, 쓰이는 용례가 존재한다.
- 정의
- 팩토리 메서드 패턴은 객체 생성을 처리하기 위한 디자인 패턴 중 하나로, 객체 생성을 서브 클래스에 위임하여 객체를 생성하는 방식으로 동작합한다. 이를 통해, 클라이언트 코드에서는 구체적인 클래스의 인스턴스화를 처리하지 않고, 추상 클래스나 인터페이스를 통해 객체에 접근할 수 있다.
- 장점
- 유연성 : 팩토리 메서드 패턴을 사용하면 클라이언트 코드가 구체적인 객체를 인스턴스화하지 않고도 객체에 접근할 수 있다. 이는 클라이언트 코드가 변경되더라도 객체 생성 방식을 변경할 필요가 없도록 만들어준다.
- 확장성: 새로운 서브 클래스를 추가하여 객체를 생성하는 방식을 확장할 수 있다. 이는 새로운 요구사항이나 변경 사항에 대응하기 쉽게 만들어준다.
- 코드 중복 최소화: 객체 생성 코드가 클라이언트 코드에 중복되지 않도록 만들어준다. 이는 유지보수성을 향상시키고 코드를 더 간결하게 만든다.
- 단점
- 클래스 수 증가: 팩토리 메서드 패턴을 사용하면 추가적인 클래스가 생성되므로 클래스의 수가 늘어날 수 있다. 이는 프로젝트 규모가 커질수록 클래스의 관리가 어려워질 수 있다.
- 복잡성 증가: 팩토리 메서드 패턴을 사용하면 객체 생성 로직이 서브 클래스로 이동되기 때문에 클래스 간의 상호 의존성이 높아질 수 있다. 이는 코드를 이해하기 어렵게 만들 수 있다.
- 어느 상황에 쓰면 좋을까
- 객체 생성 방식이 변할 가능성이 있거나 여러 구현체 중에서 선택해야 하는 경우에 팩토리 메서드 패턴을 사용하는 것이 좋다.
- 객체 생성 과정이 복잡하거나 객체 간의 관계가 복잡한 경우에도 팩토리 메서드 패턴이 유용할 수 있다.
- 실제코드
@Service
@RequiredArgsConstructor
public class CommunityContentsFactoryService {
private final CommunitySlaveMapper communitySlaveMapper;
private final BoardSlaveMapper boardSlaveMapper;
public List<CommunityBoardContentsDTO> findContents(ContentsDTO contentsDTO) {
BoardDTO boardDTO = boardSlaveMapper.selectBoardById(contentsDTO.getBoardSeq());
// 런타임에서 의존성 주입! 게시판 타입에 따라 다르게 처리
CommunityContentsQueryService communityContentsQueryService = getCommunityContentsQueryService(boardDTO);
return communityContentsQueryService.findSelectedContents(contentsDTO);
}
private CommunityContentsQueryService getCommunityContentsQueryService(BoardDTO boardDTO) {
if (BoardSelectedType.TOP_CONTENTS.equals(boardDTO.getType())){
return new CommunityTopContentsService(communitySlaveMapper);
} else if (BoardSelectedType.BEST_EXPERIENCE.equals(boardDTO.getType())) {
return new CommunityBestExperienceService(communitySlaveMapper);
} else if (BoardSelectedType.PERSONAL_BOARD.equals(boardDTO.getType())) {
return new CommunityPersonalContentsService(communitySlaveMapper);
} else {
return new CommunityContentsService(communitySlaveMapper);
}
}
}
이후 (Service 추가해주면 끝)
@Service
@RequiredArgsConstructor
public class CommunityBoardService {
private final CommunityContentsFactoryService communityContentsFactoryService;
@Transactional(readOnly = true)
public List<CommunityBoardContentsDTO> selectCommunityBoardContents() {
long userId = SecurityUtil.getCurrentUserId();
List<CommunityBoardContentsDTO> results;
ContentsDTO contentsDTO = new ContentsDTO();
results = communityContentsFactoryService.findContents(contentsDTO);
return results;
}
}
인터페이스
public interface CommunityContentsQueryService {
List<CommunityBoardContentsDTO> findSelectedContents();
}
구현체 A) 이런 식으로 무한대로 추가 가능
@RequiredArgsConstructor
public class CommunityBestExperienceService implements CommunityContentsQueryService {
private final CommunitySlaveMapper communitySlaveMapper;
@Override
public List<CommunityBoardContentsDTO> findSelectedContents() {
return communitySlaveMapper.selectCommunityBoardBestExperienceContents();
}
}
- 적용 후 느낀 장점
- 조회해야 할 게시판 유형이 하나 더 늘어나더라도, 인터페이스의 구현체를 하나 더 추가해주면 됨. 그러면 Service 레이어 코드 수정 없이도, BoardType에 따라 원하는 게시판(테이블)을 조회할 수 있도록 만들어 줄 수 있다.
- 보기 싫은 if / else 안봐도 됨! 코드가 아주 깔끔!
반응형
'Framework > Spring' 카테고리의 다른 글
[Spring] IoC와 DI란 무엇인가? (0) | 2024.09.20 |
---|---|
[Test] Spring Boot 환경에서의 유닛테스트와 통합테스트, 슬라이스 테스트 (1) | 2024.09.19 |
DispatcherServlet를 중심으로 본 스프링 MVC 동작 원리 (0) | 2024.01.31 |
[Spring] 모니터링을 위한 스프링 액추에이터 알아보기 (0) | 2024.01.18 |
[Spring] JPA Auditing을 활용하여 생성/수정시간 자동화하는 방법 (0) | 2023.06.22 |
댓글