포스트

퍼사드(Facade) 패턴: 서비스 순환 참조와 JPA 데이터 정합성 해결

퍼사드(Facade) 패턴: 서비스 순환 참조와 JPA 데이터 정합성 해결

“개발자의 진짜 실력은 ‘가장 완벽한 구조’를 만드는 것이 아니라 현재 상황에서 ‘가장 타당한 최선의 선택’을 내리는 데 있습니다.”

프로젝트 규모가 확장됨에 따라 발생하는 서비스 간 순환 참조(Circular Dependency) 문제를 퍼사드 패턴을 통해 어떻게 해결했는지 그리고 왜 이 방식이 가장 효과적인 전략인지 정리해 보았습니다.


1. 퍼사드(Facade) 패턴이란?

퍼사드(Facade)는 프랑스어로 건물의 ‘정면’을 뜻합니다. 복잡한 내부 구조를 가진 건물이라도 밖에서는 깔끔한 정면만 보이듯 여러 서브시스템의 복잡한 로직을 하나의 단순한 인터페이스 뒤로 숨기는 패턴입니다.

  • 핵심 역할: 여러 서비스(Service)가 얽혀 있는 복잡한 비즈니스 로직을 하나의 창구(Facade)로 통합하여 클라이언트(Controller)에게 제공합니다.
  • 추상화: 클라이언트는 내부에서 어떤 서비스가 호출되는지 알 필요 없이 퍼사드가 제공하는 메서드 하나만 호출하면 됩니다.

2. 왜 퍼사드 패턴인가?

단순히 “퍼사드가 좋다”가 아니라 왜 다른 방식보다 우월한지를 기술적 관점에서 분석했습니다.

해결 방안트랜잭션 관리결합도비용주요 특징
컨트롤러 조합불가높음낮음여러 서비스 호출 시 중간 실패 시 롤백 불가 (데이터 불일치 위험)
Facade 서비스가능낮음보통@Transactional로 전체 로직의 원자성(Atomicity) 보장 가능
도메인 모델 개선가능매우 낮음매우 높음엔티티 간 연관관계 수정이 동반되어 개발 중 설계 난이도가 극도로 높음
  • 결론: 퍼사드는 ‘트랜잭션의 원자성’과 ‘설계의 유연성’ 사이에서 가장 균형 잡힌 타협점입니다.

3. 트랜잭션 및 성능 최적화

퍼사드를 통해 여러 서비스를 조율할 때 JPA의 핵심 메커니즘을 이해하면 더 견고한 설계를 할 수 있습니다.

① 트랜잭션과 쓰기 지연 (Identity 전략)

퍼사드 메서드에 @Transactional을 걸면 전체 비즈니스 로직이 하나의 영속성 컨텍스트를 공유합니다.

  • 주의점: @Id 전략이 IDENTITY인 경우, save() 시점에 즉시 SQL이 실행되어 쓰기 지연이 제한됩니다. 퍼사드 설계 시 벌크 연산 등을 조합한다면 이러한 ID 생성 전략이 쿼리 순서에 미치는 영향을 고려해야 합니다.

② 프록시와 지연 로딩 (Proxy Initialization)

퍼사드는 여러 서비스의 결과를 조합합니다. 이때 FetchType.LAZY로 설정된 필드들은 프록시 상태입니다.

  • 퍼사드가 DTO를 조합하는 과정에서 프록시 데이터를 조회할 때 실제 객체처럼 동작하게 하는 Proxy Initialization 과정이 일어납니다. 퍼사드는 이 과정을 깔끔하게 캡슐화하여 클라이언트(컨트롤러)가 데이터 로딩 시점을 고민하지 않게 합니다.

③ 프로젝션을 통한 성능 개선

퍼사드에서 ScheduleComment를 조합할 때 엔티티 전체를 가져오는 대신 필요한 데이터만 프로젝션(DTO/Interface)으로 조회하면 네트워크 부하와 메모리 사용량을 획기적으로 줄일 수 있습니다.


4. 퍼사드 클래스가 비대해진다면?”

현재는 UserActionFacade라는 이름으로 사용자 중심의 행위를 관리하고 있습니다. 하지만 기능이 늘어남에 따라 이 클래스가 비대해지는 것 은 경계해야 할 부분입니다. 이를 방지하기 위한 대응 전략은 다음과 같습니다.

  1. 단계적 분리: 현재는 통합 퍼사드로 관리하여 전체 비즈니스 흐름을 한눈에 파악하지만 특정 도메인의 비즈니스 로직이 임계점을 넘는 순간 ScheduleFacade 등으로 책임 분리를 진행할 예정입니다.
  2. 조율자의 역할 유지: 퍼사드 내부에는 도메인 로직을 직접 구현하지 않습니다. 오직 Service 계층의 메서드를 호출하고 조합하는 ‘오케스트레이션(Orchestration)’ 역할에만 충실하게 유지하여 퍼사드 자체가 너무 복잡해지지 않도록 관리합니다.
  3. 문서화를 통한 가이드: 현재 작성된 JavaDoc은 다른 팀원들이 이 클래스의 역할을 명확히 이해하게 도와줍니다. 이후 분리가 필요할 때도 이 문서들이 리팩토링의 나침반이 되어줄 것입니다.

5. 하이브리드 방식과 오버 엔지니어링 경계

이론적으로는 모든 서비스 호출을 퍼사드로 통일하는 것이 좋지만 지금은 비용 대비 효율을 따져야 합니다.

  • 하이브리드 방식 도입: * 단순한 저장/삭제처럼 추가 로직이 없는 경우는 컨트롤러에서 서비스를 직접 호출합니다.
    • 순환 참조가 발생하거나 3개 이상의 서비스 조율이 필요할 때만 퍼사드를 도입합니다.
  • 이유: 아무 로직도 없이 호출만 전달하는 ‘깡통 퍼사드’를 만드는 것은 오버 엔지니어링이며 관리 포인트만 늘릴 뿐입니다.

오늘의 회고: 최선의 선택이 곧 실력이다

예전의 저였다면 무조건 ‘교과서적으로 완벽한 구조’를 만들기 위해 모든 곳에 퍼사드를 적용했을지도 모릅니다. 하지만 지금은 기술적 타당성과 구현 효율 사이의 균형을 맞추는 것이 더 중요하다는 것을 알고있어 빠른 수용이 가능했습니다. 지금 당장 모든 코드를 퍼사드 뒤로 숨기지 않은 이유는 현재의 문제를 가장 깔끔하게 해결하기 위한 전략적 선택입니다. 나중에 프로젝트가 커져 컨트롤러의 의존성이 늘어날 때 그때 퍼사드로 완전히 전환해도 늦지 않습니다. 이러한 선택의 근거를 문서화하고 대비책을 세우는 과정에서 백엔드 개발자로서 한 단계 더 성장했음을 느낍니다.


References

  • GoF의 디자인 패턴 - 퍼사드 패턴의 정의와 구조
  • 헤드 퍼스트 디자인 패턴 - 인터페이스 단순화와 캡슐화
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.