강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

alcuz님의 프로필 이미지
alcuz

작성한 질문수

토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1

인터페이스

해결된 질문

작성

·

18

0

안녕하세요! 이번 강의 수강 중 서비스 인터페이스에 대해 궁금하여 글을 남기게 되었습니다.

 

흔히 스프링 프로젝트에서 보통 Service 인터페이스와 ServiceImpl 구현체로 나누어 개발하며 제가 참여하게 된 프로젝트 또한 이러한 구조였습니다.

이미 이전 개발자 분께서 Service 인터페이스와 ServiceImpl 구현체로 나눠놓은 상태였고, 제가 A 기능의 AServiceImpl 안에 특정 서비스 로직이 비대해짐에 따라 해당 코드가 명확하지 않다고 생각되어 메서드로 분리하게 되었습니다. A 엔티티의 상태, 계산 등의 로직으로 private 메서드를 두었습니다.

여기서 동료와 의견이 갈렸습니다:

동료 입장:

1. ServiceImpl 안의 모든 메서드는 반드시 인터페이스에 있어야 한다.

2. 인터페이스는 외부·내부 구분이 아니라, 구현체가 제공하는 기능을 보장하는 것이므로 전부 담아야 한다.

3. 내부 메서드를 둘 거라면 굳이 인터페이스로 추상화할 필요가 없고 그냥 클래스로 쓰면 된다.

제 입장:

1. 인터페이스에는 “외부 계약(=공개 API)”만 있어야 한다.

2. 구현체 내부에서만 쓰이는 로직은 private으로 감추는 게 맞고, 외부에서 호출할 필요가 전혀 없다.

3. 인터페이스를 구현체 내부 헬퍼까지 다 포함하면, 오히려 계약이 불필요하게 비대해지고 역할이 모호해진다.

핵심 쟁점

저는 public 메서드는 모두 인터페이스에 있어야 한다는 데에는 동의합니다. 또한, 단순 public, private 만 있다면, 인터페이스의 역할이 모호하다는 것또한 이해하지만, 이전 코드와의 일관성과 운영 중인 시스템에 대한 변경이 필요할정도의 중대사항이라고 생각하지 않습니다.

궁금한 점은 private/내부 헬퍼 메서드까지 인터페이스에 강제로 올리는 게 맞는지, 인터페이스를 사용하는 의의가 궁금합니다.

 

긴글 읽어주셔서 감사합니다.

답변 2

0

토비님의 프로필 이미지
토비
지식공유자

인터페이스를 구현한 클래스라고 모든 메소드가 다 인터페이스에 있어야 한다는 것은 잘못된 생각입니다.

객체지향의 설계 원칙인 캡슐화를 생각해보면 됩니다. 자주 변경되지 않고 다른 오브젝트에게 공개할 기능은 public으로 만듭니다. 반대로 내부에서 여러가지 이유로 분리해서 만들었지만, 이건 외부에 공개할 이유가 없고, 그리고 변경될 가능성이 있거나 구현 상세를 노출하면 안 되는 것들은 private으로 만듭니다. 이건 인터페이스를 떠나서 일반 클래스를 설계할 때도 적용되는 것입니다.

그러면 인터페이스를 구현해서 만든 경우는 어떻게 해야할지에 말씀드릴게요. 당연히 인터페이스를 구현했다고 모든 메소드를 public으로 만들고 인터페이스에 클래스의 모든 메소드를 넣어야 하지 않습니다. 이건 클래스 레벨서의 이유와 동일합니다.

그런데, 클래스의 모든 public 메소드는 인터페이스에 있어야 할까요? 일반적으로 단일 인터페이스를 구현해서 만드는 ServiceImpl 같은 경우엔 그럴 가능성이 높지만, 꼭 그래야하는 것은 아닙니다.

오브젝트를 외부 모듈에 인터페이스를 통해서 공개할 때는 그 인터페이스를 이용하는 클라이언트(인터페이스를 통해서 기능을 호출하는 코드를 다 클라이언트라고 부를 수 있습니다)의 의도에 맞춰서 인터페이스를 정의하고 구현하도록 하면 되는데, 종종 하나의 오브젝트가 한 개 이상의 인터페이스를 구현하기도 하고 이게 필요하거나 자연스러운 구현 방법일 수도 있습니다. 이 경우 특정 인터페이스가 그걸 구현한 클래스의 모든 public 메소드를 다 가지고 있지 않게 되죠.

자바에서 자주 쓰는 ArrayList라는 클래스를 보시면 구현하고 있는 인터페이스가 List, Iterable, Collection, RandomAccess, Cloneable, Serializable로 여러개 입니다. 이 클래스로 만든 오브젝트를 사용하는 클라이언트가 어떤 인터페이스를 바라보고 쓰느냐에 따라 필요한 인터페이스 타입으로 사용하도록 만들면 됩니다. 심지어 메소드가 없는 마커 인터페이스도 있죠. 이것도 해당 타입으로 오브젝트를 인식해서 필요한 용도의 기능으로 사용할 수 있습니다.

객체지향설계원칙 SOLID 중에서 인터페이스 분리 원칙(ISP)이 있습니다. 이걸 잘 지켜서 설계하면, 여러가지 이유로 구현 클래스는 하나이지만 하나 이상의 인터페이스를 구현하도록 만드는 경우가 종종 발생합니다. 그래서 클래스의 public 메소드를 하나의 인터페이스에 반드시 다 담아야 하는 것은 아닙니다. 다만, 예로 드신 서비스 클래스와 (단일) 서비스 인터페이스인 경우엔 특별한 이유가 없다면 클래스의 public 메소드는 인터페이스에 정의된 메소드로만 구성될 것입니다. 스프링 기준으로 종종 발견되는 예외가 있다면 setter 주입을 사용하는 경우 setter 메소드는 public이지만 인터페이스에 포함되지 않겠죠. 요즘은 보통 생성자 주입을 많이 쓰지만, 선택적인 의존 오브젝트나 정보인 경우엔 생성자가 아니라 setter를 사용하기도 합니다.

alcuz님의 프로필 이미지
alcuz
질문자

이해가 어려웠던 부분을 깔끔하게 정리해주셔서 큰 도움이 되었습니다. 바쁘신 와중에도 친절하게 설명해주셔서 진심으로 감사드립니다!

0

안녕하세요, 인프런 AI 인턴입니다. alcuz님 같은 상황에서 많은 개발자들이 비슷한 고민을 합니다.

서비스 인터페이스와 구현체 간의 관계는 캡슐화 원칙을 고려하여 설계해야 하는데, 여기서 중요한 점은 인터페이스는 외부와의 계약이라는 점입니다. 이는 따라서 공개적으로 사용할 기능만을 정의하는 것이며, 내부 구현에 대한 디테일은 구현체에 맡기는 것입니다.

인터페이스의 역할

  • 계약서 역할: 인터페이스는 외부에서 이 객체가 어떠한 기능을 제공하는지를 정의하는 계약서입니다. 따라서, 외부 클라이언트가 호출해야 할 메서드만 노출되어야 합니다.

  • 외부/내부 명확화: 내부 로직이나 헬퍼 메서드가 인터페이스에 포함되면, 오히려 인터페이스의 본래 목적인 외부와의 명확한 경계 설정이 흐려질 수 있습니다.

구현체의 메서드 설계

  • Public vs Private: 공개적으로 제공해야 할 메서드는 public으로 인터페이스에 정의되며, 내부 로직에 필요한 헬퍼 메서드는 private으로 구현체 안에만 두어야 합니다.

  • 내부 로직 비대해짐 해결 방법: 로직이 비대해질 경우, 서브 클래스를 만들어 로직을 나누는 방법을 고려할 수도 있으며, 이는 코드의 가독성과 유지보수를 용이하게 만듭니다.

이와 관련해 [의존성 방향과 관련된 질문이 있습니다.] 에서, 토비님께서 의존성의 방향과 인터페이스의 적절한 활용에 대해서 설명한 답변이 있습니다. 이를 통해 인터페이스의 정확한 의도와 스프링 프로젝트에서 이를 어떻게 활용하는지가 힌트가 될 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

alcuz님의 프로필 이미지
alcuz

작성한 질문수

질문하기