오픈소스
하나의 기술적인 궁금증이 아니라 프로젝트의 전체적인 그림을 참고하고 싶을 때 오픈소스를 활용하게 됐다. 같은 기술스택으로 구현된 오픈소스 프로젝트를 찾아서 파악하는 것은 아주 효율적인 방법이었다.
패키지 설계나 예외 관리, 잘 쓰여진 다양한 코드를 말이 아니라 코드 레벨로 볼 수 있다는 게 좋았다.
REST API
그간의 경험에서 REST API로 API를 설계한 적은 없었는데 이번에 확실히 많이 배우게 됐다.
HATEOAS의 사용방법이나 개념을 어느 정도 학습하다가 이 단계까지 구현하는 것은 무리일 것 같아 Level 2 까지의 깊이로 설계했다. HTTP 메소드와 Collection, Element를 이용해서 API를 구성하다 보니 요청 URL이 굉장히 깔끔해졌고 식별하기 좋았다. 요청 URL만 보고도 어떤 요청을 하는지 짐작할 수 있었고 프로젝트 후반에는 거의 모든 API의 요청 주소가 다 외워졌다.
CI/CD 파이프라인
CI/CD 파이프라인을 개발 단계가 시작하기 전에 구축하는 것이 중요하다고 생각했다. 효율적인 협업과, 이후 개발에만 집중할 수 있도록. Docker 강의를 몰아서 듣고 GitHub Actions 와 Docker, Docker-Compose, Elastic Beanstalk 을 이용해서 파이프라인을 구성 했다. AWS와 Jenkins에 대한 현업에서의 경험이 있었기 때문에 가능했다.
클라이언트 개발자 한 분이 유료 도메인을 구입해주셔서 ALB를 이용해서 https 서버를 구성했다.
1~2주차에 배포 서버와 CI/CD 파이프라인을 구성해놓으니 이후 백엔드끼리의 협업이나 클라이언트와의 협업에서 얻는 장점이 너무 많았다. GitHub Actions의 마켓 플레이스에 있는 variable-sustitute를 이용해 properties 파일이나 key 파일 등의 민감정보를 외부에 드러나지 않도록 설정했던 부분이 만족스럽다.
Spring Security
스프링 시큐리티를 아예 사용한 경험이 없어서 굉장히 오랜 시간이 걸렸다. 수강했던 강의와 구글링에서 나오는 대부분의 예제가 Deprecated 된 방식(WebSecurityConfigurerAdapter를 implements)으로 구현되어 있어서 이 부분을 걷어내는 데 시간이 많이 들었다. 필터를 직접 빈으로 등록해서 추가해야 하는데 이 부분을 구현하는 과정에서 스프링 빈의 구성방식을 조금 더 이해하게 됐다. @Component나 @Bean으로 직접 스프링 빈을 만드는 게 재밌어서 프로젝트의 다른 영역에서도 사용하다가 싱글턴으로는 오류가 발생하는 부분이 있어서 롤백 했다.
OAuth2와 JWT를 다뤄보는 것도 좋은 경험이었는데 JWT는 Refresh Token을 관리하는 Redis 서버와 구현 로직까지 꽤 만족스럽게 만들어졌으나 Oauth2를 다루는 부분이 아직 많이 미숙하다는 생각이 든다.
예외처리
이번 프로젝트를 진행하면서 많은 강의를 듣고 여러 자료들을 참조했지만 가장 도움됐던 강의는 호돌맨 님의 인프런 강의였다. 정말 보물같은 강의였다.
그 동안 예외를 회피하거나 구체적인 예외로 전환하는 것을 이론적으로는 알았지만 이게 어떻게 실제 애플리케이션 단에서 사용되는지 난해했는데 그 부분을 시원하게 해결주었다. 예외를 커스텀하고 ControllerAdvice로 전역적으로 예외를 핸들링하면서 정형화된 에러코드를 응답할 수 있도록 설계하는 방법을 배웠는데, 프로젝트에 적용하고 나니 아주 만족스러웠다.
프로젝트에 적용하는 과정에서 Controller를 타지 않는 예외, 즉 Spring security 필터에서 터지는 예외들은 내가 구성한 ExceptionHandler에 캐치되지 않았는데 이러한 부분들은 직접 Response에 Json을 write 해서 응답을 내렸다. 이 과정에서 스프링의 필터와 디스패처 서블릿, Http Request와 Http Response에 대해서 조금 더 알 수 있었다.
DTO를 이용하는 부분도 좋았다. Entity 와 requestDTO, responseDTO를 분리해서 구성하는 방식은 정말 그동안 이론적으로 학습했던 부분을 프로젝트에 어떻게 적용시켜야 할지에 대한 확실한 답이 되었다.
메소드체이닝
JWT access token을 검증하고 condition에 따라 예외 발생, refresh token 검증 redis에 접근하는 과정 등의 로직을 짜야 하는 부분이 있었는데 IF 조건 분기가 너무 많이 발생했다. 내가 작성한 코드인데도 다음 날 켜니 조건 분기를 따라가기가 버거웠다. 이 부분을 어떻게 한눈에 파악할 수 있게 만들까 한참을 고민하다가 JwtTokenProvider 유틸 클래스를 하나 직접 만들었다. 메소드 체이닝을 통해서 코드만 보고 과정을 짐작할 수 있도록 하고 IF 분기를 줄이면서 코드의 가독성을 높였다.
내부 코드를 일일히 파악하지 않고도 메소드명을 통해 동작을 추측할 수 있도록 의도했는데 꽤 만족스럽다.
Java 8
Java 8에 대한 경험이 없었는데 최대한 열심히 써봤다. 모던 자바 인 액션을 처음부터 읽다가 시간의 한계로 최대한 실용적인 부분만 검색해가면서 부분부분 썼는데 사용 경험이 정말 최고다.
Stream, Optional, 람다식을 활용하지 않았다면 굉장히 지저분한 코드가 되었을 텐데 아주 깔끔한 코드로 만들 수 있었다.
SpringBoot + JPA 시리즈들
SpringBoot와 JPA 시리즈의 조합은 정말 생산성의 혁명이었다. 기존에 JPA 이론 강의를 듣지 않았다면 Spring Data JPA이나 QueryDSL을 쓰면서 많이 헤맸을 것 같은데 JPA 이론을 조금은 아는 상태에서 사용하니 왜 JPA, JPA 하는지 알 수 있었다.
쌩 JPA 만 배웠을 때는 MyBatis 와 비교해서 얻는 장점을 패러다임을 DB에서 Java로 옮겨온다는 개념적인 면에서만 받아들여졌었다. MyBatis도 생산성 면에서 크게 뒤지지 않는다고 생각하기도 했었다.
그런데 Spring Data JPA와 QueryDSL를 조합해서 쓰니 정말 신세계. N+1 문제도 실제로 마주하니 굉장히 즐거운 경험이었다. 모든 N+1은 제거하지 못했지만 몇 부분을 제거했다. 외부 조인일 경우 Entity Graph를 활용하고 내부 조인일 경우 JPQL의 fetch join을 사용했는데 문제를 찾아내고 해결하는 과정이 재밌었다. 다소 복잡한 조인이 들어가는 쿼리들은 아직 제거하지 못했는데 추후 QueryDSL을 사용해서 제거하는 시간을 가져보려고 한다.
Comparable 을 통한 정렬
클라이언트 요구사항 중에서 folder 조회 시 폴더 내부의 articles를 등록일자에 맞춰 내림차순으로 정렬해달라는 요구사항이 있었다. 그동안 정렬 기능은 JPA의 Sort를 사용하거나 SQL의 OrderBy 쿼리를 통해 구현했었는데, 이미 DB에서 추출된 folder 인스턴스에 대해서 등록일자를 기준으로 정렬하는 요구사항을 어떻게 구현할까 고민하다가 Comparable을 커스텀해서 사용했다.
아주 단순하지만, 새로운 부분을 알게 됐다.
아쉬운 점
테스트 코드를 많이 작성하지 못했다. 개발 시작 단계부터 Controller 레이어에서의 통합 테스트 위주로 작성을 진행했는데 엔티티가 초기 설계와는 달리 자주 바뀌면서 도중에는 테스트를 아예 제거해버렸다. 프로젝트 말미에 다시 추가했지만 테스트 코드의 양이나 퀄리티가 너무 저조했다. 좋은 테스트 코드를 쓰려면 좋은 객체 설계가 필요하다는 개념적인 말이 정말 가슴 깊이 깨달았다. jacoco 로 테스트 커버리지를 측정하고 최소한의 기준을 넘기지 않으면 빌드에 실패하게 하고 싶었는데 테스트 코드 량이 너무 적어서 실천에 옮기지 못했다.
객체지향적 설계나 성능적으로 아쉬움이 생기는 부분이 많았다. 요구사항이 추가될 수록 계속해서 늘어나는 메소드들이나 클래스들을 미리 예측해서 설계했다면 소스코드가 많이 줄어들었을 것 같았다. 또, 마땅한 구현법이 당장 생각나지 않아서 이중 루프를 돌리거나 N+1 문제를 방치하고 있는 메소드들을 기간 내에 해결하지 못한 점도 아쉽다.
무중단 배포가 가능하도록 Nginx 를 앞단에 두기는 했는데 실현하지는 못했다.
같이 서버를 담당한 백엔드 분을 도와드릴 수 있는 기회가 몇 번 있었는데 그럴 때마다 답변을 해드리면서 스스로 부족함을 많이 느꼈다. 설명할 수 없는 지식은 내가 가진 지식이 아니라고 하는 글을 본 거 같은데, 그 말이 떠올랐다. 내 지식의 깊이도 깊지 않았기 때문에 내 말이 틀리지는 않을까 꽤 많이 조마조마했고 그러지 않도록 최대한 노력하면서 도움을 드렸지만 아쉬움이 남는 부분은 어쩔 수가 없다.
AOP를 활용해서 반복되는 코드를 줄이고 싶었는데 그러지 못했다.
트랜잭션의 격리 레벨을 잘 관리하고 인덱스나 실행 계획을 통해 쿼리 성능을 향상 시키고 싶었는데 그러지 못했다.
'생각글쓰기 > 회고' 카테고리의 다른 글
2022년 회고: 개발자로 보낸 첫 일년 (2) | 2023.02.14 |
---|---|
2021년 회고: 비전공자가 개발자가 되기까지 (0) | 2021.12.25 |
댓글