본문 바로가기
JAVA/Spring & Java 학습 기록

퍼사드 패턴을 도입해 도메인 간 의존성을 줄이다.

by 구본식 2023. 8. 13.

Volunteer 프로젝트 관련 포스터 입니다.

 

GitHub - project-Volunteer/BackEnd: 사이드 프로젝트 - 봉사 활동 팀 매칭 서비스(BackEnd)👊🏼

사이드 프로젝트 - 봉사 활동 팀 매칭 서비스(BackEnd)👊🏼. Contribute to project-Volunteer/BackEnd development by creating an account on GitHub.

github.com

프로젝트를 진행하면서 필요한 도메인이 늘어나고, Service layer, Repository layer 간 의존성이 증가하는 상황이 빈번히 발생했습니다. 이러한 의존성을 줄이고 응집도 높은 클래스를 만들기 위해 퍼사드 디자인 패턴을 도입한 경험을 소개해드리고자 합니다.


레거시 상황

아래는 프로젝트의 일부 클래스 다이어그램입니다.

프로젝트 내에서 대부분의 Service 로직에서 다양한 정보가 필요하기 때문에, Repository들을 의존하는 상황이 많았습니다. 클래스 간 의존성이 강해지고 도메인이 추가되면서 조금씩 스파게티 코드가 되는 느낌을 받았고, Service에서 다른 Service를 의존하면서 순환 참조도 발생할 수 있는 문제가 있었습니다.

또한, 특정 클래스의 테스트 코드를 작성할때도 불필요한 의존성을 추가해줘야 했습니다. 

 

이에 그치지 않고, 각 Service에서 다른 도메인의 Repository를 주입받아서 해당 도메인을 조회하는 로직이 매 Service 마다 반복적으로 사용되었습니다. 이는 특정 Service의 핵심 비지니스 로직이 아니였고, 단일 책임 원칙에 어긋난다 생각을 했습니다.

하지만, 작은 프로젝트를 진행한다면 굳이 다른 Service를 통하지 않고 다른 Repository(DAO)를 의존하여 사용하는 것이 더 편하고 용이할 수 있다고 생각합니다. 각각의 Service 기능의 단위가 크지 않을 것이기 때문입니다.

하지만 프로젝트의 도메인이 증가하고, 어플리케이션의 규모가 커지게 되면, 그만큼 기능 구현에 다양한 Repository가 요구되기 때문에 Service에 많은 책임과 역할이 부여되고 중복 코드가 발생할 수 있으므로 Service의 역할이 모호해질수 있다고 생각합니다. 그렇기 때문에 상황에 맞게 선택하는 것이 중요하다고 생각합니다.

퍼사드 패턴까지 도입해야할 프로젝트인지는 확신하지 못하지만, 사용해보고 싶어서 저는 도입해봤습니다..ㅎㅎ

 

하나의 Controller에서 여러 Service를 의존하는 상황에서 봉사 모집글 정보 등록, 반복 주기 등록, 스케줄 등록, 이미지 등록 등의 작업이 수행되는 봉사 모집글 등록 API에서 문제점이 하나 더 있었습니다. 

컨트롤러가 일부 비지니스 로직을 가짐으로써 트랜잭션을 관리하는 Serivce layer가 여러개 사용되어, 트랜잭션 원자성이 모호해지고, 마찬가지로 컨트롤러 계층의 역할이 모호해진다 생각했습니다.

(하나의 트랜잭션에서 처리되어야 한다.!)


리팩토링

앞서 레거시 상황에서의 문제점을 해결하기 위해 퍼사드 디자인 패턴을 도입해보자.

Facade 패턴이 뭘까?
퍼사드 패턴은 서브 시스템에 있는 인터페이스들에 대한 통합된 인터페이스를 제공합니다. 다양한 서브 시스템을 더 쉽게 사용할 수 있도록 만드는 더 높은 수준의 인터페이스를 말합니다.
즉, 메인 시스템과 서브 시스템의 중간에 새로운 인터페이스 계층을 만드는 것입니다. 이러한 구조는 시스템의 강한 결합 구조를 해결하며 의존성을 해결할 수 있습니다.

Facade 특징
- 복잡성 해결
- 서브 시스템 보호: 서브 시스템을 직접 노출시키지 않기 때문에 잘못된 사용을 방지할 수 있고, 서브 시스템 업그레이드에 자유롭다.
- 결함도 감소
- 공개 인터페이스: 퍼사드 인터페이스로 서브 시스템들을 캡슐화 시키므로, 공개할 부분과 공개하지 않을 부분을 결정할 수 있다.
- 확장성 유리 

Adapter 패턴과 차이
어댑터 패턴은 한 클래스의 인터페이스를 클라이언트가 사용하고자 하는 다른 인터페이스로 변환시켜주는 것이 목적입니다. 어댑터를 사용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스를 연결해서 사용할 수 있습니다.

 

Facade 패턴을 도입한 후의 일부 클래스 다이어그램이다.

 

일부 코드

각 도메인의 Service와 Repository(DAO)가 일대일 매핑을 이루게 함으로써, 각 Repository와 관련된 작업만 수행하도록 역할을 명확히 구분했다. 필요한 Repository에 대한 의존성만 Service가 가지고 있기 때문에, 결합도를 낮아지고 응집도가 높아진다.!

(참고로 프로젝트내에는 뷰에 맞춘 DTO를 리턴이 필요한 도메인에 DTO 전용 Service들이 존재하는데, 이 Service까지 일대일 매핑을 만드는 것은 과도한 분리라 생각해 DTO Service에는 최소 Repository를 의존하도록 했습니다.)

 

일부 코드

이후 매퍼 클래스로 퍼사드 클래스를 만들어 필요한 Service를 주입받아 필요한 퍼사드 메서드에 사용합니다.

이전 레거시 상황에서 봉사 모집글 저장시 필요한 작업들을 하나의 트랜잭션에서 처리되어야 할 작업들을 묶을 수 있었고,

추가로 봉사 모집글 삭제 시 봉사 모집글 정보, 이미지, 공지사항, 일정 등 봉사 모집글과 관련된 정보 삭제를 퍼사드 매퍼 메서드로 하나의 트랜잭션에서 처리할 수 있게 되었습니다.

 

결과적으로, 각 Sevice가 하나의 역할을 담당할수 있 수 있었고, 여러 Service에서 반복적으로 사용되던 엔티티 검증 메서드도 관련된 도메인서비스 메서드에 정의해 사용하므로써 복을 줄이고 재사용성을 높일 수 있었다.

또한, 각 서비스 레이어의 불필요한 Repository(DAO) 의존성이 없어져, Service layer 단위 테스트 작성에도 용의하게 됩니다.

 

하지만, 퍼사드 패턴도 여전히 단점이 존재하는 거 같습니다.

Service layer의 의존성은 줄어들었지만, 퍼사드 매퍼 클래스가 많은 Service layer에 의존성을 가져 비만 클래스가 될 우려가 있고, 퍼사드 매퍼 클래스가 복잡해진다는 문제가 발생합니다.

퍼사드 매퍼 클래스가 하나가 아니여도 되기 때문에, Additional 퍼사드 매퍼 클래스를 통해 복잡도를 낮출 수 있습니다.


참고 자료