1. 신호
이 고민은 유효성 검사 로직에서 출발한다.
유효성 검사 로직이 많아지다 보니 처음에는 validator class를 분리해야 하나 생각했다. 하지만 그것보다 근본적인 문제가 있었다. 전시 서비스에서 카테고리 서비스를 참조해서 카테고리를 조회해야 하는데 이때 자신이 사용하는 것보다 더욱 많은 기능을 가져오게 된다. 왜 그럴까?
2. 원인과 문제
원인
원인은 서비스 클래스가 너무 많은 일을 하고 있는 것이다. 이는 SRP 위반이라고 할 수 있다. 따라서 서비스 클래스의 길이가 점점 커진다.
문제점
- 서비스 클래스가 길어지면 기능을 추가할 때마다 코드를 깔끔하게 유지하기 어렵다.
- 전형적인 뚱뚱한 클래스로 Fan-In 문제가 발생한다. 예컨대 전시 서비스, 카테고리 컨트롤러가 의존함
- 테스트 코드가 길어진다.
- 레파지토리 인터페이스가 변경되면 연관되지 않은 기능들도 포함해 다시 빌드해야 한다.
- 서비스 클래스가 변경되면 의존하는 클래스들을 다시 빌드해야 한다. 혹은 수정해야 한다.
- 클래스가 작으면 내부 클래스로 exception들을 분산시킬 수 있다. (하지만, 지금은 힘들다.)
- 서비스 클래스의 역할이 불분명하다. => 좋은 이름을 지을 수 없다.
- 무분별한 기능들 때문에 유효성 검사 로직이 길어지는 것처럼 보인다. 역할을 분리하면 완화될 것으로 생각된다.
이외에도 controller->service->repository 간의 의존성도 런타임과 소스코드 의존성이 서로 일치한다는 문제점이 있다. 레이어드 아키텍처의 방향성은 런타임 의존성을 의미하는 거지 소스코드 의존성은 역전시켜야 한다.
그리고 역할을 분리했을 때 유효성 검사 로직이 중복되는 경우가 생기면, 그 유효성 검사는 도메인 객체 내부에 정의하는 것이 더 나은 판단일지도 모른다. validateCategoryNotFound 같은 경우는 Optional에서 orElseThrow를 이용해서 충분히 다룰 수 있을 것 같고, validateOwnedByUser는 지금도 카테고리 객체에게 메시지를 보내서 처리한다. (단지 재호출할 뿐이니 삭제하고 도메인 객체 내부에서 처리하도록 변경)
3. 개선
2차 데브캠프 이후 받은 피드백에서 많은 아이디어를 얻었다.
위 문제를 해결하기 위해 여러 방법을 생각해 봤다.
3.1 Facade 패턴을 적용 + 클래스 분리(version 1)
파사드를 기반으로 서비스 클래스를 각 책임에 맞게 분리할 수 있다. 하지만 문제는 존재한다. 소스 코드 의존성은 여전히 런타임 의존성과 같다. 정답은 없겠지만, 복잡도 vs 의존성이라는 트레이드오프 중에서 팀원과 잘 협의해야 한다.
3.2 Facade 패턴을 적용 + 클래스 분리 + 의존성 역전(version 2)
의존성은 역전 됐지만, 이렇게 만들기는 어려울 것 같다. creator, updater,... etc에 대한 범용 인터페이스를 만들기 어렵다. 여기서 creator는 CreateCategoryServiceImpl 정도가 될 것이다.
3.3 Facade 패턴을 적용 + 클래스 분리 + 의존성 역전 + 인터페이스 분리(version 3)
문제는 대부분 해결될 것 같다는 생각이 들지만, 복잡도가 늘어난다. 어쩌면 과할지도 모른다. controller->facade 간의 역전도 과할지도 모른다. version1, version3 중에서 트레이드오프를 고려해서 선택해야겠다고 생각한다. 오늘은 여기까지..
'개발(레거시) > 문제 해결' 카테고리의 다른 글
레거시 코드를 헥사고날 아키텍처로 전환(2) - 회원 도메인에서 읽기 전용 유스 케이스 분리, 영속성 어댑터 만들기 (0) | 2023.03.31 |
---|---|
레거시 코드를 헥사고날 아키텍처로 전환(1) - 회원 도메인에서 쓰기 전용 유스 케이스 분리 (2) | 2023.03.29 |
아르티 리팩터링 기록 - publish (0) | 2023.01.23 |
Intelij Ultimate 다이어그램 안나올 경우 (0) | 2022.12.11 |
Typeorm 식별 관계 문제 (0) | 2022.10.03 |