-
Notifications
You must be signed in to change notification settings - Fork 16
[안중원] Sprint11 #160
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 #160
Conversation
| @Bean(name = "taskExecutor") | ||
| public Executor TaskExecutor() { | ||
| ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||
| executor.setCorePoolSize(4); | ||
| executor.setMaxPoolSize(8); | ||
| executor.setQueueCapacity(50); | ||
| executor.setThreadNamePrefix("TaskExecutor-"); | ||
|
|
||
| executor.setTaskDecorator(new MdcTaskDecorator()); | ||
| executor.initialize(); | ||
| return executor; | ||
| } | ||
|
|
||
| @Bean(name = "eventTaskExecutor") | ||
| public Executor EventTaskExecutor() { | ||
| ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||
| executor.setCorePoolSize(4); | ||
| executor.setMaxPoolSize(8); | ||
| executor.setQueueCapacity(50); | ||
| executor.setThreadNamePrefix("EventTaskExecutor-"); | ||
|
|
||
| executor.setTaskDecorator(new MdcTaskDecorator()); | ||
| executor.initialize(); | ||
| return executor; | ||
| } |
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.
설정이 완전히 동일해 보이는데 빈을 따로 설정한 이유가 있을까요?
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.
eventTaskExecutor를 카프카용으로 구상하고 만들긴했어요!
그런데 동일한 executor 두 개라.. 하나로 합쳐서 다음 미션에 반영하겠습니다!
| import org.springframework.retry.annotation.EnableRetry; | ||
|
|
||
| @SpringBootApplication | ||
| @EnableRetry // Spring Retry 활성화 |
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.
해당 설정은 AppConfig로 가는게 자연스럽습니다.
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.
Pull Request Overview
This PR implements Spring Event for asynchronous file upload processing, notification functionality, and introduces Redis cache and Kafka messaging for distributed system support. The key changes separate binary data storage from metadata storage to improve transaction management, add notification features for message creation and role updates, and enable asynchronous processing with retry mechanisms.
- Separated binary content storage from metadata storage using Spring Events to avoid long-running transactions
- Implemented notification system with Kafka for distributed event processing and added notification APIs
- Added Redis cache for distributed caching and configured async processing with retry mechanisms for S3 uploads
Reviewed Changes
Copilot reviewed 176 out of 182 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/resources/application.yaml | Added Kafka, Redis, and cache configuration settings for distributed system support |
| src/main/resources/schema.sql | Added status column to binary_contents table and notification-related columns to read_statuses table |
| src/main/java/com/sprint/mission/discodeit/service/basic/BasicUserService.java | Refactored to publish BinaryContentCreatedEvent instead of directly storing binary data, added cache annotations |
| src/main/java/com/sprint/mission/discodeit/service/basic/BasicMessageService.java | Publishes MessageCreatedEvent for notification processing |
| src/main/java/com/sprint/mission/discodeit/service/basic/BasicBinaryContentStorageService.java | Implements retry logic with @retryable and recovery mechanism for failed S3 uploads |
| src/main/java/com/sprint/mission/discodeit/storage/s3/S3BinaryContentStorage.java | New S3 storage implementation with presigned URL support |
| src/main/java/com/sprint/mission/discodeit/security/MdcTaskDecorator.java | Propagates MDC context and SecurityContext to async threads |
| src/test/resources/application-test.yaml | New test configuration file with H2 database and test JWT settings |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| } | ||
|
|
||
| public Cookie genereateRefreshTokenCookie(String refreshToken) { |
Copilot
AI
Nov 6, 2025
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.
Corrected spelling of 'genereate' to 'generate'
| return refreshCookie; | ||
| } | ||
|
|
||
| public Cookie genereateRefreshTokenExpirationCookie() { |
Copilot
AI
Nov 6, 2025
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.
Corrected spelling of 'genereate' to 'generate'
| updated_at timestamp with time zone, | ||
| user_id uuid NOT NULL, | ||
| channel_id uuid NOT NULL, | ||
| last_read_at timestamp with time zone NOT NULL, |
Copilot
AI
Nov 6, 2025
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.
The schema.sql is missing the notification_enabled column for read_statuses table mentioned in the PR description. According to the requirements, this column should be added with: notification_enabled boolean NOT NULL
| last_read_at timestamp with time zone NOT NULL, | |
| last_read_at timestamp with time zone NOT NULL, | |
| notification_enabled boolean NOT NULL, |
Spring Event - 파일 업로드 로직 분리하기
BinaryContentStorage.put을 직접 호출하는 대신BinaryContentCreatedEvent를 발행하세요.BinaryContentCreatedEvent를 정의하세요.BinaryContent메타 정보가 DB에 잘 저장되었다는 사실을 의미하는 이벤트입니다.BinaryContentStorage를 호출하는 대신BinaryContentCreatedEvent를 발행하세요.UserService.create/updateMessageService.createBinaryContentService.createApplicationEventPublisher를 활용하세요.BinaryContentStorage를 통해 바이너리 데이터를 저장하세요.BinaryContent에 바이너리 데이터 업로드 상태 속성(status)을 추가하세요.PROCESSING: 업로드 중SUCCESS: 업로드 완료FAIL: 업로드 실패BinaryContent의 상태를 업데이트하는 메소드를 정의하세요.BinaryContent의status를SUCCESS로 업데이트하세요.BinaryContent의status를FAIL로 업데이트하세요.Spring Event - 알림 기능 추가하기
1)채널에 새로운 메시지가 등록되거나2)권한이 변경된 경우이벤트를 발행해 알림을 받을 수 있도록 구현합니다.MessageCreatedEvent를 정의하고 새로운 메시지가 등록되면 이벤트를 발행하세요.사용자 별로 관심있는 채널의 알림만 받을 수 있도록 ReadStatus 엔티티에 채널 알림 여부 속성(
notificationEnabled)을 추가하세요.true로 초기화합니다.false로 초기화합니다.알림 여부를 수정할 수 있게
ReadStatusUpdateRequest를 수정하세요.RoleUpdatedEvent를 정의하고 권한이 변경되면 이벤트를 발행하세요.NotificationDto를 정의하세요.receiverId: 알림을 수신할 User의id입니다.GET /api/notifications200 List<NotifcationDto>401 ErrorResponseDELETE /api/notifications/{notificationId}204 Void401 ErrorResponse403 ErrorResponse404 ErrorResponse이벤트를 처리할 리스너를 구현하세요.
on(MessageCreatedEvent)해당 채널의 알림 여부를 활성화한
ReadStatus를 조회합니다.해당
ReadStatus의 사용자들에게 알림을 생성합니다.단, 해당 메시지를 보낸 사람은 알림 대상에서 제외합니다.
on(RoleUpdatedEvent)권한이 변경된 당사자에게 알림을 생성합니다.
비동기 적용하기
AsyncConfig) 클래스를 구현하세요.@EanbleAsync어노테이션을 활용하세요.TaskExecutor를 Bean으로 등록하세요.TaskDecorator를 활용해 MDC의 Request ID, SecurityContext의 인정 정보가 비동기 스레드에서도 유지되도록 구현하세요.@Async어노테이션을 활용하세요.파일 업로드 로직에 의도적인 지연(
Thread.sleep(…))을 발생시키세요.메시지 생성 API의 실행 시간을 측정해보세요.
@Timed어노테이션을 메소드에 추가합니다.Actuator 설정을 추가합니다.
/actuator/metrics/message.create.async에서 측정된 시간을 확인할 수 있습니다.@EnableAsync를 활성화 / 비활성화 해보면서 동기 / 비동기 처리 간 응답 속도의 차이를 확인해보세요.비동기 실패 처리하기
org.springframework.retry:spring-retry의존성을 추가하세요.@EnableRetry어노테이션을 활용해 Spring Retry를 활성화 하세요.@Retryable어노테이션을 사용해 재시도 정책(횟수, 대기 시간 등)을 설정하세요.@Recover어노테이션을 활용하세요.실패 정보를 관리자에게 통지하세요.
캐시 적용하기
org.springframework.boot:spring-boot-starter-cache의존성을 추가하세요.com.github.ben-manes.caffeine:caffeine의존성을 추가하세요.@Cacheable어노테이션을 활용해 캐시가 필요한 메소드에 적용하세요.@CacheEvict,@CachePut,CacheManager등을 활용하세요.Caffein Spec에
recordStats옵션을 추가하세요./actuator/caches,/actuator/metrics/cache.*를 통해 캐시 관련 데이터를 확인해보세요.유의사항
Spring Kafka 도입하기
Docker Compose를 활용해 Kafka를 구동하세요.
Spring Kafka 의존성을 추가하고,
application.yml에 Kafka 설정을 추가하세요.implementation 'org.springframework.kafka:spring-kafka'Spring Event를 Kafka로 발행하는 리스너를 구현하세요.
NotificationRequiredEventListener는 비활성화하세요.KafkaProduceRequiredEventListener를 구현하세요.Kafka Console을 통해 Kafka 이벤트가 잘 발행되는지 확인해보세요.
broker컨테이너 쉘 접속docker exec -it -w /opt/kafka/bin broker sh토픽 리스트 확인 (실행위치:
/opt/kafka/bin)특정 토픽 이벤트 구독 및 대기 (실행위치:
/opt/kafka/bin)Kafka 토픽을 구독해 알림을 생성하는 리스너를 구현하세요.
이 리스너는 메인 서비스와 별도의 서버로 구성된 알림 서비스라고 가정합니다.
NotificationRequiredTopicListener를 구현하세요.기존
@EventListener기반 로직을 제거하고@KafkaListener로 대체하세요.Redis Cache 도입하기
대용량 트래픽을 감당하기 위해 서버의 인스턴스를 여러 개로 늘렸다고 가정해봅시다.
Caffeine과 같은 로컬 캐시는 서로 다른 서버에서 더 이상 활용할 수 없습니다.따라서
Redis를 통해 전역 캐시 저장소를 구성합니다.Redis 환경을 구성하세요.
Docker Compose를 활용해 Redis를 구동하세요.
Redis 의존성을 추가하고,
application.yml에 Redis 설정을 추가하세요.직렬화 설정을 위해 다음과 같이 Bean을 선언하세요.
DataGrip을 통해 Redis에 저장된 캐시 정보를 조회해보세요.