
개발을 하면서 궁금증이 생겼다.
왜 Spring에서는 굳이 귀찮게 Service Interface를 만들고, 이를 구현하는 구현체를 만들어야 할까?
그냥 바로 Service Class를 만들어서 Controller나 Repository로 의존 관계를 갖게 하면 될 것 같은데 왜 개발의 생산속도를 늦추는 걸까??
내 귀찮음으로부터 시작했던 질문을 통해서 공부했던 것들을 함께 이야기 해보고자 한다.
본론부터 말하면 프록시 객체를 만드는 방법 때문이다.
Spring AOP는 빈을 등록할 때 사용자의 특정 메서드 호출 시점에 AOP를 수행하는 프록시 빈을 생성해주며, 이 때 크게 2가지 프록시 객체 생성 방법이 있다.
→ 이 때 프록시는 JPA가 지연 로딩시 엔티티를 프록시 객체로 만들고, 실제 해당 엔티티를 호출할 시에 진짜 데이터를 가져오는 느낌으로 이해하면 된다.
첫번째 방법으로는 JDK Dinamic Proxy, 두번째 방법으로는 CGLib이 있다.
JDK Dinamic Proxy 같은 경우에는 프록시 객체 생성시 인터페이스 존재가 필수적이고, CGLib 같은 경우에는 클래스 기반으로 프록시 객체를 생성할 수 있다.
옛날에는 CGLib이 Spring AOP에서 여러 문제를 일으켜서 기본 세팅 값(spring.aop.proxy-target-class=false)이 JDK Dinamic Proxy이었다.
그래서 객체를 빈을 등록 시키고 싶으면 인터페이스를 만들고, 구현체를 만들어야 했던 것이었다.
그러나 Spring 3.2 이후부터는 CGLib의 문제점이 외부 라이브러리의 도움을 받아 개선되었다고 판단되어서 Spring boot에서는 기본 세팅 값 (spring.aop.proxy-target-class=true)으로 설정 되었다.
이로 인해서 Spring boot는 기본 프록시 객체 생성 방법은 CGLib이며, 인터페이스를 구현한 클래스이어도 CGLib을 default로 사용하여 프록시 객체를 생성한다.
다만 사람에 따라서는 Service가 Interface를 구현하는 방식으로 작성되어야 한다고 생각하는 사람들이 존재하는데, 이는 Spring 개념이 아닌 OOP 관점에서 이러한 주장을 한다.
이 때 주장을 한 문장으로 요약해보면 아래와 같다.
저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 하고, 그 반대가 되면 안된다.
이 말을 자세히 풀어보면, 고수준에서 저수준으로 갈 수록 구현과 더 가까워진다고 볼 수 있다.
저수준 컴포넌트는 구현과 더 가까이 있기에 변경에 더 취약하다.
클라이언트의 요청을 Controller 계층이라고 표현하고, Interface 사용없이 Service 클래스가 있다고 하자. Service 클래스는 저수준 컴포넌트이고, Controller는 고수준 컴포넌트라고 할 수 있다.
이 때 고수준 컴포넌트인 Controller가 저수준 컴포넌트인 Service를 의존하는 경우, 자주 변경이 일어나는 저수준 컴포넌트의 변경으로 인해 고수준 컴포넌트가 변경이 일어난 경우가 생기기 때문에 OCP와 DIP 원칙을 어기게 된다.
만약 Interface를 구현하는 방식을 사용한다면 Controller 즉 고수준 컴포넌트가 직접적으로 저수준 컴포넌트를 의존하는게 아닌 Interface 그러니까 고수준 컴포넌트를 의존하기에 이러한 문제를 해결할 수 있다.
다들 Service의 구현 코드가 변경되거나, 코드 변경으로 인해 Controller 계층의 코드를 변경해본 일이 한번쯤은 있을 것이다.
이런 측면에서 OOP를 생각해 Interface를 도입하는 것도 좋을 것 같다는 생각이 들었다.