비대해진 Drive 서비스 리팩토링 및 권한 상속 모델을 적용한 드라이브 자원 공유
기존 구조와 문제
초기에는 DriveController에서 단일 DriveService를 주입받아 모든 비즈니스 로직을 처리하는 구조였다.
DriveController
└── DriveService
└── DriveEmbeddingService
DriveEmbeddingScheduler
└── DriveEmbeddingService
이 구조에서 DriveService는 다음과 같은 다양한 역할을 홀로 비대하게 수행하고 있었다.
- 폴더의 생명주기 관리 (생성, 이동, 삭제, 수정 등)
- 파일의 생명주기 관리 (생성, 이동, 삭제, 수정 등)
- 드라이브 자체의 관리 및 유저 식별
- AI 연동 질의 시 참조될 파일 및 폴더 관리
기능이 추가될수록 단일 클래스가 비대해졌고, 코드의 가독성과 확장성이 떨어졌다. 각 도메인(파일, 폴더, 드라이브 등)의 경계가 모호해져 유지보수성을 저해함으로써, 미래에 기술 부채로 다가올 것으로 예상되었다.
1차 리팩토링: Facade 패턴을 통한 책임 분리와 오케스트레이션
DriveController
└── DriveFacade (흐름 제어 및 트랜잭션 관리)
├── DriveFolderService (폴더 생명주기 관리)
├── DriveFileService (파일 생명주기 관리)
├── DriveManageService (드라이브 생성 및 유저 식별)
├── DriveRefService (질의 참조용 폴더/파일 관리)
└── DriveEmbeddingService (폴더 임베딩 상태 업데이트)
DriveEmbeddingScheduler
└── DriveEmbeddingService (임베딩 API 호출 및 상태 관리)
- 각 서비스는 본연의 비즈니스 로직만 수행하도록 하였으며, DriveFacade는 이들을 주입받아 오케스트레이션 역할을 담당하도록 했다.
- 특히, 트랜잭션의 범위가 명확해지는 장점이 있었다. Facade 계층의 메서드 진입점에서 트랜잭션을 시작하므로, 여러 서비스 간의 복합적인 상태 변경이 하나의 원자적 단위로 묶이기 때문이다.
- 다음은 파일/폴더 이동 로직의 예시이다.
/**
* 파일/폴더들을 특정 폴더로 이동 (Facade Layer)
*/
@Transactional
public void moveItems(String userId, DriveModel.MoveItemsReq req) {
// 이동에 따른 임베딩 상태 영향 받는 폴더 아이디 리스트
Set<String> affectedFolderIds = new HashSet<>();
if (req.getTargetFolderId() != null) {
affectedFolderIds.add(req.getTargetFolderId());
}
// 파일 이동 처리
driveFileService.moveFiles(req.getFileIds(), req.getTargetFolderId(), affectedFolderIds);
// 폴더 이동 처리
driveFolderService.moveFolders(req.getFolderIds(), req.getTargetFolderId(), affectedFolderIds);
// 이동에 따라 영향 받는 폴더 임베딩 상태 변경
driveEmbeddingService.updateFolderEmbStatusByFolderId(affectedFolderIds);
}
공유 기능이라는 새로운 요구사항: 1:1에서 M:N으로 확장
드라이브 기능이 운영되던 중, 드라이브의 자원(파일/폴더)들은 다른 사용자와 공유될 수 있으며 공유 드라이브 또한 추가된다는 새로운 비즈니스 요구사항이 추가되었다. 이로 인해 기존 아키텍처와 DB 설계에 변화가 필요해졌다.
기존:
- 기존에는 User와 Drive가 1:1 관계였다.
- 따라서 드라이브의 PK인 driveCode만으로 이 자원이 누구의 것인지 즉, 접근 권한이 있는지를 판별할 수 있었다.
- 따라서 DB 쿼리와 인덱스 또한 driveCode에 강하게 결합되어 있었다.
요구사항 추가 후:
- User와 Drive가 M:N 관계로 변경되어야 했다.
- 이제 driveCode만으로는 어느 사용자가 해당 자원에 접근 가능한지 구분할 수 없게 되었다.
- 또한 권한 등급(읽기/쓰기/관리자 등)에 따라 공유받은 자원에 대해 취할 수 있는 행동도 제한해야 했다.
결론적으로, 다형적인 권한 상태를 제어하고 M:N 관계를 해소할 단일 진실 공급원 역할의 별도 매핑 테이블이자 사건 엔티티가 필요해졌다.
2차 리팩토링: Command/Query 분리와 권한 서비스 도입
복잡해진 요구사항(공유, 권한 검증, 임베딩 등)을 하나의 Facade에서 모두 처리하려다 보니, Facade가 너무 많은 것을 알게 되는 God Class가 될 가능성이 있었다.
특히 단순히 데이터를 조회하는 로직(Query)과 상태를 변경하는 로직(Command)의 복잡도 차이가 컸다. 이를 해결하기 위해 Facade를 CQS 형태의 설계를 적용했다.
DriveController
├── DriveQueryFacade (조회 전용)
│ ├── DriveManageService
│ ├── DriveFileService
│ ├── DriveFolderService
│ └── DriveRefService
│
└── DriveCommandFacade (상태 변경 및 비즈니스 로직 전용)
├── DriveManageService
├── DriveFileService
├── DriveFolderService
├── DriveRefService
├── DriveEmbeddingService
└── DrivePermissionService
DriveEmbeddingScheduler
└── DriveEmbeddingService
조회 퍼사드와 명령 퍼사드를 분리함으로써 코드의 응집도를 높였고, DrivePermissionService라는 전담 서비스를 추가하여 M:N 환경에서의 공유 권한 검증 로직을 처리하도록 했다.
AOP를 이용한 권한 검증과 권한 상속 모델
'심심할 때' 카테고리의 다른 글
| AI 연동형 사내 드라이브 시스템 (0) | 2026.02.08 |
|---|---|
| 운영 중인 시스템의 1:1 구조를 1:N으로 바꾸기 (0) | 2026.01.14 |
