5주간 개인 프로젝트를 진행하였다.
혼자서 해보는 개인 프로젝트가 처음이라 시작하기 전 약간의 설렘이 있었지만 프로젝트 경험이 적어 혼자 해낼 수 있을까하는 두려움, 막막함이 더 컸다.
다행히 같은 주제로 프로젝트를 진행한 다른 분들의 도움을 많이 받기도 하고 내가 아는 선에서 도움을 줄 수 있는 부분도 있어서 개인 프로젝트지만 팀 프로젝트처럼 으쌰으쌰하면서 매일 늦은 시간까지 남아 프로젝트를 진행할 수 있었다.
이제 마무리하는 시점에서 프로젝트 진행 타임테이블, 내용, 기술적 의사결정과 이전에 비해 더 나아진 점, 아쉬운 점을 정리해보고자 한다.
일정
2024. 01. 24. ~ 2024. 02. 28.(5주)
기술적 의사결정
Java 17
운영체제나 플랫폼에 독립적인 프로그래밍 언어로 객체지향적 설계, 모듈화를 통해 재사용성 측면에서 좋음
LTS 버전으로 지원 기간이 길고 Spring boot 3.0부터는 17 이상 지원하기 때문에 호환성 측면에서 선택
gc 처리량, 지연시간면에서 이전 버전에 비해 성능이 향상됨
Spring Boot
스프링
의존성 주입 - 자바와 마찬가지로 객체지향적 설계 가능
제어 역전 - 스프링과 같은 프레임워크가 제어 흐름 권한을 가짐, 객체의 의존성을 역전시켜 객체 간 결합도를 낮춤
AOP - 공통관심사 분리하여 핵심 로직을 건드리지 않으면서 다양한 기능 추가 가능
스프링 부트
스프링 프레임워크는 XML 설정파일, Java config등 구성을 정의해야 하지만 부트는 내장되어 있음
starter 패키지를 통해 의존성을 자동으로 추가
was 서버 등 따로 설정 해주지 않아도 apache tomcat이 내장되어 있음
yaml, properties 파일을 이용해 외부 환경 설정 간편하게 가능
JPA
객체-관계 매핑(ORM)을 위한 프레임워크로 JDBC를 내장하고 있음
JPA 구현체는엔티티 클래스와 관련된 SQL을 자동으로 생성하고 실행하므로 객체 중심 개발 가능
데이터베이스의 변경이 생기더라도 SQL을 모두 수정하지 않아도 되기 때문에 유지보수가 쉬움
기존에 학습할 때 JDBC, MyBatis 방식 모두 사용해보았는데 데이터베이스 액세스를 위한 시간이 많이 들어서 JPA 방식을 사용
MySQL
테이블 간 관계 설정이 필요해서 관계형 데이터 베이스 선택
MySQL이 Oracle보다 용량을 덜 차지하고 처리 속도가 빠르고 대용량 데이터 처리에 용이한 편
이전에 Oracle을 사용했었는데 툴 다루기가 MySQL이 좀 더 쉬운 것 같았음
JWT
인증 정보를 클라이언트가 가지고 있는 방식으로 인증 정보를 서버가 가지고 있는 세션 인증 방식에 비해 서버 부담이 적음
JWT는 디지털 서명이 존재하기 때문에 토큰 내용 변조 여부나 만료시간 등 사용자 인증 정보를 서버에서 확인할 수 있고, 데이터베이스 조회 없이 토큰 검증
QueryDSL
QueryDSL이란? SQL 쿼리를 자바 코드로 작성할 수 있도록 도와주는 라이브러리
기본적으로 JPA 방식은 JPA가 메서드명을 자동으로 인식해서 쿼리문을 작성하기도 하지만, 복잡한 쿼리문일 경우 직접 작성해야 하는 경우가 있는데 이럴 때 오타나 문법 오류를 찾기 어려움
자바문법으로 쿼리문 작성이 가능하고 여러 조건에 대해 메서드를 나눠서 객체지향적으로 설계할 수 있음
프로젝트 내용
◾Java, Spring boot 기반 REST API / JPA 방식
◾보안 및 인증 - JWT, Spring Security / 그 외 QueryDSL, AWS S3 등 사용
◾Artillery 부하테스트를 이용하여 성능 측정 및 개선
✅ 프로젝트 설계
ERD 설계
- 외래키 참조 관계를 신경써서 설계하려고 했음
- Soft Delete 방식을 위해 유저 상태 필드 추가
- 피드백) 데이터베이스를 H2로 할 거면 USER가 예약어이기 때문에 테이블명 정할 때 유의하기
(개발 DB는 MySQL로 했지만, 테스트코드 때 잠깐 DB를 H2로 시도했다가 USER 때문에 힘들었음)
API 명세서
- 노션으로 초기 작업
- Postman으로 body 응답까지 포함하여 명세서 완성
- 피드백) 명세서 작성 시 header까지 꼼꼼하게!
✅ 기능 구현
- 유저 - 회원가입 / 로그인 / 카카오 로그인 / 프로필 이미지(AWS S3)
- 관리자 - 권한 부여 / 강제 탈퇴 / 게시글과 댓글 수정 및 삭제
- 게시글 - QueryDSL 이용한 검색 기능/ 삭제 시 댓글, 좋아요도 함께 삭제 되도록 casecade 적용
- 댓글 - 삭제 시 좋아요 함께 삭제 되도록 casecade 적용
- 좋아요
- 팔로우 - 내가 팔로우 한 유저들의 게시글 목록 조회
✅ 코드 리팩토링
- 예외처리 모듈화 - RestControllerAdvice 이용하여 도메인 단위로 CustomException 분리
- Spring AOP로 데이터 로깅, 수행시간 측정
- Service 계층 인터페이스 분리
- Service 비즈니스 로직에서 사용자 확인, 기존 게시글 확인 등 반복되는 조건문은 따로 분리하여 객체지향적 설계
- 게시글 목록 조회 API 성능 개선 작업 - fetch join과 batch size 사용
✅ 테스트
- 부하 테스트 - 게시글 조회 API 성능 개선 작업에 이용, 더미데이터 유저 1000명, 게시글 1000개로 약 1만번 호출
- 단위 테스트 - 서비스 계층 단위테스트
- 통합 테스트 - 컨트롤러 통합테스트(별도 DB 생성)
이전에 비해 나아진 점
1. 프로젝트의 전체 흐름 경험
개인 프로젝트 경험이 없어서 프로젝트 설계부터 기능 구현, 테스트 코드 작성까지 전반적인 과정을 혼자서 다 경험해봤다는 점에서 꽤 의미가 있는 프로젝트였다. 사실 팀 프로젝트를 하면서 유저나 게시글 등 핵심 기능을 맡지 못해서 아쉬움이 컸고 해보지 못한 역할에 대한 갈증이 있었는데, 1인 N역을 소화해야 했기에 오히려 다음 협업을 하게 된다면 중요하게 소통해야 할 부분에 대해서도 잘 보였다.
2. 인증 및 보안 - JWT, Spring Security
유저 관련 기능의 기본 중 기본인 JWT, Spring Security 기능을 처음 해보았다. 이전에는 팀원이 유저 관련 기능을 미리 세팅해두어서 그저 토큰에 있는 사용자 정보를 뽑아쓰는게 다였다면, 이번에는 토큰을 생성하고 클라이언트가 인증 요청을 했을 때 처리하는 과정을 전부 다 혼자서 구현했다.
게다가 화면단을 만들어 회원가입, 로그인 기능을 테스트하였는데 인증 관련 오류가 계속해서 발생하여 인증 필터를 뜯어보고 브라우저의 응답과 요청을 분석해볼 수 있는 좋은 기회가 되었다. 화면까지 연결해보지 않았다면 postman으로 오류없이 잘 작동하는구나 하고 말았을테지만, 화면단과 연결해봄으로써 클라이언트와 서버단의 인증 방식을 일치시켜 코드 리팩토링을 진행할 수 있었다.
3. 테스트 코드
테스트 코드의 중요성은 익히 들어 알고 있었다. 사실 예전에 테스트 코드를 간단하게 작성하는 법을 배운적은 있었지만 단위/통합 테스트로 나누어서 진행해본 적은 처음이었고, 특히나 DB 영향 없이 Mock 객체를 만들어 단위테스트를 해본 것은 굉장히 좋은 경험이었다. 앞으로는 기능 개발에 있어 테스트를 동시에 진행해야 겠다는 생각이 들었다.
4. 부하 테스트, 성능개선
이번 프로젝트에서 가장 큰 성과가 아닐까싶다. 사실 10만건을 목표로 잡고 부하테스트를 해보고 싶었는데, 1만건으로도 큰 성능 저하가 확인되어서 여기서 개선작업을 한 것만으로도 만족스럽다. N+1 문제에 대해 고민해보면서 fetch join으로만 리팩토링이 끝날 줄 알았지만 생각하지도 못한 페이지네이션 문제로 batch size까지 적용해볼 수 있어서 좋았다. 사실 fetch join이나 batch size가 N+1 문제를 해결하는 보편적인 방법인데, 이제서야 적용을 해봤다는게 좀 머쓱하긴 하지만 앞으로 비슷한 기능 구현에 있어서 처음부터 바로 더 좋은 성능을 낼 수 있는 코드를 적용할 수 있는 배움의 기회가 됐다고 생각한다.
중간에 멘토링 받으면서 부하테스트 시나리오를 짜고 이 문제를 이런 방식으로 해결해보고 싶다 했을 때 '부하테스트를 해보지 않아도 문제가 발생할 것이라고 인지가 된다면 그냥 고치면 되지 않나요?' 라는 말을 듣긴 했지만 부하 테스트 없이 코드를 고치고 넘어갔다면 부하테스트 방법이나 더 좋은 성능을 낼 수 있는 방법은 없는지에 대한 고민은 덜 했을거라 생각한다. 물론 멘토님 말도 맞는 말이기 때문에... 다음부터는 조회 기능을 구현할 때 여러 연관 관계의 엔티티가 있다면 fetch join이나 batch size를 처음부터 바로 적용하겠지...!
5. 깃 관리
이건 개인 프로젝트이긴 하지만 같은 주제로 프로젝트를 하는 사람들끼리 팀으로 진행하면서 도움을 많이 받은 부분이었다. Issue, Pull Request, 리드미, 커밋 메시지 관리를 굉장히 꼼꼼하게 하는 분이 계셨는데 이런 부분에 있어서의 중요성을 많이 알려주셔서 도움을 많이 받았다. 이슈 관리 하면서 기능 구현에 있어서 진척도나 해야할 일에 대해 놓치지 않고 챙길 수 있었고 깃 브랜치 나누어서 기능 단위로 작업하면서 팀 협업 시에 어떤식으로 브랜치를 쓰면 좋을 지에 대해서도 적응해 볼 수 있었다.
아쉬운 점
1. 배포
나는 피드백을 받고 반영하는 것에 강한 사람이다. 이전 회사에서도 사업을 기획하는 단계에서 시도때도없이 팀장님한테 불려가서 피드백을 받아 더 나은 기획서를 만들기 위해 고민하고, 정기 회의를 열어 사업에 참여하는 위원들에게 설문조사를 받아 또 사업 내용을 수정하고, 사업 대상자들에게도 꾸준히 피드백을 받아서 반영하는 일을 했었다.
그래서 이번 프로젝트를 시작 할때는 화면단까지 보기좋게 다 만들어서 배포하고 유저 피드백까지 받아 반영해보고 싶었다. 하지만 초반에 프로젝트를 한번 엎어서 다시 시작하고, 화면단 만들어서 연결하는 과정에서 맞닥드린 인증 오류로 인해 시간이 많이 지체되어 화면단도 다 못 만들고 배포도 못해봤다. 굉장히 아쉬워... 계속해서 기능 추가하면서 화면단 만들고 배포까지 해보고 싶다.
2. 테스트 코드 작성
repository, service, controller 레이어가 나누어지는 이유가 있다. 그래서 각각 다 단위테스트를 해보고 싶었다.
하지만 테스트 코드 작성하는 게 익숙지 않아 처음에 controller 테스트를 하다보니 결국은 통합테스트가 되어버렸고, 테스트용 DB를 별도로 분리하긴 했지만 기능 구현하는 중에 Postman을 돌려서 테스트한 것과 결론적으로는 다르지가 않았다. MockMvc를 이용해서 테스트를 하는 거라면 mock 객체를 만들어 테스트에서만 유효한 데이터를 적용해서 테스트를 했어야 했는데 그냥 DB에 데이터 세팅을 다 해두고 테스트한거라 크게 의미가 없는 느낌이 들었다.
그리고 뒤늦게 service 레이어 단위테스트를 했는데, 그래도 이번엔 DB 영향없이 독립적으로 테스트를 잘 했다. 하지만 시간이 부족해서 메인 서비스 기능인 게시글 기능만 테스트했고 아직 다른 기능은 테스트를 해보지 못했다. service에는 핵심적인 비즈니스 로직이 다 포함되어있기때문에 다음에는 꼭 단위테스트를 하면서 동시에 기능구현을 해야겠다.
3. 실제 서비스 환경과 비슷하게 사용자 입장에서 테스트 시나리오 짜기
내가 진행한 부하테스트 방식은 API 하나를 1만번 호출하는 방식이었다. 하지만 테스트는 실제 사용자가 사용하는 환경과 유사하게 진행해야 한다. 다음번에 부하테스트를 다시 하게 된다면 사용자가 수행하는 동작 단위로 API를 2~3개 연달아서 호출하고, 만약 인증이 필요한 API라면 토큰 정보까지 담아서 요청할 수 있도록 하고싶다.
'프로젝트 > 뉴스피드' 카테고리의 다른 글
[성능 테스트][트러블 슈팅] Artillery를 이용한 부하 테스트 - 3 (성능 개선) (0) | 2024.02.28 |
---|---|
[성능 테스트][트러블 슈팅] Artillery를 이용한 부하 테스트 - 2 (성능 저하 요인 찾기) (0) | 2024.02.28 |
[성능 테스트] Artillery를 이용한 부하 테스트 -1 (테스트 설정, 결과 보기) (0) | 2024.02.27 |
[단위 테스트/Mockito] 게시글 서비스레이어 단위 테스트 - 2 (with JUnit 버전 문제) (0) | 2024.02.26 |
[단위 테스트/Mockito] 게시글 서비스 레이어 단위 테스트 - 1 (with ReflectionTestUtils) (0) | 2024.02.26 |