[중간 회고] MEETUP_STUDY 프로젝트 - "호기심 충족 위한 SPRING 학습"

이정환·2023년 7월 25일
0

[개인 프로젝트]

목록 보기
1/8
post-thumbnail

시작 동기 및 프로젝트 과정

NESTJS -> EXPRESS -> SPRING

이 프로젝트는 42SEOUL 공통과정의 마지막 과제인 웹+게임 프로젝트(NESTJS 백앤드)를 마친 이후에 시작했다.

NESTJS를 레고 조립하듯 사용만 하는게 아니라 작동 방식 및 흐름 등이 궁금했다. NESTJS 는 EXPRESS의 프레임 워크라고 하니 EXPRESS를 찾아보고 간단히 서버 토이 프로젝트 만들어 봤다. 확실히 코드 치는 생산성은 NESTJS가 압도적이었다. 파일 구조도 일관되고, 특정 기능하는 코드들의 위치도 정해져 있었고, 자동완성 기능도 좋았다. EXPRESS 코드는 사람마다 치는 구성하는 방식이 달랐는데 NESTJS는 비슷해서 실력 쌓기가 보다 수월할 것이라 판단했다. 하지만 구글링 하는데 별로 인상깊은 자료가 없었다.

그러다가 SPRING과 유사하다는 것이 떠올랐다. 혹시나 싶어 SPRING 검색했더니 NESTJS와 유사한 기능을 설명한거 같은데 내용이 더 많고 깊었다. 가령 'nestjs interceptor' 과 'spring interceptor' 검색했더니 연관 검색이나 설명 등 깊이와 넓이가 너무 차이 났다. '아.. SPRING을 잘 알면 자료가 적은 NESTJS로 학습하는 것보다 더 빠르게 실력 키울 수 있지 않을까'라고 생각했다. 그때부터 'SPRING을 학습해보자' 라고 마음먹었다.

(결론은 JAVA SPRING 학습 선택 잘한 것 같다. 웹 모양만 만드는게 아니라 더 잘 동작될 수 있는 방법에 대해서도 고민 할 수 있게 되었다.)

REACT -> NEXTJS -> VUEJS

spring 은 nestjs 와 비슷하다고 하니 금방 습득 가능할거라 판단했다. 그런 이유로 관심있지만 잘 모르는 REACT를 먼저 학습해서 프로젝트를 시작하자고 결정했다.
유데미에서 강의 보고 토이프로젝트 만들기 시작했다. MATERIAL UI 강의도 결제해서 같이 보면서 적용하기 시작했다. 근데 하다보니 시간이 너무 많이 들었다. 생각대로 작동되지 않아서 괜히 시간낭비 하는 경우가 많았다.
그러다가 REACT의 프레임워크라는 NESTJS를 사용하면 보다 코드가 수월하다는 웹+게임 프로젝트의 한 팀원 말이 떠올라서 NESTJS 강의 유데미에서 결제하고 토이 프로젝트 시작했다. 라우팅 처리를 파일로 관리하는 것부터 시작해서 리액트보다 간단하게 기능구현하는데 수월했다. 이를 통해 진짜 진짜 기본적인 토이 프로젝트를 제작했다. 오케이!

이제 SPRING으로 서버 만들면서 프론트 동시에 코드 만들자 라고 결정했다. 그런데... SPRING 코드 짜고 보는데 NESTJS 보다 뭐가 더 많았다. 모습도 뭔가 달랐다. 지금와서 어느정도 익숙해지고 보니 SRPING SECURITY, DI, IOC, SOLID, JDBC, JDBC TEMPLATE, JPA, REPOSITORY 등 개념이 선행 배경지식으로 탑재되어 있있었으면 훨씬 수월했으리라 생각한다. 이때 유데미에서 SPRING 강의 결제하고 수강하면서 몇번 좌절했다. 와... 내가 아는 NESTJS랑 좀 다른거 같은데?... 아무튼 자주 하다보니 익숙해지고 수월해졌다.

SPRING 어느 정도 익숙해지고나서 NEXTJS와 동시에 코드 짜려고 했는데, NESTJS와 REACT 개념 및 코드의 많은 부분이 익숙하지 않게 여겨졌다. 안되겠다 싶어서 SPRING으로 MEETUP 서버 프로젝트 먼저 시작하고 프론트는 나중에 다시 학습 및 프로젝트 하기로 결정했다.

어느 정도 서버에서 소셜로그인, 모임 룸 CRUD, 관리자 뭐 등등 API 구현한 상태에서 REACT와 NEXTJS 공부 다시하려다가 VUEJS 공부하기로 했다. 예전에 다른팀 코드 본 적 있는데 러닝 커브가 상대적으로 낮았던 기억이 났다. 또한 현대오토에버에서 프론트엔드로 근무중인 42SEOUL 동료가 자신 속한 사내 팀은 VUE3으로 프론트엔드 코드 리팩토링 중이라고 추천했다.
유데미에서 결제하고 일주일 정도 시청하니 대강 감 잡혔고 바로 프로젝트 시작했다. VUETIFY와 함께 디자인하며 VUEX로 상태관리 할 수 있는 대략적인 모임사이트 만들 수 있었다. 구현 한 기능 중에 기억에 남는 건, 유저가 모임룸에 join 유무에 따라 댓글 컴포넌트를 렌더링 시킬지 말지 선택하는 부분이다. 이유 모르겠지만 기억에 남는 기능이다.

일단은 여기까지 해보니 전반적으로 서버와 API 통신 잘 작동됐다. 이에 추가로 할 일은 서버 쪽 코드 부터 먼저 수정하고 나중에 하기로 결정했다. 할 일이라면 아토믹하게 설계구조 바꾸고, 상대관리, 쿼리최적화, 짜잘한 기능 버그 수정 등이 남아 있다. 학습 목적으로 만든 프로젝트인 만큼 앞으로 지속적으로 유지 보수 및 기능 추가 해 나갈 계획이다. 디자인도 부족하고.. 내부적으로 프레임워크가 어떻게 동작되는지도 알아보고 싶다.

다시 SPRING 으로

VUESJS meetup 프로젝트 전에 유데미에서 SPRING 학습하기 시작했다. 잘 몰랐지만 한번 쭉 본 강의 내용이 전부 레거시 였다. 30시간 넘게 본 거 같은데 ㅡㅡ; 거의 강의 다 봤을때쯤 강의자는 현재 사용한다는 기술로 바꿔서 강의를 재 업로드 했다. 뭔가 시간 낭비한 것 같았지만, 다시 보면서 기술이 어떻게 변해가는지 알 수 있었다. 현재 사용하는 기술의 편리함과 이렇게 있기까지의 불편한 배경을 알 수 있었다. 가령 프로젝트 셋팅 할때 spring boot starter 사용하면 손쉽고 빠르게 작동되는 웹 프로젝트 셋팅 할 수 있지만, 그런걸 모를때는 이클립스에 라이브러리 추가할때 관련 jar 파일 다운받고 라이브러리 적용하고 삽질하고 그랬었다. 아무튼 다시 강의 보고나서 어떤 식으로 코드 짜면될지 방향성 잡을 수 있었다. 소셜로그인 구현 방법에 대해서는 관련 강의 챕터가 없어서 유튜브에서 찾아보면서 관련 기능에 대해 학습했다.

코드 치기전에 가장 먼저는 DB 스키마 부터 만들었다. 최대한 중복코드 줄여서 정규화 시켰다. 다대다는 1:다 다:1로 분할해서 조인테이블로 구현했고 각 조인테이블은 각각의 특성에 따라 일부 컬럼을 추가하기도 했다. 즉시로딩은 지양하며 즉시로딩이 디폴트인건 지연로딩으로 수정했다. 최대한 양방향 보단 단방향 맵핑이며 1:다 에서 다 쪽을 주인으로 셋팅했다. 쿼리 줄이기랑 무한참조관계를 방지하기위해서다. 하지만 코드 치면서 떠오른 필요한 기능이나 업로드 파일 및 이미지 파일 업로드 테이블 추가하니 정규화를 깨뜨리는게 파악하기 수월해보여서 테이블 쪼갰다. 사실 이때부터 이 부분 이렇게 처리하면 안될 것 같은 우려가 계속 든다.

코드 치기 시작했다. 소셜로그인 구현할때는 전반적인 인증 과정이 스프링에서는 어떻게 이뤄지는지 몰라서 무조건 authentication 객체를 만들어야 되는줄 알았다. 주로 세션으로 인증 구현할때 해당 기능 까지 구현해서 객체에 값 저장해서 사용하는 것 같다. 일단 따라서 해당 기능 구현해둔상태에서 JWT로 방향을 틀어서 인증 로직 구현했다. 추가로 소셜로그인도 관련 객체가 이미 있어서 DefaultOAuth2UserService 상속받아서 구현했다. 거기서 받은 유저로 따로 DB에 유저 생성해서 관리하기로 했다. 스프링은 뭔가 만들필요 없이 찾아서 가져다 쓰면 된다고들 한다. 역시 oauth2 인증 후 성공하면 AuthenticationSuccessHandler를 상속받아 동작할 구현체 만들고, 실패하면 AuthenticationFailureHandler를 상속받아 동작할 구현체가 동작하게 만들었다. 유저 인증이 끝나면 JWT 엑세스토큰과 리프레쉬토큰 두개 발급받아서 리프레쉬 토큰으로 액세스 토큰 계속 갱신시켜서 쓰려고 했는데 코드가 복잡해져서 비활성화 시켰다. 아무튼 엑세스 토큰 쿠키로 발급해서 클라이언트로 전송하게 만들었다.

JWT 토큰 발급 및 유효성 처리할 url은 OncePerRequestFilter를 구현한 구현체와 인터셉터에서 컨트롤러 요청전에 처리하게 만들었다. filter와 interceptor의 차이가 뭘까 고민해봤는데 일단 소속이 다르고 filter는 전역적으로 일련의 과정에서 무조건 실행되는거고, Interceptor는 컨트롤러 별로 선택해서 일부 요청 전에만 작동되기 할 수 있는 차이가 있는 것 같다. 또한 소속과 범위가 달라서 예외처리할때 filter는 스프링 시큐리티 실행되는 영역에서 AccessDeniedHandler 상속받은 구현체로 예외 처리했고, interceptor를 포함한 그외 예외처리는 @ControllerAdvice로 구현한 객체에서 처리하도록 구현했다.

유저, 모임룸 CRUD 엔티티, DTO, 컨트롤러, 서비스, 레포지토리 개발했다. 모임룸은 조회수 기능에 Redis 추가했는데 이상하게 몇번 클릭안했는데 80이 넘는다. 아마 내부적으로 여러번 실행되는 부분이 있지 않나 싶다. 레디스 사용한 이유는 싱글 스레드라서 동시성 이슈 방지할 수 있을거라 판단했다. 만들고 나니 컨트롤러와 서비스에서 DB로 예외처리 확인하기위해 던지는 쿼리가 무척 많았다. 다른 방법이 있을 것 같은데 일단은 기능개발을 최우선으로 여기고 넘어갔다.

스케쥴링 기능 구현해봤다. 마치 스마트폰 알림에 제품 광고 뜨는 것처럼 추천 모임 목록을 매일 오전 9시 1분에 이메일로 전송하도록 구현했다. 경험용으로 구현했다. 아마 실제 서비스에서는 스케쥴링 기능하는 서버는 꺼지지 않도록 다른 서버에서 관리하게 설계되지 않을까 싶다. 스케쥴링과 관련해서 batch가 쓰이고, batch를 쓰면 특정시간에 한꺼번에 수정 필요한 데이터를 일괄 수정할 수 있다고 하여 경험해보고자 코드 셋팅했다. 실제 해당 기능까지 구현하진 못하고 테이블 만들어지는 정도에서 비활성화 시켰다.

aop 로깅 구현해서 기능개발에 디버깅 보다 효율적으로 할 수 있었다. 로그 레벨 (일반, 에러) 및 날짜별로 구분해서 파일 생성되도록 기능 구현했다.

스웨거 구현했다. 해보고 나니 API 문서를 코드에서 관리하는 장점은 있지만 작성된 코드에 괜히 스웨거 코드 메소드별로 서너줄씩 추가되니 전체적인 코드 가독성이 떨어졌다. 또한 소켓 기능 추가되어 있는 경우까지 고려하면 스웨거를 yml문서로 만들거나 따로 노션같은 툴로 관리하는게 더 낫겠다는 생각이다.

git action 추가했다. CI/CD 라고해서 지속적 통합 및 배포 라는데, 주로 깃허브에 푸쉬후에 머지전에 테스트 후 머지 유무 체크하는 용도로 사용할 수 있도록 구현했다. 그 전에 테스트 케이스 필요해서 junit으로 몇개 만들고 Jacoco로 테스트 커버리지 체크 및 문서제작하도록 하고 테스트는 데이터를 h2 DB에서 관리되도록 구현했다. h2는 메모리에 데이터 저장되어서 일시적으로 사용하기 수월할 것이라 판단했다. 테스트는 규칙 없이 일단 컨트롤러들을 검증해본다는 이유로 mock 요청으로 테스트 구현했다. 하지만 작성하면서 지속적인 한계가 계속 발견됐고 갈아엎고 다시 제작했다. 흠... 새로 제작한 것도 다른 사람들이 만든 테스트 케이스와 비교해보면 뭔가 규칙도 없고 덩어리가 크다. 나중에 TDD 추가로 학습 할 필요가 있겠다. 스프링에서 의존성주입은 생성자주입, 셋터주입, 필드주입이 있는데 필드주입을 하게되면 테스트 할때 수동적인 객체 주입으로 테스트 하기 어려움을 알 수 있었다. 셋터주입은 일관성이 떨어지고 개발자가 직접 주입한다는점에서 지양 할 필요가 있어보인다.

각 엔티티에 validation 추가해서 작동시켜봤는데 nestjs에서의 유효성 검사가 더 직관적이고 익숙한 기분이다.

docker compose 로 jdk11 기반 spring, mysql, adminer, redis 컨테이너 구현했다. 하지만 테스트코드와 어플리케이션 코드와 환경변수 사이에 싱크가 계속 맞지 않아서 굉장히 삽질 많이 했다 ㅡㅡ...; 로그는 엄청 많이 뜨는데 자꾸보다보니까 '$'이게 보였고 환경변수 사용할때 쓰는 기호임을 단서로 겨우 문제 해결할 수 있었다.

application.yml , application-dev.yml 로 실행 상황에 따라 환경 다르게 셋팅하려했으나 일단 기능구현 우선으로 비활성화 시켰다. 유데미 레거시 코드 강의 볼때는 maven 으로 만들고 xml 코드도 손댔는데 gradle로 만들고 application.properties 를 application.yml 로 만들어서 사용해보니까 훨씬 작성도 편하고 가독성도 높다.

댓글 CRD, 공지사항 CRUD 생성했다. 스프링 시큐리티는 애초에 url 별로 유저 역할에 따른 접근 권한 셋팅 할 수 있는 점이 새로워 보였다. 뭔가 스프링은 표준적인 코드 제작 권장 방식이 있는 것 같은데 잘 모르겠다. 하다보면 익숙해지겠지만..

코드 작성하다가 테이블을 통째로 바꿔야할 상황이 생겼다. 이때 고민한건 새로 처음부터 시작하냐 아니면 기존코드 수정하냐 였다. 결국은 새로 시작하려다가 더 시간 걸릴 것 같아서 기존 코드 수정하기로 결정했다. 고민할때는 코드 별로 안 짠 줄 알았는데 돌아보니 좀 많이 짜놔서 새로짜기 무척 부담스러웠다. 아마 그 때 당시는 그 정도까지가 내 수용력이었던 것 같다.

DB와의 연결은 스프링 JPA를 사용했다. 레거시 코드 인줄 모르게 스프링 배울땐 JDBC 나 JDBC template 로 짜야되는줄알고 디비 연결하고 sql 짜고 그랬다. 나중에 알고보니 일련의 DB mapper 변천과정중 사용한 기술이었다.
가령 JDBC는 mysql 이나 postresql 등을 가리키는 DMBS 를 java spring 에 연결 부위를 추상화 한 인터페이스를 제시한거고, jdbc template 는 그 중 중복 코드들을 더 추상화 시킨거고, mybatis 는 sql 을 xml 로 분리해서 코드 가독성을 향샹시켰고, orm 을 java에서는 jpa라는 인터페이스와 hibernate라는 구현체로 구현 후 sql 종속적 코드스타일을 객체지향 식으로 바꾸고 DB와 객체지향 사이의 패러다임 불일치를 해소시켰고 메소드로 dbms와 소통하기에 db 교체를 쉽게했고, spring jpa 는 레포지토리로 한번 더 추상화 해서 엔티티 매니저나 영속성 컨텍스트가 함수내부에서 작동되도록 추상화 했다는 점을 알 수 있었다.
이렇게 알고나니 이전에 했던 작업들이 java spring 과 DB 를 보다 손쉽게 연결하고자 하는 기술 발전 과정의 부산물 임을 알 수 있었다.

이렇게 대강 어느 정도 만들어두고 이때부터 Vue, Vuetify, Vuex 이용해서 프론트엔드 공부 및 코드 짜기 시작했다.

Spring + Vue = HTTP 통신 성공 이후

대략 Spring 서버 실행 후 Vue에서 접근해서 API 통신 잘 됐다. 그럼 이제 남은건 서버와 클라이언트 최적화가 남았다. 먼저는 최적화를 어떻게 할 수 있을까 이것저것 알아봤다. 둘러보니 서버 쪽은 n+1 쿼리 문제 해결을 위해 fetch join을 쓰고, 검색향상을 위해 인덱싱 할 필요가 있다고 판단했다. 쿼리 최적화는 userRepository에서 @EntityGraph() 를 사용해서 적용했다. 콘솔에 출력되던 2번의 쿼리가 1번으로 압축됐다. @Query() 를 쓰면 inner join 이 되고 @EntityGraph()를 쓰면 outter join 차이가 있어서 프로젝트에 적용한 fetch join 은 @Query()로 수정할 필요가 있겠다. 트래픽이 없어서 인덱싱 할 필요가 있을까 싶었지만, 경험 해보자는 마음으로 UserEntity에서 카디널리티가 높은 컬럼을 찾아 적용하려고 했다. 이메일은 유니크하지만 인덱스 테이블에서 데이터 변경으로인한 정렬 때 비효율만들거 같았다. 결국 유저네임을 유니크한 값으로 만들고 인덱싱 했다.

프론트엔드는 서버쪽 끝나고 손댈 계획이다.

중간 회고 이후 할 일

프로젝트 내 모든 항목에 fetch join 연결할 거다. read 가 많아서 인덱싱 필요할 부분에 인덱스 적용할 거다. 예외처리 너무 많이 한 것에 대해 낭비되는 쿼리 줄일 것이다. 코드를 깔끔하게 짜는 방법도 알아봐야겠고 파일 구조도 깔끔하게 만드는 규칙 정해야겠다. 분산 DB 시스템 위한 DB 클러스터링과 리플리케이션 그리고 트랜잭션의 프로파게이션과 아이솔레이션을 적용시켜 보고 싶다. 도커 스웜과 쿠버네티스도 해보고 싶다. 아우터서클 프로젝트 중에 쿠버네티스 있다고 하는데 나중에 레지스트 할 계획이다.

소셜로그인 추가, API 스트레스 테스트, 위에서 언급한 레디스 문제 해결, 환경변수 통합, 환경별 AOP 로깅 출력 유무, 도커 컨트롤 위한 makefile 이나 shell 작성, 파일 구조정리, DB 정규화, 룸 파일 업로드시 에러수정 등등 할일이 많다.

이것 말고도 이미 구현한 기능에서 뭔가 하나씩 고치면 다른데서 문제생기고 있다. fetch join 구현한다고 괜히 엔티티 손댔는데 그뒤에 갑자기 룸 파일 업로드와 유저 이미지 업로드 이후 파일 저장이 잘 안되는 것 같은 문제가 발생했다 ㅡㅡ;; 할 일이 많다.

중간 회고 결론

JAVA SPRING 은 엔터프라이즈 급 프레임워크이면서 평이 별로 좋지않아서 선호하지 않았다. NESTJS 를 좀 더 잘 알고 싶어서 공부해 본 건데 하다보니 이것 나름의 매력이 있다. 만약 NESTJS 만 계속 파고 , 취업 했으면 기능 개발에만 에너지를 쏟고 있지 않을까 싶은 생각 든다. 결론은 JAVA SPRING 학습하길 잘했다.

0개의 댓글