Today I Run

Facade Pattern 이해하고 서비스에 적용하기

조핑구 2024. 7. 3. 17:25

사건의 발단

" service 끼리 참조하지 않도록 파사드 패턴을 사용해서 개발하자 " 프로젝트를 시작하며 선배가 했던 말이다. 정보처리기사를 공부하며 디자인패턴을 개념적으로는 알고 있었지만 실무에 적용해볼 생각은 해본적이 없다! 그래서 파사드 패턴을 사용하라는 말이 내게는 매우 추상적으로 들렸고, 완벽히 적용하기 위해 공부를 시작했다

service 끼리 참조하면 무슨 일이 벌어지나?

순환참조는 A클래스가 B를 참조하고, B클래스가 A를 참조하는 것이다. 서로의 Bean에 등록되는 것이기 때문에 lazy 순환참조의 고리를 끊도록 재설계하는 것이 최선이다.

예시로 살펴보자면

Member Entity, Team Entity가 있고, 각각의 CRUD를 구현한 Service 가 있다고 가정해보자. 그런데 Member를 등록할 때 Team을 지정해줘야 한다고 해보자. 그렇다면 MemberService.createMember() 에서 TeamService를 불러 지정하고자 팀이 있는지( isPresent() )를 검사하는 메소드를 호출해야 한다. 이런 경우에 MemberService에서 TeamService를 참조하고 있다.

Team에서 팀장을 지정할 때, Member 생성일을 기준으로 가장 오래된 사람을 선정하는 서비스를 만든다면 TeamService.setLeader()에서 MemberService.getTeamMembersCreatedDate()와 같은 메소드를 호출해야한다. 이 경우에는 TeamService에서 MemberService를 참조하고 있다. 이 두가지 서비스만으로도 순환참조가 발생한다. MemberService를 부르면 TeamService가 불러지고 또 TeamService안에 있는 MemberService가 호출되는 무한호출의 고리가 완성된다.

 순환참조하면 뭐 어때😑

사실 우리 팀 코드는 이런 순환참조에 빠져있었고, 별 문제 없었다(없는 것 처럼 보였다!) 우리 팀의 개발물은 분량이 방대했고 서로 서비스끼리 얽히고 설킬 일이 많다. 초반에 간단한 서비스를 구현할때는 문제가 되지 않았던 부분들이 비즈니스 로직을 짤때는 걸림돌이 되는 경우가 많았다. 그리고 디버깅의 어려움이 있다는 점이 가장 문제였다. 서비스끼리 얽혀있는 경우에서 어느 부분이 에러의 원인인지 정확히 파악하는데 시간이 많이 걸렸다. 추가적으로 Memory Leak의 이슈도 있을 수 있다.

Facade야 도와줘!

이제 파사드 패턴에 대해 알아보자. Facade는 '건물의 정면'이라는 의미를 가지고 있고, 건물 안에 무엇이 있든 정면만을 본다는 뜻을 품고 있다. 파사드는 많은 움직이는 부분을 포함하는 복잡한 하위 시스템에 대한 간단한 인터페이스를 제공하는 클래스라고 할 수 있다. 여기서 인터페이스는 꼭 'interface'로만 구현된다는 의미가 아니라 접점이라는 뜻에 더 무게를 두고 있다. 

예를 들어보자. 컴퓨터와 전등과 TV은 각각 켜고 끌 수 있다. 하지만 이것들을 한 번에 켜고 끄고 싶다면 컴퓨터 켜기 메소드와 전등 켜기 메소드와 TV 켜기 메소드를 각각 부르지 않고도 facade 를 구현해 슈퍼켜기 메소드를 만들 수 있다.

 

이제 facaed parttern을 사용해 Member와 Team의 순환참조의 고리를 끊어낼 수 있다. 우리 팀은 service 별로 facade를 추가하기로 했다. 이제 서비스는 MemberFacadeService와 TeamFacadeService, MemberService와 TeamService가 존재한

다. 이런 구조에서 팀원을 생성하면서 팀에 배정하는 서비스를 구현하려면 다음과 같은 flow를 가져야 한다.

기존의 방식대로라면 MemberService에서 TeamService를 참조해야하지만 MemberFacade에서 MemberService 와 TeamService를 각각 가져다가 사용한다.

TeamFacadeService.setLeader()도 비슷하게 동작한다. TeamFacadeService에서  MemberService.getTeamMembersCreatedDate() 와 TeamService.updateTeamInfo()를 호출해 팀장을 교체할 수 있다. 이때 MemberService와 TeamService는 Facade에서만 참조되고 서로는 접근하지 않는다. FacadeService가 다른 FacadeService를 부르지도 않기 때문에 순환참조는 발생하지 않는다. 

적용 후기😎

파사드 패턴을 통해 Service layer를 나눠보니 가장 체감된 것은 모듈화에 도움이 된다는 것이었다. 원래같으면 한 메소드 안에서 여러가지 일을 처리했을텐데 레이어를 나눠버리다보니 이게 Member만의 일인지, Facade에서 이루어져야하는 일인지를 구별해야했고 책임이나 동작에 따라 메소드를 구분하고 위치시키는 일을 더 고민하게 되었다. 물론 순환참조도 끊었다! 앞으로 복잡한 서비스를 효율적으로 관리하기위한 여러가지 방법들을 더 살펴봐야겠다는 생각이 들었다.