오늘까지 작성한 코드를 쭉 리뷰를 해보았습니다.
시작 깃허브 기록은 다음과 같습니다.
https://github.com/soyeonnnb/restagram-api/tree/0593db7bf7f0ab782011318d47932a1467da6bc4
다음과 같은 리스트가 나왔습니다. 오늘부터 차근히 하나씩 리팩토링해보도록 하겠습니다.
22/26
1. ✅ jwt filer 익명필터 넣기 -> 검색해보기 .permitall() 되는지 (24.06.10)
jwt filter 내 비로그인 시 shouldNotFilter를 사용했는데, 이를 사용하지 않고 인증을 해보고 싶어졌습니다.
결국 하지는 못했습니다.ㅜㅜ
2. ✅ commonResponse 삭제하고 다시 하기 (24.06.11)
CommonResponse라는 엔티티를 이용해 데이터를 리턴해주었는데, HttpStatus를 숫자로 보내주어 이를 활용할 수 없었습니다. 따라서 ResponseEntity가 더욱 직관적인것처럼 보여 이를 삭제해주고 ApiResponse를 생성하여 사용해주었습니다.
3. ✅ stream 사용 (24.06.11)
이러한 코드리뷰를 받아 모든 foreach 문을 stream으로 변경해주었습니다.
그래서, 기존의 for문을
@Override
public List<AddressResponse> getSiggList(Long sidoId) {
SidoAddress sidoAddress = sidoAddressRepository.findById(sidoId).orElseThrow(() -> new RestApiException(CommonErrorCode.ENTITY_NOT_FOUND));
List<SiggAddress> siggAddressList = siggAddressRepository.findALlBySidoAddressOrderByName(sidoAddress);
List<AddressResponse> addressResponseList = new ArrayList<>();
for(SiggAddress siggAddress : siggAddressList) {
addressResponseList.add(AddressResponse.of(siggAddress));
}
return addressResponseList;
}
stream으로 변경해주었습니다.
@Override
@Transactional(readOnly = true)
public List<AddressResponse> getSiggList(Long sidoId) {
// 시도 엔티티를 이용해 시군구 리스트 반환
SidoAddress sidoAddress = sidoAddressRepository.findById(sidoId).orElseThrow(
() -> new RestApiException(AddressErrorCode.INVALID_SIDO_ID,
"시도 ID가 유효하지 않습니다. [시도ID=" + sidoId + "]"));
List<SiggAddress> siggAddressList = siggAddressRepository.findALlBySidoAddressOrderByName(
sidoAddress);
return siggAddressList.stream()
.map(AddressResponse::of)
.collect(Collectors.toList());
}
4. ✅ validation exception handler (24.06.11)
기존 validation exception handler에 MethodArgumentNotValidException 을 받는 핸들러가 없는 것을 발견하여, 이를 추가해주었습니다. 더보기를 클릭하시면 코드 전문을 확인하실 수 있습니다.
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<List<ValidationError>>> handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<ValidationError> errors = fieldErrors.stream()
.map(ErrorResponse.ValidationError::of)
.collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.createError("ERROR-001", errors, e.getMessage()));
}
그 결과, 기존
{
"timestamp": "2024-06-12T08:21:19.641+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [1] in public org.springframework.http.ResponseEntity<com.restgram.global.exception.entity.ApiResponse<?>> com.restgram.domain.feed.controller.FeedController.postFeed(org.springframework.security.core.Authentication,com.restgram.domain.feed.dto.request.AddFeedRequest,java.util.List<org.springframework.web.multipart.MultipartFile>): [Field error in object 'req' on field 'storeId': rejected value [null]; codes [NotNull.req.storeId,NotNull.storeId,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [req.storeId,storeId]; arguments []; default message [storeId]]; default message [피드 작성 가게는 필수 영역입니다.]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.resolveArgument(RequestPartMethodArgumentResolver.java:148)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:224)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:178)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:206)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)\r\n\tat org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365)\r\n\tat org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)\r\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131)\r\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter.doFilterInternal(DefaultLogoutPageGeneratingFilter.java:58)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:189)\r\n\tat org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter.doFilter(DefaultLoginPageGeneratingFilter.java:175)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat com.restgram.global.jwt.filter.JwtFilter.doFilterInternal(JwtFilter.java:52)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227)\r\n\tat org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:181)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)\r\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)\r\n\tat org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)\r\n\tat org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\r\n\tat org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)\r\n\tat org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)\r\n\tat org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)\r\n\tat org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)\r\n\tat org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)\r\n\tat org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)\r\n\tat org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:230)\r\n\tat org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)\r\n\tat org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:175)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:150)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1736)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n",
"message": "Validation failed for object='req'. Error count: 1",
"errors": [
{
"codes": [
"NotNull.req.storeId",
"NotNull.storeId",
"NotNull.java.lang.Long",
"NotNull"
],
"arguments": [
{
"codes": [
"req.storeId",
"storeId"
],
"arguments": null,
"defaultMessage": "storeId",
"code": "storeId"
}
],
"defaultMessage": "피드 작성 가게는 필수 영역입니다.",
"objectName": "req",
"field": "storeId",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"path": "/api/v1/feed"
}
의 결과에서,
{
"code": "ERROR-001",
"message": "Validation failed for argument [1] in public org.springframework.http.ResponseEntity<com.restgram.global.exception.entity.ApiResponse<?>> com.restgram.domain.feed.controller.FeedController.postFeed(org.springframework.security.core.Authentication,com.restgram.domain.feed.dto.request.AddFeedRequest,java.util.List<org.springframework.web.multipart.MultipartFile>): [Field error in object 'req' on field 'storeId': rejected value [null]; codes [NotNull.req.storeId,NotNull.storeId,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [req.storeId,storeId]; arguments []; default message [storeId]]; default message [피드 작성 가게는 필수 영역입니다.]] ",
"data": [
{
"field": "storeId",
"message": "피드 작성 가게는 필수 영역입니다."
}
]
}
로 변경하여, 프론트엔드에서 더 직관적으로 확인할 수 있도록 해주었습니다.
5. ✅ rest api exception handler log 정확하게 (24.06.11)
이전에는 단순히 에러 코드만 보내주었습니다.
Store store = storeRepository.findById(req.storeId())
.orElseThrow(() -> new RestApiException(UserErrorCode.INVALID_USER_ID));
로그는 정확해야 한다는 말을 들어 에러 로그도 정확하게 어떤 곳에서 발생했는지 등을 추가해주었습니다.
Store store = storeRepository.findById(req.storeId())
.orElseThrow(() -> new RestApiException(UserErrorCode.INVALID_USER_ID,
"사용자ID가 유효하지 않습니다. [사용자ID=" + req.storeId() + "]"));
6. ✅ 발생위치, 원인, 종류, 환경
기존 로그에는 요청 경로와 오류 메세지밖에 없어 오류 발생 시간과 발생 위치(클래스 및 메서드)도 추가해주었습니다.
2024-06-12T17:25:04.323+09:00 WARN 9820 --- [nio-8080-exec-1] c.r.g.e.handler.GlobalExceptionHandler : 요청 실패 => 요청 경로: http://localhost:8080/api/v1/feed, 발생 시간: 2024-06-12T17:25:04.322496400, 발생 위치: com.restgram.domain.feed.service.impl.FeedServiceImpl.lambda$addFeed$1(FeedServiceImpl.java:90), 오류메세지: 사용자ID가 유효하지 않습니다. [사용자ID=1117]
7. ✅ error 코드 추가 (24.06.11)
기존에는 에러에 대해 메세지만 응답으로 보내주었습니다. 하지만 이를 프론트엔드에서 확인하기에 조건이 복잡하여, 코드를 추가하였습니다. 더보기를 누르시면 자세히 확인하실 수 있습니다.
이전에는 다음과 같이 상태 코드와 영문으로 된 로그만 전송하였습니다.
@RequiredArgsConstructor
@Getter
public enum UserErrorCode implements ErrorCode{
USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "User not found"),
USER_DUPLICATED(HttpStatus.BAD_REQUEST, "User already exists"),
USER_MISMATCH(HttpStatus.BAD_REQUEST, "User mismatched"),
PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST, "Password mismatched"),
INVALID_USER_CODE(HttpStatus.BAD_REQUEST, "User code is invalid"),
EMAIL_DUPLICATED(HttpStatus.CONFLICT, "User Email is duplicated"),
NICKNAME_DUPLICATED(HttpStatus.CONFLICT, "Username is duplicated")
;
private final HttpStatus httpStatus;
private final String message;
}
하지만, 이는 가독성이 떨어질 뿐 아니라 메세지를 통해 정확한 에러를 확인하기 어렵다는 단점이 있습니다.
따라서, 오류코드의 형태를 맞추어주고, 메세지를 한국어로 맞추어주었습니다.
@RequiredArgsConstructor
@Getter
public enum UserErrorCode implements ErrorCode{
INVALID_LOGIN_USER_ID(HttpStatus.BAD_REQUEST, "USER-001", "유효하지 않은 로그인 사용자 ID입니다."),
INVALID_USER_ID(HttpStatus.BAD_REQUEST, "USER-002", "유효하지 않은 사용자 ID입니다."),
USER_DUPLICATED(HttpStatus.BAD_REQUEST, "USER-003", "이미 존재하는 사용자입니다."),
USER_MISMATCH(HttpStatus.BAD_REQUEST, "USER-004", "사용자가 일치하지 않습니다."),
PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST, "USER-005", "비밀번호가 일치하지 않습니다."),
EMAIL_DUPLICATED(HttpStatus.CONFLICT, "USER-006", "중복된 유저 이메일입니다."),
NICKNAME_DUPLICATED(HttpStatus.CONFLICT, "USER-007", "중복된 유저 닉네임입니다.")
;
private HttpStatus httpStatus;
private String code;
private String message;
UserErrorCode(HttpStatus httpStatus, String code, String message) {
this.httpStatus = httpStatus;
this.code = code;
this.message = message;
}
}
8. ✅ @Data 필요없는 부분 다 삭제하기
@Data를 삭제하고 필요한 메서드만 추가해주었습니다.
9. ✅ entity 한번 싹 고치기 (24.06.11)
엔티티에 주석을 추가해주었습니다.
10. ✅ entity string 길이나 제약조건 수정하기 (24.06.11)
string length를 추가해주었습니다.
11. ✅ requestDTO에는 getter만 필요 (24.06.11)
requestDTO내 Getter를 제외한 어노테이션을 삭제해주었습니다.
12. ✅ RequestDTO 유효성 검사 다 넣기 (24.06.11)
이전에는 단순히 필드만 작성해주었습니다. 하지만 이렇게하면 유효성을 검사하지 못합니다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class AddFeedRequest {
private Long storeId;
private String content;
}
따라서, @Valid 어노테이션을 통해 유효성을 검사하도록 해주었습니다.
또한 Request의 경우 서비스 내에서 생성이 되지 않기 때문에 getter 만 두었습니다.
@Getter
public class AddFeedRequest {
@NotNull(message = "피드 작성 가게는 필수 영역입니다.")
@Min(value = 0, message = "가게 ID는 음수가 될 수 없습니다.")
private Long storeId;
@Size(max = 2000, message = "피드 내용은 최대 2000자까지 가능합니다.")
@NotBlank(message = "피드 내용은 필수 영역입니다.")
private String content;
}
13. ✅ 더티체킹하는거 save 변경하기 (24.06.11)
@Transactional을 통해 엔티티가 더티체킹이 되기 때문에, 직접 save() 함수를 호출하여 업데이트하는 경우 save() 함수 호출을 삭제해주었습니다.
14. ✅ service/service impl 계층 나누기 (24.06.10)
service 인터페이스와 serviceImpl 구현체의 패키지를 나누어주었습니다.
15. ✅ 서비스 내 서비스 트랜젝션 분리해놓기 (24.06.11)
16. ✅ 조회 시 treansaction readonly 달아놓기 (24.06.11)
조회 시 1차 캐시에 저장하지 않게 하기 위해 @Transactional(readOnly = true)를 추가해주었습니다.
16. ✅ 같은 계층끼리 줄바꿈
코드의 가독성을 늘리기 위해 같은 로직이 아니면 줄바꿈을 해주었습니다.
17. ✅ import 안쓰는거 싹 제거 (24.06.10)
18. ✅ ;; 이런 코드도 존재. 파일 보고 확인 (24.06.11)
오타 삭제
19. ✅ 주석달기 (24.06.11)
엔티티 및 서비스 내 주석을 추가하였습니다.
20. ✅ 출력문도 필요 없으면 다 제거 (24.06.10)
21. ✅ refresh repository 어노테이션 삭제 (24.06.11)
필요없는 어노테이션을 삭제했습니다.
22. ✅ requestMapping에 "/api/v1" 붙이기 (24.06.10)
이전에 다음과 같은 코드리뷰를 받은 적이 있습니다.
저 또한 이에 동의하기 때문에 api에 /api/v1을 붙여주었습니다.
23. ✅ dto는 한번 만들면 수정X. 그러니까 record 사용 (24.06.12)
DTO의 경우에는 한번 생성이 되면 수정되는 일이 없습니다. 따라서 더 유연한 코드 작성을 위해 DTO의 경우 class 에서 record로 변경시켜주었습니자.
자세한 내용은 다음 포스팅을 참고해주세요.🥰
https://soyeonnnb.tistory.com/126
24. ✅ of랑 toEntity 이름 확인
메서드 명을 DTO -> Entity 는 toEntity, Entity/DTO -> DTO는 of 를 사용해주었습니다.
24. 팔로우 같은거 삭제하는거 어떤가
25. emdAddress 테이블 합치는거 정규화 테스트
시작일: 24.06.10
최근 수정: 24.06.12
'프로젝트' 카테고리의 다른 글
[Spring/QueryDSL] Fetch Join이랑 limit은 함께 못쓴다고요 ? (1) | 2024.06.13 |
---|---|
[Java] Record란? & 프로젝트에 Record 적용해보기 (0) | 2024.06.12 |
[Spring] 오프셋 기반 vs 커서 기반 페이지네이션 (0) | 2024.06.06 |
[Spring] 쿠폰 발급 동시성 문제 - 비관적 락 사용 테스트 (0) | 2024.06.06 |
[Spring/OAuth] 카카오 캘린더 API 사용하기 - 캘린더 생성 및 일정 추가 (0) | 2024.06.05 |