스프링 개발을 처음 시작할 때 오직 service
만을 이용하여 개발을 시작했다. 그리고 service에 Interface
를 쓰게된 이유는 의존성 역전 (DIP) 을 고려하여 사용하게 되었는데.. 근데 개발하면서 인터페이스도 작성하고, 인터페이스를 구현하는 클래스 코드도 따로 작성하게 되면, 수정하면 둘다 수정해야한다는 점에서 의문점을 가지게 되었다.
개방-폐쇄 원칙
Interface
를 구체화하는 구현체 Service
의 차이만 있을 뿐, 클라이언트 코드 controller
입장에서는 무엇이 변했는지 전혀 알 수 없다.
→ 변화에 닫혀있다
OCP 원칙에 따르면, Interface와 구현체가 1:N
관계일 때, ServiceLayer의 구현체가 변경되더라도 클라이언트 코드는 전혀 수정할 필요가 없다.
→ 확장에 열려있다
의존 관계 역전의 원칙
하나는 todo 리스트를 메모리에서 가져오는 것이고, 하나는 DB와 같은 곳에서 가져온다고 생각해보자.
TodoService가 두 개의 구현체를 가지게 되고, 이때 유용하게 작용할 수 있다. 이렇게 하나의 서비스가 여러개의 구현체를 가지고 있다면, 다형성
을 활용할 수 있다.
public interface TodoService {
List<Todo> findAllTodos();
}
@Service
public class InMemoryTodoServiceImpl implements TodoService {
public List<Todo> findAllTodos() {
// TODO: Implement
return new ArrayList<>();
}
}
@Service
public class DatabaseTodoServiceImpl implements TodoService {
public List<Todo> findAllTodos() {
// TODO: Implement
return new ArrayList<>();
}
}
정률 할인 (10% 할인)에서 정액 할인 (1000원 할인)으로 변경할 때를 생각해보자.
만약 현재 정률 할인을 진행하더라도, 갑자기 정액할인으로 정책이 변경된다면, interface
는 변경하지 않고 구현체만 간단하게 변경하면 된다.
내가 처음 고민했던 것처럼 구조가 복잡해져서 코드를 분석하는 과정에서도 인터페이스를 거쳐서 다시 그 인터페이스의 구현체들을 확인하는 단계가 추가되면서 오히려 더 불편해진다는 문제점이 있다.
또한, 실제로 대부분의 프로젝트나 예제 코드를 보면 Service 인터페이스와 구현체 클래스 사이의 관계가 1:1
로 구성되어 있다.
그렇기 때문에 구현체가 2개 이상이 아니라면 인터페이스의 장점을 살릴 수 없기 때문에 인터페이스가 필요할 이유가 없지 않을까하는 의문점이 생긴다.
그러나, 위에서 말했듯이 인터페이스와 구현체를 분리해서 얻을 수 있는 장점은 서비스가 커지고 변화함에 따라서 얼마든지 구현체 클래스가 확장될 가능성을 가지고 있기 때문에 변화를 효과적으로 대처할 수 있다는 점이다.
예를 들면, 당근마켓 서비스 같은 경우, 일반 사용자도 존재하지만, 지역 게시판에 자신의 가게를 홍보할 수 있는 사장님의 권한을 가진 사용자도 존재한다.
이처럼, 서비스가 확장됨에 따라서 지금의 프로젝트가 1:1관계
를 가지고 있다고 하지만, 얼마든지 1:N 관계
를 가지는 서비스로 확장될 수 있다.
Naming은 어떻게 할까?
여기서 우리는 그럼 MemberService, MemberServiceImpl1, MemberServiceImpl2 이렇게 네이밍을 지어야하나 고민할 수 있다.
MemberService : 회원 서비스 인터페이스
- GeneralMemberService : 일반 회원 서비스 구현체 클래스
- CeoMemberService : 사장님 회원 서비스 구현체 클래스
다음과 같이 네이밍하는 것은 어떨지 추천해본다. 이런 구조가 정답은 아니지만, 기존 설계보다 충분히 이름만 가지고 동작과 역할이 유추가능한 클래스 이름을 가지고 있게 된다.
Impl라는 Package 만들고 그 안에 service 구현 클래스를 배치하는 이유는?
Service Interface와 Interface를 구현한 클래스를 별도의 패키지에 위치시키는 이유는 코드의 가독성을 높이고, 코드를 모듈화하고 분리하는데 도움이 되기 때문이다.
인터페이스와 구현체를 분리한 설계를 통한 이점들과 이유와 근거에 대해서 설계를 한 개발자 당사자는 명확히 이해를 해야한다는 것이 중요한 포인트이다.
https://see-one.tistory.com/1
https://junior-datalist.tistory.com/243
https://dev-coco.tistory.com/180
https://velog.io/@hsw0194/Spring-Boot%EC%97%90%EC%84%9C-interface%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C