Skip to main content

Service의 책임과 분리 가능한 데이터 접근 경계

개요: Service는 고정된 데이터 접근 계약을 제공합니다. 업무 Facade는 이 계약을 외부에 허용된 접근 경로로 제공합니다. 필요한 경우에만 유스케이스로 조합합니다. 조회와 Command는 CQRS의 의도에 따라 구현과 저장소를 분리할 수 있도록 구성합니다.

적용 상태​

공통 구현 기준으로 채택되었습니다. Backend Knowledge의 Controller, Facade, 테스트 구조와 함께 현재 기준입니다. 테이블 Service의 코드 모양은 Spring 템플릿을 따릅니다.

설계 의도​

데이터 접근과 유스케이스 정책을 분리합니다. 같은 데이터 접근 기능을 여러 업무에서 재사용하고, 업무가 추가될 때마다 Service의 책임이 확장되는 것을 방지합니다.

Service는 정해진 조회, 저장, 수정 등의 계약을 제공합니다. 그 계약 안에 자기 데이터의 업무 규칙을 둡니다. 업무 Facade는 필요한 Service와 Port를 조합하여 실행 순서, 화면 조합, 다른 모듈의 실패 정책을 결정합니다.

Service에 남기는 업무 규칙:

  • 그 데이터의 존재 여부와 없을 때의 오류
  • 그 데이터 자신의 상태 전이와 삭제 의미
  • 그 데이터 집합 안의 저장 제약과 정합성
  • 그 Service의 엔티티와 응답 DTO 사이 변환

Facade가 조율하는 정책:

  • 어떤 Service와 Port를 어떤 순서로 호출하는지
  • 화면이나 유스케이스에 맞게 결과를 조합하고 노출 범위를 맞추는지
  • 다른 모듈의 조회가 실패했을 때 전체를 실패로 볼지, 일부만 반환할지
  • 한 프로세스 안에서 로컬 트랜잭션에 어떤 변경을 포함할지

상태 변경을 허용하는 권한은 그 데이터를 소유한 백엔드 업무 경계에 둡니다. 호출 경로가 GraphQL BFF로 바뀌어도 이 소유는 Service 쪽에 남습니다. 자세한 확장안은 Facade Design에 있습니다.

Service의 안정성​

이 구조에서 Service를 불변하게 유지한다는 것은 유스케이스마다 메서드와 정책을 임의로 추가하지 않는다는 의미입니다. 객체 상태의 불변성이나 부작용 없는 순수 함수를 의미하지는 않습니다.

계약은 메서드 이름뿐 아니라 입력, 반환값, 오류와 데이터 접근 의미를 포함합니다. 계약 변경이 필요한 경우에는 기존 유스케이스에 미치는 영향을 검토합니다.

책임과 경계​

구성요소책임
진입 AdapterREST 또는 gRPC 요청과 내부 입력 및 결과를 변환합니다.
업무 Facade유스케이스 실행, Service와 Port 조합, 정책 및 작업 경계를 결정합니다.
Service자기 데이터의 조회·저장과 그 데이터의 존재, 상태 전이, 정합성 규칙을 수행합니다.
Repository조회 또는 Command에 필요한 영속성 접근을 수행합니다.
외부 Port 구현체인증, 암호화, 외부 서비스 통신 등의 기술별 접근을 수행합니다.

Service에는 화면별 마스킹, ID에서 다른 모듈의 이름으로 바꾸는 조합, 외부 통신과 이벤트 발행 정책을 포함하지 않습니다. Service는 Facade를 참조하지 않습니다.

현재 구현에 남은 결합​

현재 Service는 공유 모듈에서 여러 애플리케이션이 직접 의존하는 형태입니다. 조회 메서드가 JPA Specification을 인자로 받아, 영속성 조건이 Service 밖으로 드러납니다. 화면용 Facade가 그 Specification을 만들어 넘기는 호출이 있습니다.

MSA로 가려면 공유 모듈 의존과 Service 밖으로 드러난 Specification을 먼저 끊어야 합니다. 프로세스를 나누는 것만으로 그 결합이 사라지지는 않습니다.

원격 서비스로 나누면 Facade Design의 로컬 트랜잭션을 그대로 유지할 수 없습니다. 조회 조합에는 Saga가 필요 없고, 여러 서비스의 변경을 완수하는 유스케이스만 Saga의 대상입니다.

업무 Facade는 Repository를 직접 사용하지 않고 Service의 계약을 통해 데이터에 접근합니다. Service 내부의 조회 조건과 영속성 구현을 업무 Facade로 이동하지 않습니다.

예시: 카드 정보와 사용자 이름 제공​

카드 Service는 카드 정보와 사용자 ID를 조회합니다. 사용자 이름을 추가하고 화면 노출 범위에 맞게 마스킹하는 작업은 카드 조회 Facade가 조율합니다.

카드 조회 Facade
├─ 카드 Service → 카드 정보와 사용자 ID 조회
├─ 사용자 조회 Port → 사용자 이름 조회
└─ 노출 정책 → 결과 조합 및 마스킹

카드 Service는 결과가 관리자 화면, 일반 사용자 화면 또는 외부 연동에 제공되는지 알지 않습니다. 같은 조회 계약을 각 유스케이스에서 재사용합니다.

사용자 조회가 로컬 호출에서 gRPC로 변경되어도 카드 Service의 데이터 접근 책임은 유지됩니다.

조회와 Command의 분리​

조회는 데이터를 읽는 책임을 가집니다. Command는 데이터를 저장하거나 변경하는 책임을 가집니다.

두 책임은 하나의 배포 단위와 같은 DB 안에서도 논리적으로 구분할 수 있습니다. CQRS를 적용하기 위해 처음부터 DB를 물리적으로 분리할 필요는 없습니다.

조합 정책이 Facade에 있고 데이터 접근과 그 데이터의 규칙이 Service에 유지되므로, 조회와 Command의 구현 및 저장소를 분리할 기반이 마련됩니다.

업무 Facade
├─ 조회 계약
│ └─ 조회 구현 → 조회 Repository → 읽기 DB
└─ Command 계약
└─ Command 구현 → Command Repository → 쓰기 DB

이 구조는 분리 가능한 경계를 정의합니다. 이미 별도 DB로 분리하여 운영하고 있다는 의미는 아닙니다.

실제 분리 시에는 구현 구성에 따라 데이터소스, 트랜잭션 관리자, Repository 연결과 데이터 동기화 방식을 설정해야 합니다. 업무 Facade는 기존 계약을 조합하는 역할을 유지합니다.

트랜잭션 경계​

업무 Facade는 유스케이스에 필요한 로컬 DB 작업의 트랜잭션 범위를 결정합니다. 관련 Service는 지정된 트랜잭션에 참여합니다.

모든 Facade 호출을 하나의 트랜잭션으로 묶는 것은 아닙니다. 외부 시스템 호출과 로컬 DB 변경의 경계를 구분합니다.

같은 DB의 같은 트랜잭션에서 자신의 변경 내용을 조회하는 경우와, 별도 읽기 DB에서 조회하는 경우는 보장이 다릅니다. 로컬 트랜잭션만으로 별도 읽기 DB의 반영 시점까지 보장하지는 않습니다.

물리적 분리 이후의 지연 정책​

조회와 Command DB를 물리적으로 분리하면 복제 또는 조회 모델 갱신에 지연이 발생할 수 있습니다. 분리 설계 단계에서 유스케이스별 정책을 수립하고 운영 전에 검증합니다.

정책 항목결정할 내용
변경 결과 응답Command 결과를 반환할지, 다시 조회할지 결정합니다.
변경 직후 조회자신의 변경을 즉시 확인해야 하는지 결정합니다.
조회 경로최신 값이 필요한 경우 쓰기 DB를 사용할지 결정합니다.
지연 허용 범위읽기 DB의 데이터가 얼마나 오래되어도 되는지 결정합니다.
반영 대기필요한 버전까지 기다릴지, 제한시간 후 다른 경로를 사용할지 결정합니다.
진행 상태비동기 작업의 접수, 진행, 완료 및 실패를 어떻게 표현할지 결정합니다.

업무 Facade는 유스케이스가 요구하는 일관성 수준을 선택합니다. 조회 경로 선택과 반영 상태 확인 등의 기술적 처리는 해당 계약의 구현체에서 수행합니다.

테스트 경계​

테스트검증 대상
Service 계약 테스트입력, 반환값, 오류와 데이터 접근 의미
Service 통합 테스트실제 조회, 저장, 제약조건과 트랜잭션 동작
Facade 단위 테스트Service와 Port 조합, 정책 선택, 분기와 실패 처리
유스케이스 시나리오 테스트전체 흐름의 최종 결과와 정합성
분리 이후 통합 테스트동기화 지연, 변경 직후 조회, 제한시간과 대체 조회 경로

고정된 Service 계약과 Facade의 정책 경계를 함께 검증합니다. 조회와 Command를 분리할 때는 기존 계약을 유지하는지와 새로 정의한 지연 정책을 충족하는지 확인합니다.