기존에 진행했던 스프링 프로젝트(SimpleBBS)는 전통적인 MVC 기반 웹 애플리케이션이었다. 하지만 기업들의 채용 공고를 살펴보면 대부분 MVC보다는 RESTful API 제작 경험을 요구하고 있기 때문에 이번에는 API 애플리케이션을 만들어보기로 했다.
스프링 프레임워크와 관련 모듈을 활용하면 어렵지 않게 JSON으로 통신할 수 있는 API 서버 애플리케이션을 제작할 수 있다고 알고 있었는데 단순히 알고 있는것과 실제로 만들어서 얻는 경험은 다르기 때문에 이전부터 만들고 싶었던 주제였다.
주제는 원래 블랙 커피 스터디에서 진행했던 할 일 리스트 관리 프론트엔드 애플리케이션과 통신할 수 있는 서버였다. 하지만 이 서버에는 팀, 할 일 리스트, 할 일 생성, 수정, 조회, 삭제 기능만 있었고 인증이나 인가에 대해서는 아무런 기능도 없었기 때문에 회원 시스템이나 팀 검색 등 다양한 서비스를 추가하며 애플리케이션을 보강했다.
MVC 환경에서는 웹 브라우저를 통해서만 접근할 수 있지만 API로 통신한다면 웹 브라우저 뿐 아니라 모바일 애플리케이션 등 다양한 환경에서도 통신할 수 있기 때문에 더 장점이 많다고 생각한다.
그리고 이전에 순수 MVC 환경에서 애플리케이션을 제작했을 때의 단점(정보 전송 시 무조건 새로고침, 비동기 통신 어려움 등)을 생각해보면 역시 대세는 뷰를 서버 측에서 렌더링하는 것보단 프론트엔드와 백엔드를 분리하고 fetch API 등을 사용한 AJAX 통신으로 정보를 주고받는 것이 아닐까 생각한다.
환경은 SimpleBBS 때와 동일하게 Heroku 기반 프리 티어 애플리케이션으로 이번에는 두 개의 JawsDB 애드온을 사용한다. 하나는 애플리케이션의 데이터를 저장하는 메인 DB며 다른 하나는 테스트 용으로 사용하는 테스트 DB다.
이전에는 테스트는 로컬에서 진행하고 문제가 없다면 배포했지만 이번에는 Spring Rest Docs를 활용하기 때문에 배포되는 곳에도 테스트 용 데이터베이스가 필요하기 때문에 애드온을 하나 더 받아서 사용했다.
몰랐는데 Heroku의 JawsDB 애드온은 프리 티어에서는 1 시간 당 전송할 수 있는 쿼리의 갯수가 3600개로 한정되어 있다. 이를 초과하면 에러가 발생하기 때문에 로컬에서 테스트할 때는 로컬 DB를 테스트 DB로 활용하고 배포 시에는 JawsDB 애드온의 테스트 DB를 활용하도록 환경 변수로 지정하였다.
가끔 로컬에서 잘 작동하던 애플리케이션이 실제로 Heroku 노드로 배포되면 에러를 일으키는 경우가 있었다. 파일 시스템의 차이(로컬: Windows 10, 노드: AWS EC2 Ubuntu?)를 고려하지 않아서 발생한 것도 있었고 의존성이나 모든 설정이 완료된 로컬 환경의 IDE에서 그냥 버튼 하나 눌러서 실행시키는 것과 배포 환경에서 그래들 데몬으로 빌드하고 bootJar로 fat jar를 만들어서 실행시키는 것에는 확실히 차이가 있었던 것 같다.
그래서 서버 가상 머신을 하나 만들어서 실제로 배포하기 전에 해당 서버에서 빌드한 후 실행시켜서 접근해보고 문제가 없다면 배포하는 방식으로 꼼꼼하게 확인하였다.
새로 알게된 것은 가상 머신에서 그래들이나 자바 애플리케이션을 실행시키는 경우 root 권한으로, 즉 sudo로 실행시키는 경우가 많은데 이 경우 root 사용자의 환경 변수로 실행되기 때문에 현재 계정에 애플리케이션에서 필요한 환경 변수들을 등록해두었다 하더라도 제대로 반영되지 않는다. 이때는 sudo의 E 옵션으로 계정의 환경 변수를 적용해야 한다.
대부분 테스트는 IDE에서 실행하고 그 결과를 출력 창에서 확인했기 때문에 별도의 보고서를 확인한 적은 없다. 하지만 IDE를 활용하지 않는 가상 환경 콘솔에서 그래들 데몬으로 빌드 및 테스트를 진행할 경우 어떤 테스트가 실패했고 그에 대한 스택트레이스만 간략하게 출력되기 때문에 무슨 에러가 발생했는지 확인하기 까다로웠다.
그러던 중 로그에서 빌드 디렉토리에 레포트가 저장되었다는 메시지를 보고 한번 확인해봤는데 아래처럼 html파일로 편하게 볼 수 있도록 테스트 보고서를 제공하는 것을 알 수 있었다.
지금은 모든 테스트가 통과했기 때문에 초록색 100%로 나타나지만 실패한 테스트가 있을 경우 빨간색으로 나타난다. 어떤 테스트 클래스의 어떤 테스트 메서드에서 어떤 예외가 발생했는지 상세하게 알 수 있기 때문에 추후 디버깅 시 적극 활용해야겠다.
스프링을 배우면서 꼭 넘고 싶었던 산이며 아직 넘지는 못했지만 이번에 JWT를 활용하면서 Authorization 헤더에 JWT가 들어있는지 검사하고 JWT에 저장된 사용자 정보를 기반으로 리소스에 대한 접근을 제한하는 인가를 적용하는 데 사용하였다.
스프링 시큐리티는 일종의 필터 체인으로 사용자 요청이 서블릿에 도달했을 때 요청을 확인하고 차단하거나 허용할 수 있다. 이 필터 체인에 Authorization 헤더의 JWT를 검사하는 필터를 추가하고 로그인 컨트롤러에서 인증 서비스를 사용하여 인증 및 인가를 구현할 수 있었다.
아직 정확하게 이해하지 못했기 때문에 좀 더 연구가 필요할 것이다.
이 SimpleTodoList 애플리케이션을 개발하고 배포를 마무리 할 때 제일 짜증났던 부분이 이 CORS 설정이었다. 물론 스프링 시큐리티에서 CORS 옵션을 활성화하고 필터를 등록해주면 끝나는 간단한 문제지만 문제는 프론트엔드 페이지를 분리해서 구현할 때 사용했던 localhost, 즉 로컬 환경이었다.
크롬 / 파이어폭스 브라우저는 localhost의 CORS를 허용하지 않는다. 이 사실을 몰랐기 때문에 스프링 시큐리티에서 CORS 설정이 잘 안된건지, fetch API를 잘못 사용하고 있는건지, 뭐가 문제인지 도저히 알 수 없어 많은 시간을 허비했었다.
그래서 일단 백엔드 API 서버만 배포하고 테스트 코드와 Postman으로 API 요청을 검증한 후 문서를 제공하는 방식으로 배포하기로 결정했다. 추후 정적 페이지 호스팅 서비스를 활용해서 프론트엔드에서 백엔드 애플리케이션과 통신하는 방식으로 구현하거나 애플리케이션 자체에서 정적 페이지를 내장하는 방식으로 구현하고자 한다.
이 외에도 서버 측에서 별도로 허용하지 않는 이상 fetch API에서는 제한된 헤더만 접근할 수 있다는 것, 스프링 시큐리티의 CORS 옵션에서 크레덴셜을 허용하는 것은 교차 출처 요청에서 자바스크립트가 인증 정보를 요청에 포함할 수 있도록 허용한다는 것이며 그렇기 때문에 보안을 위해 서버측에서 허용 출처(Access-Control-Allow-Origin) 설정에 애스터리스크("*")를 사용할 수 없다는 것 등 다양한 웹 지식을 알 수 있던 경험이었다.
배포 때마다 그렇지만 생각했던 것과 다른 상황이 계속 나타나고 이걸 해결하느라 진땀을 빼는 시간을 생각하면 정말 스테이징 서버를 도입해야 할 것 같다. 물론 실제로 서비스되는 애플리케이션이 아니기 때문에 무중단 배포같은걸 도입할 것 까진 없겠지만 생각해 볼 만하다.
특히 MVC 때와는 달리 이번에는 API 서버기 때문에 다른 서버, 즉 교차 출처(Cross-Origin)에서 전송되는 요청도 고려하다 보니 막바지에 시간이 더 많이 들었다. CORS와 localhost가 정말 까다로웠지만 그만큼 알아가는 것도 많았기 때문에 좋은 경험이 됐다.
추후 관련 문제가 해결되면 정적 페이지 호스팅을 통해 API 서버와 통신할 수 있는 프론트엔드 애플리케이션도 만들어보고 싶다.