💡 발단 엔티티 생성자에서 정말 기본적인걸 놓치고 있었다. 1:N (일대다) 관계의 두 엔티티가 있었고, 나는 자식쪽에서 부모 엔티티만 연결해주고 있었다. 마치 레코드를 INSERT할 때 FK만 넣어서 참고하는 것처럼 말이다. 기존의 코드: 객관식 문제(Quiz)와 그 선택지(Choice)이다. 중요한 부분 외엔 생략, 단순화시켰다. // 문제쪽의 생성자 @Builder public Quiz(String properties, List choiceList) { this.properties = properties this.choiceList = choiceList; } // ... // 선택지쪽의 생성자 @Builder public Choice(String properties, Quiz quiz) { thi..
💡 발단 프로젝트에서 맞닥뜨린 요구사항은 이렇다. 엔티티로는 로케이션이 있고, 각 로케이션에 대해서 서울시 인구 혼잡도 API로부터 5분마다 혼잡도 데이터를 적재한다. ex. 홍대입구역(id=3)에 대한 9시의 인구혼잡도가 2레벨(기획상 1~3으로 나뉜다)이라면 congestion(location_id=3, congestion_level=2, observed_at=2023-08-05 18:35:43.769817) 으로 저장된다. 나는 각 location별로 가장 최근의 혼잡도 (congestion)을 조회해야 했다. 아래 사진은 와이어프레임인데, 각 카드 하단의 '여유', '복쟉', '보통'이 각 로케이션의 가장 최근 혼잡도 레벨이다. '지하철'은 location_category에 해당. 💦 삽질 과정 ..
스웨거를 도입하게 된 이유 기존의 프로젝트에서는 노션으로 API 명세를 하고 있었다. API 내용을 일일이 쓰는게 꽤 오래걸리긴 하지만, 그만큼 상세하게 설명할 수 있어서 좋았다. 개발 전에는 프론트엔드와 크로스체킹까지 해서 누락되거나 기획상 틀린 정보가 거의 없었다. 문제는 개발을 하는중에 생겼는데, API 명세서와 다르게 응답값을 설정하는 일이 빈번하게 생겼던 것. 그래서 프론트엔드 팀원께서 많은 불편을 겪었다. 그래서 결국 스웨거를 적용하게 됐다. 해당 프로젝트의 Spring Boot 버전은 3.0이고 springfox가 아닌 springdoc-openapi를 의존한다. 생각보다 springdoc-openapi, 특히 webmvc-ui 버전을 적용하고 정리해놓은 블로그가 많이 없었다. 공식 문서까지..
🔍예외의 종류 예외처리를 알아보기 전에 예외의 종류부터 살펴보자. Unchecked Exception (RunTimeException) Exception Class의 서브 클래스 RuntimeException Class를 상속 Transaction으로 Rollback이 진행 됨 반드시 예외를 체크하지 않아도 되는 경우 사용합니다. 이처럼 명시적인 예외처리를 강제하지 않기 때문에 언체크 예외라고 부릅니다. 언체크 예외는 런타임 예외라고도 부릅니다. 체크 예외와는 달리 따로 처리하지 않아도 컴파일 에러가 발생하지 않습니다. 예외를 피할 수 있지만 개발자가 부주의해서 발생할 수 있는 경우를 위해 만든 예외입니다. 즉, 주로 프로그램 자체의 오류가 있을 때 발생하도록 의도됩니다. 대표적으로 NullPointer..
잠깐, CI란? CI(Continuous Integration) : 지속적인 통합 (= 빌드, 테스트 자동화) CI가 필요한 환경 : 다수의 개발자가 형상관리 툴(git)을 사용해 공유 레포지토리에서 작업하는 환경 비정상적인 코드가 통합되는 것을 예방할 수 있다. 여러 명의 개발자가 동시에 관련된 코드 작업을 할 경우 발생하는 충돌 문제를 해결할 수 있다. 사실 충돌 문제 해결은 깃허브에서 자동으로 실행해주는 기능이다. (공포의 붉은색 conflict 문장) 내가 적용한건 주로 빌드 자동화이다. ✅ PR 생성시 빌드 테스트 자동화 PR을 생성한 뒤에 github에서 자동으로 빌드 테스트를 하도록 만들었다. 그리고 빌드 실패시엔 merge가 불가능하도록 설정했다. "All checks have passed..
💡 발단 기존에는 HTTP code(2xx, 3xx, 4xx)와 메세지로 서버의 응답을 표현하는 것이 무조건 옳다고 생각했다. 표준을 따른다면 웹 API에서는 이 방식을 따르는게 맞다. 그런데, HTTP의 40여개 코드로는 REST API의 모든 분기를 처리하기에 너무 적다. 예를 들면 권한 처리가 있다. 정지된 유저가 요청을 할 때와, 글쓴이가 아닌 유저의 글 삭제 요청에 대한 응답 코드는 모두 403(not Authorized) 이다. // 403 에러 { "message" : "banned user." } // 403 에러 { "message" : "not author of this content." } 문제는, 403으로 표현할 수 있는 가짓수가 너무 적다는거. 그래서 여러가지 분기를 표현하려면 ..
💡 발단 현재 프로젝트에선 기존에 로그인된 유저 정보를 불러오는 편의메소드를 제작하여 사용했다. (잠깐, SecurityContextHolder란) public class SecurityUtils { public static String getCurrentUserSocialEmail() { // 유저 정보를 불러오는 부분 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || authentication.getName() == null) { throw new UserException(StatusCode.NOT_FOUND_USER); } // 유저 식별값..
💡 발단 현재 프로젝트에선 팀원이 Security 로직을 잘 구현해놨다. *카카오 연동 소셜 로그인(OAuth2.0)과 JWT 인증 방식을 사용한다. 그래서 난 내가 맡은 기능만 구현하면 되겠다고 생각했는데, Security의 구조를 모르니 문제가 생겼다. 로그인된 유저 객체를 ... 어떻게 가져오지? 분명 DB에서 조회해올텐데? 찾아보니 편의 메서드를 만들어서 쓰고 있었고, 난 이 함수를 불러와서 다른 로직을 구현할 수 있었다. public class UserService { // ... public User getCurrentUser() { // DB에서 유저 엔티티 조회 return userRepository.findBySocialEmail(getCurrentUserSocialEmail()).orE..