모든 글

유즈케이스 아래 계층에서 코드를 재사용하기

유즈케이스끼리 재사용하면 비즈니스 변경이 번지고 순환 참조가 생깁니다. 위에서 조합하거나 아래의 구현 컴포넌트를 추출합니다.

출처 및 AI 안내: 이 글은 제미니의 개발실무 유튜브를 기반으로 작성되었습니다. gpt-5.6-sol 모델을 사용해 생성·편집했습니다.

제가 주로 코드를 나누는 방식에서 유즈케이스는 하나의 비즈니스 동작을 나타냅니다. 그런 역할 때문에 단순한 코드 재사용을 목적으로 유즈케이스가 다른 유즈케이스를 호출하는 구조를 조심합니다.

첫 참조는 별일 없어 보입니다. 비즈니스 흐름 B가 A에서 이미 읽는 정보가 필요하니 A를 호출합니다. 이후 다른 흐름이 B 일부를 필요로 합니다. 어느새 애플리케이션에는 비즈니스 관계를 알아보기 어려운 유즈케이스 체인이 생깁니다. 참조가 양쪽으로 늘어나면 순환 의존성도 자연스럽게 따라옵니다.

순환이 생기기 전에도 문제가 있습니다. A 자신의 비즈니스 요구사항 때문에 동작을 고쳤는데, B가 구현 편의를 위해 A를 재사용하고 있었습니다. B의 비즈니스를 바꾸려는 의도가 없었는데 새 동작이 B까지 전파됩니다. 오늘 우연히 같은 단계를 쓰는 두 정책을 재사용이라는 이름으로 결합한 셈입니다.

유즈케이스가 다른 유즈케이스를 절대로 호출하면 안 된다는 주장은 아닙니다. 그 관계가 정직한 상황도 있습니다. 다만 중복 코드를 발견했을 때 자동으로 선택할 기본값이 아니라 설계 결정으로 봅니다.

조합은 올리고 기능은 내리기

두 비즈니스 동작을 함께 사용해야 할 때는 보통 두 방향을 검토합니다.

첫 번째는 조합을 한 계층 위로 올리는 방법입니다. 코디네이터, 퍼사드, 래퍼나 명시적인 결합 유즈케이스가 두 동작을 호출합니다. 이름에서도 두 비즈니스 흐름을 조합한다는 사실이 보여야 합니다. 원래 유즈케이스가 서로를 조용히 소유하게 만들지 않습니다.

두 번째는 재사용할 기능을 유즈케이스 아래로 내리는 방법입니다. 여러 동작에서 사용자를 읽어야 한다면 그 책임을 가진 UserReader 같은 구현 컴포넌트를 만듭니다. 두 유즈케이스가 여기에 의존하면 서로의 비즈니스 정책까지 물려받지 않고 필요한 구현만 공유할 수 있습니다.

무엇을 공유하는지에 따라 방향이 달라집니다. 완성된 비즈니스 판단 두 개를 함께 조율하는 일이라면 위에서 조합합니다. 여러 유즈케이스가 같은 사용자 조회를 필요로 한다면 UserReader 같은 하위 구현체로 내려 재사용할 수 있습니다.

인증 관련 흐름에서 사용자 정보가 필요하다고 해보겠습니다. 기존 사용자 유즈케이스가 인증 때문에 조회 메서드를 제공한다면 경계를 다시 볼 필요가 있습니다. 사용자를 읽는 일 자체가 반드시 사용자 비즈니스 유즈케이스인 것은 아닙니다. UserReader가 더 정직하게 표현할 수 있는 구현 기능일 수 있습니다. 인증 컴포넌트가 사용자를 가져오는 책임까지 가져야 하는지도 함께 물어야 합니다.

이 질문들은 모든 재사용을 같은 육각형 그림에 끼워 넣는 것보다 유용합니다.

구현 계층을 단단하게 만들기

저는 비즈니스 코드에 작고 재사용 가능한 재료를 제공하도록 구현 계층을 단단하게 만드는 편을 선호합니다. UserReader처럼 책임이 분명한 구현체는 유즈케이스끼리 직접 의존하지 않고도 여러 흐름에서 재사용할 수 있습니다.

첫날부터 모든 리포지토리 호출을 래퍼로 감싸라는 뜻은 아닙니다. 하위 동작에 안정된 책임이 생기고 여러 비즈니스 흐름에서 필요해지는 시점을 보자는 말입니다. 그때 추출하면 한 유즈케이스를 다른 유즈케이스 안으로 넣지 않고도 결합도를 낮출 수 있습니다.

결과적으로 변경이 전파되는 범위를 예측하기 쉬워야 합니다. UserReader의 조회 방법을 바꾸면 구현 경계에 의존한 모든 호출자에 영향을 줄 수 있습니다. 예상 가능한 일입니다. 반면 “사용자 등록” 규칙을 고쳤다는 이유로 등록 유즈케이스를 재사용하던 “사용자 인증”까지 조용히 바뀌어서는 안 됩니다.

아키텍처는 소프트웨어의 성장에 따라가야 한다

이 논의는 포트와 어댑터라고도 부르는 육각형 아키텍처를 학습하던 질문에서 시작했습니다. 그 아키텍처가 나쁘다는 뜻은 아닙니다. 소프트웨어에 필요가 생기기 전에 완성된 형태를 학습 목표로 삼는 방식에는 조심스러운 편입니다.

저는 소프트웨어가 완성된 건축 도면보다 세포처럼 성장한다고 생각합니다. 작게 시작하고 동작이 쌓이면 도메인 모듈이 드러날 수 있습니다. 계층형 애플리케이션에 HTTP, RPC, TCP처럼 여러 외부 인터페이스가 붙을 수도 있습니다. 그 시점에는 포트와 어댑터가 안정된 애플리케이션과 외부 인터페이스를 나누는 구체적인 문제를 해결합니다.

처음부터 전체 구조를 적용하면 아직 가치가 드러나지 않은 경계를 유지하는 데 시간을 쓰면서 생산성이 낮아질 수 있습니다. 더 나쁜 경우에는 비즈니스를 잘못 이해한 채 만든 이른 경계가 굳어집니다.

구현 자체가 학습입니다. 서비스를 운영하면서 어디가 자주 바뀌는지 봅니다. 어떤 기능이 재사용되고, 어떤 정책이 함께 움직이며, 어떤 외부 인터페이스가 늘어나는지 관찰합니다. 그러면 아키텍처는 미래를 전부 짐작한 그림이 아니라 소프트웨어에서 얻은 지식을 기록합니다.

유즈케이스 재사용을 볼 때 묻는 것

유즈케이스 사이의 재사용을 만나면 다음을 확인합니다.

  1. 호출자가 두 비즈니스 동작을 의도적으로 조합하는가?
  2. 다른 유즈케이스 안에 갇힌 하위 구현 기능만 빌리려는 것인가?
  3. 호출 대상의 정책이 바뀔 때 호출자의 동작도 함께 바뀌어야 하는가?
  4. 참조가 늘어나면 비즈니스 흐름을 숨기는 체인이나 순환이 생기지 않는가?

비즈니스 동작이 정말 함께 있어야 한다면 상위 조합에 이름을 붙입니다. 구현 작업만 공유한다면 범위가 분명한 하위 컴포넌트로 내립니다. 유즈케이스는 비즈니스 판단으로 읽히게 둡니다.

메서드 두 개가 같은 코드를 호출한다고 재사용이 성공한 것은 아닙니다. 공유 경계가 한 가지 분명한 이유로 바뀌고, 각 비즈니스 흐름이 여전히 이해 가능할 때 좋은 재사용입니다.