Spring Framework에서 Service와 ServiceImpl 구조를 사용하는 이유는 무엇인가?
Spring 프레임워크에서 Service 인터페이스와 ServiceImple 클래스(구현체)를 나누어 사용하는 이유는 유연한 설계와 유지보수성을 위한 객체지향 설계 원칙과 밀접한 관련이 있습니다. 그 이유를 아래에 정리해 드리겠습니다.
1. 역할 분리와 추상화
- Service는 기능을 정의하는 인터페이스입니다.
- ServiceImpl은 실제 구현을 담당하는 클래스입니다.
이렇게 나누면 코드가 "무엇을 할 수 있는지"와 "어떻게 작동하는지"가 분리됩니다.
2. 느슨한 결합 (Loosely Coupled) 원칙을 이용한 소프트웨어 설계
- 클라이언트(Controller 등)는 구현체가 아닌 인터페이스에 의존합니다.
- 구현체가 바뀌어도 인터페이스만 유지되면 다른 클래스에 영향을 주지 않습니다.
3. 테스트와 유지보수 용이
- 테스트 시 UserService를 Mock 객체로 대체 가능 → 유닛 테스트에 유리합니다.
- ServiceImpl을 여러 버전으로 만들 수도 있어서 A/B 테스트나 신규 로직 적용에도 유리합니다.
※ Mock 객체란?
실체 클래스의 행동만 흉내내는 객체입니다. 테스트 중인 코드의 의존성을 대체하여, 해당 객체에 메서드가 호출되었는지만 확인하거나 원하는 리턴값을 임의로 지정할 수 있스비다. 주로 Mockito 같은 라이브러리를 사용하여 생성합니다.
4. 객체지향 원칙 적용
- DIP (의존 역전 원칙) : 고수준 모듈(Controller)이 저수준 모듈(ServiceImpl)에 의존하지 않고, 인터페이스에 의존합니다.
- OCP (개방-폐쇄 원칙) : 기존 코드를 수정하지 않고 기능 확장이 가능합니다.
5. 확장성과 다형성 활용
- 하나의 Service 인터페이스에 여러 Impl을 둘 수 있어 상황에 따라 다른 구현을 주입할 수 있습니다.
하지만 실제로 Spring에서 Service/ServiceImpl 구조에 대한 비판도 존재하며, 단순히 "관행이라서" 사용하는 것에 회의적인 의견도 많습니다. 다음은 해당 구조에 대한 비관적 시선(혹은 비판적 광점)을 정리한 내용입니다.
1. 쓸데없는 추상화 (Over-Engineering)
- "인터페이스를 위한 인터페이스"라는 비판이 많습니다.
- "구현 클래스가 하나뿐인데 굳이 인터페이스를 왜 나누는가?"
- 변화 가능성이나 확장이 실제로 없는데, 미리 인터페이스를 두는 것은 불필요한 복잡성이라는 주장입니다.
"아직 확장도 안 했는데 왜 추상화부터 하고 있지?"
2. 중복과 보일러플레이트 코드 증가
- UserService와 UserServiceImpl을 매번 만들어야 하므로 중복 코드와 클래스 수 증가 문제가 발생합니다.
- 결국 대부분의 메서드가 interface → impl로 단순 위임만 하게 되어 가독성과 생산성이 모두 하락 할 수 있습니다.
3. 의미 없는 계층 분리
- 설계 원칙을 따른다고 하지만, 실제로는 단순한 CRUD 처리를 위한 계층 분리만 존재하는 경우가 많습니다.
- 이러한 경우 서비스 계층이 가치를 제공하지 못하고 그냥 패스스루 역할에 그칩니다.
4. 테스트 용이성이라는 명분이 실제론 빈약
- "Mock을 쓰기 위해 인터페이스를 둔다"고 하지만, Mockito 같은 라이브러리는 클래스도 Mock 가능합니다.
- 따라서 테스트 목적이라면 꼭 인터페이스가 필요하진 않다는 주장도 설득력이 있습니다.
5. 구현체가 여러 개 생기는 경우는 드물다
- 실무에서 UserService를 두고 구현체를 2개 이상 두는 일은 거의 없습니다.
- 오히려 구현체가 많아질수록 코드는 더 복잡해지고 버그 발생 가능성도 증가합니다.
Service/ServiceImpl 구조에 대한 현재 실무(현업)의 전반적인 시선은 다음과 같이 실용주의 중심으로 흘러가고 잇습니다. 다시 말해, "무조건 한다"도 아니고, "절대 안 한다"도 아닌, 상황에 따라 선택하는 분위기입니다.
1. 규모가 작거나 단순한 프로젝트
- 대부분 ServiceImpl 하나만 쓰거나, 아예 인터페이스 없이 직접 구현합니다.
- 코드 수를 줄이고, 빠르게 개발하고 유지보수하기 위해 불필요한 계층 분리를 지양합니다.
- 특히, 스타트업·PoC(개념검증)·내무 시스템 등에서는 단일 클래스 사용이 보편적입니다.
"인터페이스 나눌 시간에 기능 하나라도 더 넣자"는 실용주의 관점이 많습니다.
2. 도메인이 복잡하거나 확장이 예상되는 경우
- Service와 ServiceImpl 구조를 적극적으로 도입합니다.
- 예를 들어, 결제/정산 시스템처럼 복잡한 규칙이 있고, 추후 외부 서비스로 교체 가능성이 있는 경우거나
- 또는 구현체가 환경에 따라 달라져야 하는 경우에 사용합니다. (ex. 내부 캐시 vs 외부 API 연동)
3. 팀/조직 차원의 개발 문화나 룰
- 어떤 회사는 전사 표준으로 무조건 인터페이스 분리를 강제하는 경우가 있습니다.
- 반면 어떤 회사는 간단한 기능은 클래스 하나로 처리하는 것을 장려하는 경우도 있습니다.
- 결론만 말하자면 회사 룰에 따른느게 팀워크와 생산성 면에서 더 중요할 때가 많아 케바케라는 것입니다.
마지막으로, 현대 실무는 '설계 원칙'과 '현실적인 생산성' 사이에서 균형을 찾으려는 분위기입니다.
"도구는 목적을 위한 수단일 뿐, 목적 그 자체가 아니다"라는 생각이 점점 확산되고 있습니다.
본인의 목적에 맞게 사용하는 것이 이 글을 쓰면서 들었던 생각으로 마지막을 정리하겠습니다.