-
Notifications
You must be signed in to change notification settings - Fork 16
[강은혁] Sprint11 #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 강은혁
Are you sure you want to change the base?
[강은혁] Sprint11 #162
Conversation
# Conflicts: # .gitignore # build.gradle # gradle/wrapper/gradle-wrapper.jar # gradle/wrapper/gradle-wrapper.properties # gradlew # gradlew.bat # settings.gradle # src/main/java/com/sprint/mission/discodeit/entity/Channel.java # src/main/java/com/sprint/mission/discodeit/entity/Message.java # src/main/java/com/sprint/mission/discodeit/entity/User.java # src/main/java/com/sprint/mission/discodeit/repository/ChannelRepository.java # src/main/java/com/sprint/mission/discodeit/repository/MessageRepository.java # src/main/java/com/sprint/mission/discodeit/repository/UserRepository.java # src/main/java/com/sprint/mission/discodeit/service/ChannelService.java # src/main/java/com/sprint/mission/discodeit/service/MessageService.java # src/main/java/com/sprint/mission/discodeit/service/UserService.java
# Conflicts: # build.gradle # src/main/java/com/sprint/mission/discodeit/controller/AuthController.java # src/main/java/com/sprint/mission/discodeit/controller/BinaryContentController.java # src/main/java/com/sprint/mission/discodeit/controller/ChannelController.java # src/main/java/com/sprint/mission/discodeit/controller/MessageController.java # src/main/java/com/sprint/mission/discodeit/controller/ReadStatusController.java # src/main/java/com/sprint/mission/discodeit/controller/UserController.java # src/main/java/com/sprint/mission/discodeit/dto/request/BinaryContentCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/LoginRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/MessageCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/MessageUpdateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/PrivateChannelCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/PublicChannelUpdateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/ReadStatusUpdateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/UserCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusCreateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/UserStatusUpdateRequest.java # src/main/java/com/sprint/mission/discodeit/dto/request/UserUpdateRequest.java # src/main/java/com/sprint/mission/discodeit/entity/User.java # src/main/java/com/sprint/mission/discodeit/entity/base/BaseEntity.java # src/main/java/com/sprint/mission/discodeit/exception/DiscodeitException.java # src/main/java/com/sprint/mission/discodeit/exception/ErrorCode.java # src/main/java/com/sprint/mission/discodeit/exception/ErrorResponse.java # src/main/java/com/sprint/mission/discodeit/exception/GlobalExceptionHandler.java # src/main/java/com/sprint/mission/discodeit/exception/channel/ChannelException.java # src/main/java/com/sprint/mission/discodeit/exception/channel/ChannelNotFoundException.java # src/main/java/com/sprint/mission/discodeit/exception/channel/PrivateChannelUpdateException.java # src/main/java/com/sprint/mission/discodeit/exception/message/MessageException.java # src/main/java/com/sprint/mission/discodeit/exception/message/MessageNotFoundException.java # src/main/java/com/sprint/mission/discodeit/exception/user/UserException.java # src/main/java/com/sprint/mission/discodeit/exception/user/UserNotFoundException.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicAuthService.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentService.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicChannelService.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicReadStatusService.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java # src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserStatusService.java # src/main/resources/application-dev.yaml # src/main/resources/application-prod.yaml # src/main/resources/application.yaml # src/main/resources/logback-spring.xml
| return executor; | ||
| } | ||
|
|
||
| private TaskDecorator mdcAndSecurityContextDecorator() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mdcAndSecurityContextDecorator 를 추가하신 부분 잘하셨습니다.
|
|
||
| @Configuration | ||
| @EnableRetry | ||
| public class RetryConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RetryConfig 보다는 이런 기본적인 공통 기능은 AppConfig에 모아두는 게 좋을것 같습니다!
| e | ||
| ); | ||
|
|
||
| throw new RuntimeException("S3 업로드 실패 (모든 재시도 실패)", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이것도 별도의 Custom exception을 만들어 관리해보시기 바랍니다.
Spring Event - 파일 업로드 로직 분리하기
디스코드잇은 BinaryContent의 메타 데이터(DB)와 바이너리 데이터(FileSystem/S3)를 분리해 저장합니다.
만약 지금처럼 두 로직이 하나의 트랜잭션으로 묶인 경우 트랜잭션을 과도하게 오래 점유할 수 있는 문제가 있습니다.
따라서 Spring Event를 활용해 메타 데이터 저장 트랜잭션으로부터 바이너리 데이터 저장 로직을 분리하여, 메타데이터 저장 트랜잭션이 종료되면 바이너리 데이터를 저장하도록 변경합니다.
BinaryContentStorage.put을 직접 호출하는 대신 BinaryContentCreatedEvent를 발행하세요.
다음의 메소드에서 BinaryContentStorage를 호출하는 대신 BinaryContentCreatedEvent를 발행하세요.
ApplicationEventPublisher를 활용하세요.
이벤트를 받아 실제 바이너리 데이터를 저장하는 리스너를 구현하세요.
바이너리 데이터 저장 성공 여부를 알 수 있도록 메타 데이터를 리팩토링하세요.
PROCESSING: 업로드 중SUCCESS: 업로드 완료FAIL: 업로드 실패트랜잭션 전파 범위에 유의하세요.
바이너리 데이터 저장 성공 여부를 메타 데이터에 반영하세요.
Spring Event - 알림 기능 추가하기
채널에 새로운 메시지가 등록된 경우 알림을 받을 수 있도록 리팩토링하세요.
receiverId: 알림을 수신할 User의 id입니다.
알림 조회
GET /api/notifications응답
200 List
401 ErrorResponse
알림 확인
DELETE /api/notifications/{notificationId}204 Void알림이 필요한 이벤트가 발행되었을 때 알림을 생성하세요.
단, 해당 메시지를 보낸 사람은 알림 대상에서 제외합니다.
on(RoleUpdatedEvent)
비동기 적용하기
비동기 실패 처리하기
비동기로 처리하는 로직이 실패하는 경우 사용자에게 즉각적인 에러 전파가 되지 않을 가능성이 높습니다.
따라서 비동기로 처리하는 로직은 자동 재시도 전략을 통해 더 견고하게 구현해야 합니다.
또, 실패하더라도 그 사실을 명확하게 기록해두어야 에러에 대응할 수 있습니다.
S3를 활용해 바이너리 데이터 저장 시 자동 재시도 매커니즘을 구축하세요.
재시도가 모두 실패했을 때 대응 전략을 구축하세요.
캐시 적용하기
Spring Kafka 도입하기
회원이 늘어나면서 알림 연산량이 급증해 알림 기능만 별도의 마이크로 서비스로 분리하기로 결정했다고 가정해봅시다.
이제 알림 서비스와 메인 서비스는 완전히 분리된 서버이므로 Spring Event만을 통해서 이벤트를 발행/소비할 수 없습니다.
따라서 메인 서비스에서 Kafka를 통해 서버 외부로 이벤트를 발행하고, 알림 서비스에서는 서버 외부의 이벤트를 소비할 수 있도록 해야합니다.
Kafka 환경을 구성하세요.
Docker Compose를 활용해 Kafka를 구동하세요.
...
Redis Cache 도입하기
대용량 트래픽을 감당하기 위해 서버의 인스턴스를 여러 개로 늘렸다고 가정해봅시다.
Caffeine과 같은 로컬 캐시는 서로 다른 서버에서 더 이상 활용할 수 없습니다.
따라서 Redis를 통해 전역 캐시 저장소를 구성합니다.
Redis 환경을 구성하세요.
docker compose -f docker-compose-redis.yml up -d
implementation 'com.github.ben-manes.caffeine:caffeine'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
멘토에게