Service 인터페이스와 구현체 분리 추상화에 대해 알아보자.
오늘 Service의 추상화를 통한 코드개선 과제를 진행하였다. Service의 추상화란 무엇이며 왜 하는것일까?
Service의 추상화에 대해 조사하는 중에 Spring AOP에 대한 관련이 있다는 사실을 알게되었다. Spring AOP는 Bean 등록 시 사용자의 특정 메서드 호출 시점에 AOP를 시행하는 Proxy Bean을 생성 해주며 크게 2가지 프록시 객체 생성 방법이 존재한다.
JDK Dynamic Proxy와 CGLib이다. 두가지 방법의 대략적인 차이점은 Dynamic의 경우 프록시 객체 생성 시 인터페이스 존재가 필수
적이고 CGLib은 프록시 객체 생성 시 인터페이스가 존재하지 않아도 클래스 기반
으로 프록시 객체를 생성할수 있다는 점이다. 그럼 이게 왜 Service추상화와 어디가 관련이 있는데? 라는 질문이 머리에 떠오를 시점이다. 우리가 집중해야 할 부분은 바로 Dynamic시 인터페이스 존재가 필수라는 부분과 CGLib는 그렇지 않다는 점이다.
Spring 3.2이전에는 Spring AOP 사용시 CGLib의 여러 문제점(net.sf.cglib.proxy.Enhancer
의존성 추가, default
생성자가 반드시 필요, 타깃 생성자를 두번 호출 등)의 문제점으로 인해 JDK Dynamic Proxy 방법이 권장 되었다.
그럼 여기서 앞서 말한 Dynamic의 경우 인터페이스가 반드시 필요
하다는 점을 생각하면 슬슬 감이 올것이다. Service가 Spring AOP를 사용하기 위해 권장되는 방법인 Dynamic의 방법을 사용하기 위해 인터페이스를 통한 추상화를 하였다라고 정리할수있다.
하지만 여기서 이상한점이 느껴질것이다. Spring 3.2 이전
이라면 현재 3.2를 넘어서 3.5를 사용하는 현재시점에서는 어떻게 변화하였을까?
Spring 3.2 이후부터는 CGLib의 앞서 언급한 문제점들이 외부 라이브러리의 도움으로 개선이 이뤄져 Spring core 패키지에 포함되었고 Spring Boot 사용 시 인터페이스를 구현한 클래스여도 JDK Dynamic proxy보다 성능이 좋은 CGLib를 디폴트로 사용하여 프록시 객체를 생성한다.이렇게 변화함으로써 우리는 AOP관점에서는 더이상 Service의 추상화를 할필요가없다. 하지만 현재 Service의 인터페이스를 구현하는 구조가 권장되고있는데 다른 어떤 이유가 있을까?
맨 처음 자바를 배울때 SOLID
5원칙 과 제어의 역전에 대해 배우거나 한번 쯤은 들어봤을것이다.
개방 폐쇄의 원칙(OCP) : 소프트웨어의 구성요소(컴포넌트, 클래스, 모듈, 함수 등)은 확장에는 열려있고, 변경에는 닫혀있어야한다.
의존 역전의 원칙(DIP) : 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 하고 그 반대가 되면 안된다.
위 내용을 Service의 추상화에 연관 지어보면 ServiceImpl
은 Service
인터페이스의 구현체이므로 ervice에 대해 Compile 의존성을 가지지만 인터페이스인 Service
는 반대로 ServiceImpl
에 대해 Compile 의존성을 가지지 않는다. 간단히 말해서 우리는 설계를 할때 우리가 고려한 기능뿐만아니라 배포 단계이후에서도 추가될 기능또한 고려해야한다. 한마디로 확장성
을 생각해야한다는것이다. 예를 들어 여러 도메인이 얽혀있는 도메인의 서비스에 기능을 추가한다고 가정했을때 해당 서비스가 변함으로써 여러 도메인또한 영향을 받아 변화하게 될것이다. 하지만 구현체의 서비스가 변화하더라도 인터페이스는 변하지않으므로 다른 도메인의 서비스구현체들은 변화가 적을것이다.
하지만 지금까지 Spring을 통해 설계를 통해 인터페이스오 구현체가 1대1관계인 서비스만 구현해왔기 때문에 서비스의 추상화에 대해 중요하게 생각하지않고 굳이 필요한가?
라고 생각했지만 OOP관점에서 보면 인터페이스를 구현하여 향후 확장성을 고려한 설계를 하는것이 옳은 방법인거같다.