Do I need an interface with Spring boot?
Spring Boot를 사용하다보면 @Service annotation을 붙인 Service를 사용하게 된다. 인터넷 상의 많은 예시에서, Service를 개발할 때 interface를 사용하곤 한다. 예를 들어서 Todo Application을 만든다고 할 때, TodoService라는 interface와 TodoServiceImpl이라는 구현체를 만들 때가 있다.
그동안 강의, 책을 통해 혼자 독학으로 공부를 하다가 이번에 기회가 되어 2개의 프로젝트를 동시에 진행을 했다.
처음 기초 틀을 만들고, 한창 API를 개발할 때는 신경쓸게 많아서 깊게 생각하지 못했다.
프로젝트 개발이 얼추 끝난 지금은 프로젝트를 진행하며 느낀 의문에 대해 고찰하고, 해결해보려고 한다.
의문의 시작
우선 가장 큰 의문은 interface
의 사용이다.
독학을 하며 몇 개의 Java 관련 책을 읽었는데 크게 강조하는 것이 추상화, 다형성이었으며, 동시에 interface에 대한 것이었다.
또한, 나의 코드 공부 방식은 깃허브에 올라와 있는 완성된 프로젝트의 코드를 하나하나 분석하며 흐름을 따라가며 공부를 하는데, 종종 보이는 것이 interface의 사용이다.
즉, 나에게 interface란 반드시 사용해야 하는 것이고, 사용하지 않는 코드는 잘못된 코드란 인식이 생겼다.
그런데 이 인식이 이번에 프로젝트를 진행하면서 흔들렸다. Spring Boot로 개발을 해보니 Service에 한해서는 무조건적으로 interface
의 사용하는 것이 비효율적이라는 생각이 들었던 것이다.
이는 단순히 나의 지식 부족이거나, 나 개인의 생각일 수 있기 때문에 여러 개의 글들을 참고해서 정리해보려고 한다.
interface를 만들 필요 없다. 물론, Spring Boot에 한해서다.
Todo Application을 만든다고 할 때, Service를 만든다면 class 자체의 이름을 TodoService라고 하고 Spring Boot가 지원하는 Autowire
를 통해서 Bean들에 주입하면 된다.
예시 코드
@Service
public class TodoService {
public List<Todo> findAllTodos() {
// TODO: Implement
return new ArrayList<>();
}
}
@Component
public class TodoFacade {
private TodoService service;
public TodoFacade(TodoService service) {
this.service = service;
}
}
위 예시 코드는 @Autowired
를 이용한 filed injection을 사용하던, 생성자 주입을 사용하던간에 작동할 것이다.
굳이 필요하지 않다며는 왜 interface를 이용한 방식을 종종 사용하곤 하는 걸까?
첫번째 이유는 역사적인 이유이다. 하지만, 이를 살펴보기 전에 Spring에서 Annotation이 어떻게 작동하는지를 알아보자.
만약 @Cacheable
같은 annotation을 사용한다고 하면, cache에서 결과를 얻을 것이라고 예상할 수 있다. Spring에서 그것이 작동되는 방식은 bean들을 위한 proxy를 만들고, 그 proxy들에 필요한 로직을 추가해주는 것이다. 원래 스프링은 JDK dynamic proxies를 사용했다. 이는 interface 만을 위해서 만들어졌고, 바로 이것이 예전에는 interface를 작성해줘야 했던 이유이다.
그러나, 이제는 상황이 다르다. 약 10년 전부터, Spring이 CGLIB Proxying도 지원하기 시작했다. 이 proxy들은 별도의 interface를 필요로 하지 않는다. 심지어 Spring 3.2 버전부터는 CGLIB이 Spring에 내장되어 있어서 별도로 추가해줄 필요도 없어졌다.
때문에 첫번째 이유가 역사적인 이유인 것이다.
두 번째 이유는 두 class 간의 느슨한 결합을 만들기 위해서이다. interface를 사용함으로써, service에 의존하는 class는 더 이상 service의 구현에 의존하지 않게 된다. 이것이 service를 독립적으로 사용할 수 있게 해준다.
예시 코드
public interface TodoService {
List<Todo> findAllTodos();
}
@Service
public class TodoServiceImpl {
public List<Todo> findAllTodos() {
// TODO: Implement
return new ArrayList<>();
}
}
@Component
public class TodoFacade {
private TodoService service;
public TodoFacade(TodoService service) {
this.service = service;
}
}
위의 예시에서, TodoFacade와 TodoServiceImpl을 왜 분리한걸까?
여기서 interface를 추가하는 것은 불필요하게 복잡도를 늘리는 것은 아닐까?
해당 글을 정리한 저자는 '만약 개인적으로 interface를 service에 사용해야 하냐는 질문을 받는다면, 내 대답은 아니오다.'라고 말한다. 유일한 예외는 제어의 역전을 사용하거나 여러개의 구현체를 신경써야 하는 경우라고 한다.
만약의 경우를 위해서 interface를 만드는게 좋지 않을까?
글의 저자와 나의 개인적인 대답은 '아니오'이다. 그 근거는 'YAGNI(You aren't gonna need it)'라는 원칙이다. '추후에 필요할지도 몰라'라는 이유로 굳이 복잡도를 높힐 필요가 없다는 것이다. 왜냐하면 일반적으로 결국 필요하지 않기 때문이다.
필요한 경우가 생기더라도 문제가 없다.
대부분의 IDE는 기존의 class에서 Method만 추출해서 interface를 만들 수 있게 도와주고, 모든 코드들을 그 interface를 사용하게끔 도와준다.
YAGNI 원칙?
'You aren't gonna need it'는 프로그래머가 필요하다고 간주할 때까지 기능을 추가하지 않는 것이 좋다는 익스트림 프로그래밍(XP)의 원칙이다. 익스트림 프로그래밍의 공동 설립자 론 제프리스는 다음과 같이 썼다: "실제로 필요할 때 무조건 구현하되, 그저 필요할 것이라고 예상할 때에는 절대 구현하지 말라."
출처: https://ko.wikipedia.org/wiki/YAGNI
내가 블로그에 정리한 것은 원본 글을 축약해서 번역한 것이다. 더 자세한 원문을 보고 싶다면 아래 링크를 참고하면 된다.
Do I need an interface with Spring boot?
출처
https://ko.wikipedia.org/wiki/YAGNI
https://velog.io/@hsw0194/Spring-Boot에서-interface를-사용해야-할까#짧은-결론은
https://swk3169.tistory.com/185