[4주차] RateLimit 적용 및 공통 응답 작업#90
Conversation
|
@kimbro97 안녕하세요 형재님 ! 4주간 고생 많으셨습니다 ! 리뷰 진행하겠습니다 ~ |
| } | ||
|
|
||
| @Around("@annotation(reservationRateLimited)") | ||
| public Object around(ProceedingJoinPoint joinPoint, ReservationRateLimited reservationRateLimited) throws Throwable { |
There was a problem hiding this comment.
RateLimit 어노테이션과 AOP 를 잘 활용해주셨네요 👍🏿
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Target(ElementType.METHOD) | ||
| public @interface ReservationRateLimited { | ||
| } |
There was a problem hiding this comment.
val ttl: Int = 1, // 호출 제한 시간
val count: Int, // 분당 호출 제한 카운트
val timeUnit: TimeUnit = TimeUnit.HOURS
이런 옵션들도 설정 가능하게끔 하는 것을 고려해보는 것도 좋을 것 같아요. Controller 에서 API 의 제약조건에 대해서 더 명확하게 알 수 있을 것 같습니다.
| package com.example.config.ratelimit; | ||
|
|
||
| public interface RateLimit { | ||
|
|
There was a problem hiding this comment.
RateLimit 기능을 개발할때 인터페이스를 만들어서 객체간의 결합도를 낮췄습니다. 적절하게 인터페이스를 사용했는지 궁금합니다!!
인터페이스를 사용하여 다양한 구현체를 만드는 방식은 좋은 접근 입니다. 클라이언트 측은 인터페이스에 대한 명세만 알고있으면되고, 세부 구현체가 어떤 것인지는 몰라도 되기 때문입니다.
다만, 어떤 구현체를 선택해서 사용할지에 대한 로직도 추가적으로 필요할 것 같습니다.
Guava, Redis 의 RateLimit 활용에 사용을 해주셨는데, 시나리오 특성상 2개의 구현체가 존재해야하므로 잘 써주신 것 같아요.
다만 실무적으로 본다면 분산 애플리케이션 환경에서는 Redis 만 사용할 가능성이 높기 때문에 인터페이스 없이 바로 구현체를 활용할 수 있을 것 같습니다.
아이디어랑 접근 방식은 좋습니다 💯
| return movieService.getMovies(request.toServiceRequest()); | ||
| @MovieSearchRateLimited | ||
| public ApiResponse<List<MovieResponse>> getMovies(MovieSearchRequest request) { | ||
| return ApiResponse.ok("영화 목록 조회",movieService.getMovies(request.toServiceRequest())); |
| @Getter | ||
| public enum BusinessError { | ||
| // 유저관련 exception 3000 | ||
| USER_LOGIN_ERROR(HttpStatus.BAD_REQUEST, 3000, "로그인이 필요합니다."), |
There was a problem hiding this comment.
실무에서 자주 사용되는 방식으로 잘 구성해주셨네요 👍🏿
|
|
||
| @Test | ||
| @DisplayName("초당 요청 제한 테스트: 연속 요청 시 per-second RateLimiter가 제한하는지 확인") | ||
| void test1() { |
There was a problem hiding this comment.
@DisplayName 을 사용했다고 해서 test 이름을 대충 지어도 되는건 아닙니다 !
오히려 test 이름을 함수로 사용하고 @DisplayName 를 없애는 것을 고려해보는 것도 좋을 것 같습니다.
| memberRepository.save(member); | ||
| assertThatThrownBy(() -> { reservationService.reserve(new ReservationServiceRequest(2L, 1L, List.of(1L, 2L))); }) | ||
| .isInstanceOf(IllegalArgumentException.class) | ||
| .isInstanceOf(BusinessException.class) |
There was a problem hiding this comment.
given 에 해당되는 사전 준비 과정(데이터 생성 & save) 가 5군데 정도 중복이 있는 것 같은데요, 별도 함수로 빼서 관리해도 좋을 것 같습니다.
e.g createMovie("히트맨 2", "http://example.com/hitmen.jpg", 120, Genre.ACTION, Rating.ALL, LocalDate.of(2025, 1, 22));
|
|
||
| @Getter | ||
| public class ApiResponse<T> { | ||
|
|
There was a problem hiding this comment.
ApiResponse 도 잘 만들어주셨습니다 👍🏿
좋았던 점
아쉬웠던 점
리뷰 포인트 및 추가 질문에 대한 답변
실무에서도 따로 공통(지원) 모듈을 만들어서 exception or ApiResponse 를 정의해두고 사용하곤 합니다. |
|
안녕하세요, 코치님! 머지해 주시면 이번 주에 부족한 부분을 보완하여 PR 다시 드리겠습니다! |
[4주차] RateLimit 적용 및 공통 응답 작업
작업 내용
발생했던 문제와 해결 과정을 남겨 주세요.
이번 주차에서 고민되었던 지점이나, 어려웠던 점을 알려 주세요.
리뷰 포인트
기타 질문