본문 바로가기
SpringBoot/구현 고민들

[Spring] Facade Pattern 적용 계기 및 이유

by rla124 2024. 8. 7.

해당 PR(퍼사트 패턴 적용 시도한 링크)을 통해서 Facade Pattern을 처음 적용해보게 되었다. 이 디자인 패턴을 도입하기까지의 고민 과정을 기록하고자 한다.

 

도입 배경

내가 퍼사드 패턴을 도입하려는 프로젝트를 간략하게 소개하고자 한다. 그동안 IT 분야로 오게 되면서 컴퓨터공학과 학생으로서 현재 다니고 있는 대학교 재학생들을 위한 서비스를 졸업하기 전에 한 번은 꼭 개발해서 학생들이 이 서비스를 이용하는 모습을 정말 정말 보고 싶었다. 그러기까지 서로 다른 주제의 프로젝트들을 진행하면서 시도하였으나 출시 및 운영까지 이어지기가 쉽지 않았는데 이 프로젝트(출시 링크)에서 소망을 이룰 수 있게 되어서 이거를 많이 애정한다. 주제가 재학생들을 위한 네트워킹 서비스이며 그 중 선후배, 동기 매칭을 도와주는 "버디" 도메인에 이 패턴을 도입할 필요성을 느꼈는데 그 이유는 아래와 같다.

 

1. 컨트롤러가 여러 서비스 레이어의 클래스와 강하게 결합되어있었다.

버디 기능은 모두 하나의 BuddyController에서 관리한다. 하지만 이 컨트롤러는 BuddyService(버디 등록, 취소, 최근 매칭 상태, 이용자 수 반환, 매칭 이후 상대 정보 반환 등)와 BuddyMatchingService(버디 매칭 수락/거절 관리) 여러 서비스 코드와 강하게 결합하고 있다

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/buddy")
public class BuddyController {
	private final BuddyService buddyService;
	private final BuddyMatchingService buddyMatchingService;

	@Operation(summary = "버디등록", description = "유저가 버디에 등록")
	@PostMapping("/register")
	public void registerBuddy(@Valid @RequestBody BuddyRegistrationRequest request) {
		buddyService.registerBuddy(request);
	}
    
    // 이하 엔드포인트를 위한 컨트롤러 내 정의된 함수는 생략

 

2. 여러 서비스 레이어들간 의존 관계가 높다.

Buddy 기능 구현을 위해 우리 팀이 개발한 서비스 클래스의 수는 3개이다. 그 중 BuddyService와 위에서 언급하지 않은 유저가 원하는 버디 상대를 실제로 매칭해주는 MatchingService기능이 아래와 같이 엮어져 있다.

 

아래는 BuddyService의 여러 함수 중 버디 등록을 위한 메소드이다. 퍼사드 패턴 적용 전에는 반환형이 void였다!

public void registerBuddy(BuddyRegistrationRequest request) {
    final Member member = memberUtil.getCurrentMember();

    validatePossibleRegistration(member.getId());

    Buddy buddy = Buddy.create(request, member);
    buddyRepository.save(buddy);

    matchingService.matchBuddyWhenRegister(buddy);
}

 

이처럼 BuddyService와 MatchingService가 종속적으로 의존되어있었다. (두 서비스 메소드가 중첩되어있으므로)

 

따라서 컨트롤러와 여러 서비스 레이어 간의 결합력을 낮추고 서비스들을 독립적으로 관리하기 위해 Facade Pattern을 도입했다.

 

Facade Pattern 적용 아이디어 

가장 먼저 든 생각은 버디 기능 구현을 위한 서비스 코드와 별도로 Facade 클래스를 도입하여 퍼사드 클래스 내부에서 이 세 개의 서비스 클래스를 관리해야겠다는 생각이었다. 그러면 아래와 같이 버디 컨트롤러는 facade class에만 결합하게 된다. 이 부분이 퍼사드 패턴의 이점이다. 퍼사드는 내부 로직을 숨기고 사용자에게는 간단하고 명확한 인터페이스만 제공한다. 이로 인해 시스템의 유지보수와 확장성이 향상되고 사용자 경험이 개선될 수 있다.

 

이전 버디 컨트롤러와의 차이점은 퍼사드 클래스를 도입하여 컨트롤러는 퍼사드 클래스에만 의존하고, 퍼사드 클래스와 기존 서비스 레이어 로직을 상하관계로 관리(아이디어)하는 것이고, 이를 통해 기존 서비스 메소드를 호출하는 것이 아니라 퍼사드 클래스의 메소드를 호출하면 위에서 언급한 문제를 해결할 수 있다고 생각했다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/buddy")
public class BuddyController {
	private final BuddyManagementFacade buddyManagementFacade;

	@Operation(summary = "버디등록", description = "유저가 버디에 등록")
	@PostMapping("/register")
	public void registerBuddy(@Valid @RequestBody BuddyRegistrationRequest request) {
		buddyManagementFacade.registerBuddy(request);
	}
    
    // 이하 동일하게 버디 관련 매소드 생략

 

 

아래처럼 이제 새롭게 작성한 아래 퍼사드 클래스에서 상하관계로 버디 기능 구현을 위한 세 개의 서비스 레이어를 관리한다. 

@Service
@RequiredArgsConstructor
@Transactional
public class BuddyManagementFacade {

	private final BuddyService buddyService;
	private final BuddyMatchingService buddyMatchingService;
	private final MatchingService matchingService;

	public void registerBuddy(BuddyRegistrationRequest request) {
		Buddy newBuddy = buddyService.registerBuddy(request);
		matchingService.matchBuddyWhenRegister(newBuddy);
	}
    
    // 이하 동일한 이유로 메소드 생략

 

 

BuddyService 클래스에 있던 registerBuddy 함수의 경우 처음 코드에서는 두 개의 서비스 레이어가 의존하고 있는 상황이었지만  아래 코드 registerBuddy를 void 반환에서 Buddy를 반환하도록 수정하고 넘겨받은 newBuddy 인자를 matchingService의 matchBuddyWhenRegister에 넘겨준다면 기존 BuddyService의 registerBuddy 메소드 내부에서 matchingService.matchBuddyWhenRegister가 호출되지 않도록 할 수 있기 때문에 두 서비스간의 의존성을 제거할 수 있지 않을까라는 생각 기반하여 스스로 고민한 코드이고 실제로 서비스 레이어들 간 의존 관계를 없앨 수 있었다. 

public Buddy registerBuddy(BuddyRegistrationRequest request) {
    final Member member = memberUtil.getCurrentMember();

    validatePossibleRegistration(member.getId());

    Buddy buddy = Buddy.create(request, member);
    return buddyRepository.save(buddy);
}

 


퍼사드 패턴을 적용하면 개발자는 복잡한 서브 시스템의 내부 로직을 몰라도 되고 단순화된 퍼사드를 통해서 시스템과 상호작용 할 수 있기 때문에 개발 과정이 단순해질 수 있다. 또한 코드가 서브 시스템의 구체적인 구현에 의존하지 않고 단지 퍼사드에만 의존하므로 위에서 계속 언급했던 "시스템의 결합도를 낮추어" 유지보수와 확장성을 높일 수 있다. 

 

하지만 퍼사드가 너무 많은 책임과 복잡성을 가질 수 있다는 우려, 이제는 퍼사드가 서브 시스템에 의존하게 된다는 점(서브 시스템, 여기서는 서비스 레이어 코드들이 크게 변경되면 퍼사드 역시 이에 맞춰 변경되어야 하므로)에서 유지 보수의 복잡성을 반대로 높일 수 있다. 

 

이러한 양면성을 잘 알아두자!