Spring으로 Cli Application 구현하기

Spring Cli Application

주말동안 나를 충격과 공포에 휩사이게 했던 Spring Cli Application을 구현하며 겪은 시행착오를 정리하고자 한다.

잠깐 이번 미션 얘기를 하자면 기존 메일 서버 어플리케이션과는 별개로 cli 환경에서 실행되어 특정 사용자의 받은메일, 보낸메일, 중요메일, 삭제메일을 조회하는 기능을 가지는 어플리케이션을 구현하는 미션이었다. 처음에는 단순히 서버와 HTTP 통신을 하여 결과를 조회하는 자바 콘솔 어플리케이션을 구현했다.

이번 미션을 구현하면서 어떤 깨달음을 얻을 것이라는 말과는 달리 너무 간단하게 구현이 끝나서 조금 의아했다. 그리고 토요일 아침, 테스트 코드를 짜기위해 출근했을 때 알게되었다. 시작부터 잘못되었다는 사실을..

이번 미션의 본래 목적은 컨트롤러없이 서비스의 메소드들을 이용해 정보를 조회하는 것이었고, 그 과정에서 컨트롤러 본래의 목적이 아닌 서비스 로직들을 서비스로 옮겨야 한다는 깨달음을 얻는 것이었다. 제일 먼저 메일 컨트롤러에 있던 로직들을 메일 서비스로 옮겨 컨트롤러 본연의 역할에 충실하게 만들었다. 그 후 cli 어플리케이션을 통해 이 메소드를 호출하기만 하면 끝이었다. 하지만 스프링 어플리케이션으로 cli를 어떻게 구현을 할 것인가를 아무리 생각해보아도 답을 생각해내기 힘들었다. 컨트롤러에서 구현한 URL에 맵핑된 메소드들은 해당 URL로 HTTP 통신을 날리면 실행시키게 할 수 있다. 하지만 내가 만든 메소드는 어떻게 실행시키게 하는지 도저히 감이 오지않았다. 그래서 직접 메소드를 실행시키지 않고 다른 방법을 통해 메소드를 실행시키게 하려고 여러가지 시행착오를 하게 되었다.

Spring Shell

Spring Shell은 스프링 어플리케이션이 실행된 뒤 linux의 쉘처럼 사용자의 입력을 받는 프롬프트를 띄워주는 모듈이다. @ShellComponent 어노테이션을 붙인 클래스를 만들고 @ShellMethod 어노테이션을 붙인 메소드를 만들면 Spring Shell에서 해당 메소드를 인식하고 shell에서 명령어를 통해 해당 메소드를 실행시킬 수 있는 구조이다. 이 방법으로 구현하니 금방 cli 어플리케이션을 구현할 수 있었다. 하지만 이 모듈을 설치하면 실제 서버에서 어플리케이션을 실행시켰을 때 문제가 생겼다.

  1. maven으로 빌드를 하게되면 테스트 코드를 통해 테스트를 진행한 뒤 빌드를 진행하게 된다. 하지만 이 과정에서 유닛테스트가 아닌 SpringBootTest를 하게되면 ApplicationContext를 가져오는 과정에서 Spring Shell이 시작되어 테스트가 멈추고 무한정 대기하게 되는 상태에 빠진다. SpringBootTest를 통한 통합테스트를 안한다면 문제가 되지 않겠지만 추후에 통합테스트 테스트를 만들 시 문제가 될 수도 있다.
  2. 톰캣에서 war 파일을 통해 웹 어플리케이션을 구동시킬 때 Spring shell이 포함된 어플리케이션이 로딩되면 그 상태에서 로딩이 멈추게 된다. 따라서 현재 dev서버의 환경에서는 api서버를 반드시 먼저 로딩시키고, web서버를 로딩시켜야 한다.
  3. 서버 환경에서 Spring Shell을 사용하려면 포그라운드로 톰캣을 실행시켜야 한다. 그리고 다른 작업을 하고 싶으면 다시 백그라운드로 돌렸다가 다시 shell을 이용해 명령을 실행시키려면 다시 포그라운드로 돌리는 작업을 반복해야된다. 문제는 포그라운드에서 백그라운드로 전환시킬 때, Ctrl + Z를 눌러 Suspend 상태로 만들어야 한다는 점이다. Suspend 상태에서 다시 백그라운드로 돌리는 짧은 순간에 서비스가 일시적으로 정지되는 말도 안되는 상황이 발생한다.

이런 이유 때문에 Spring Shell을 이용하는 방법은 아니라고 생각했고, 다른 방법을 찾아보았다.

Spring Remote Shell

Spring Remote Shell은 SSH를 이용해 서버와 통신할 수 있게 해주는 모듈이다. 서버가 시작되면 2000번 포트가 열려 SSH통신을 할 수 있다. 굉장히 좋은 방법이라고 생각됐지만, 스프링 1.5버전부터 deprecated되었고, 2.0 버전부터는 아예 삭제될 예정이라고 해서 다른 방법을 찾아보았다.

commandLineRunner

CommandLineRunner는 스프링 어플리케이션이 시작될 때 임의의 명령들을 실행할 수 있게 하는 인터페이스로 run메소드를 오버라이드하여 사용할 수 있다. 이 방법은 FileServiceStorageService를 구현할 때 참고했던 Spring Boot Starter Project의 FileUpload 예제에서 사용되었던게 기억나서 사용해보았다. 하지만 원하는 결과가 나오지 않아 포기해야만 했다.

아예 새로운 프로젝트를 생성

시도했던 방법들이 모두 실패하자 기존에 구현했던 서버 어플리케이션으로는 도저히 불가능하다고 생각이 들어 아예 다른 스프링 프로젝트를 만들어 Spring Shell을 구현하기로 했다. 필요한 클래스는 MailService와 MailService에서 의존하고있는 클래스들이었다. 처음에는 해당 파일들을 복사해와서 Spring Shell을 이용해 구현하였다. 결과는 잘 나왔지만 추후 서버 어플리케이션의 MailService 클래스에 변동이 있을 시, 다시 파일을 복사해와서 빌드를 해줘야하는 문제점이있었다.

결론

결론적으로는 내가 원했던 그림의 cli어플리케이션을 구현하지 못했다. 당장 결과를 볼 수 있는 방법은 첫번째 방법과 네번째 방법이지만 develop 브랜치에 머지시키는 것은 무리라고 판단되어 첫번째 방법을 이용한 어플리케이션은 feature 브랜치에 남겨두었고 네번째 방법을 이용한 어플리케이션은 하드디스크에 저장시킨채로 마무리 지었다.

팀원들과 얘기해 본 결과 cli 어플리케이션을 구현하면서 이번 미션의 본래 목적인 컨트롤러에 있는 서비스 로직을 서비스 클래스로 옮겨야 한다는 깨달음은 충분히 얻은 것 같고, cli를 구현하기위해 이 이상 매달리는 것은 주객이 전도된 상황이라고 판단하여 마무리 짓기로 결정하였다.