Skip to content
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

refactor: 알림 기능 로직 개선 및 알림 테스트 추가 #497

Merged
merged 45 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7482a31
test: 테스트용 api 수정 및 추가
Kim0914 Jan 6, 2024
bea9ea6
test: @EnableAsync 어노테이션 추가
Kim0914 Jan 6, 2024
ffe1549
test: 알림 이벤트 로그 정보 추가
Kim0914 Jan 6, 2024
4a5082f
test: 미사용 테스트 api 주석 처리
Kim0914 Jan 6, 2024
a22ec60
refactor: Async ThreadExecutor 설정 변경
Kim0914 Jan 9, 2024
47dc133
refactor: 불필요한 트랜잭션 설정 제거
Kim0914 Jan 9, 2024
68644df
refactor: Async ThreadPool 설정 수정
Kim0914 Jan 9, 2024
c6d8f44
refactor: Async ThreadPool 설정 30 수정
Kim0914 Jan 10, 2024
8472a4a
refactor: Async ThreadPool 설정 20 수정
Kim0914 Jan 10, 2024
cdcb3c2
refactor: Async ThreadPool 설정 40 수정
Kim0914 Jan 10, 2024
0b3b9c8
refactor: Async ThreadPool 설정 20 수정
Kim0914 Jan 10, 2024
9fc3d3d
refactor: Async ThreadPool 설정 30 수정
Kim0914 Jan 10, 2024
6478813
refactor: Async ThreadPool 설정 40 수정
Kim0914 Jan 10, 2024
8630a23
refactor: Async ThreadPool 램프업 스레드 수 변경
Kim0914 Jan 10, 2024
5604c11
refactor: Async ThreadPool 스레드 수 변경
Kim0914 Jan 11, 2024
9fd8651
refactor: Async ThreadPool 스레드 수 변경
Kim0914 Jan 11, 2024
b615b8b
refactor: Async ThreadPool 스레드 수 변경
Kim0914 Jan 11, 2024
597f09a
refactor: Async ThreadPool 스레드 수 변경
Kim0914 Jan 11, 2024
3db6b51
refactor: Async ThreadPool 스레드 수 변경
Kim0914 Jan 11, 2024
e28bad0
refactor: Async ThreadPool 스레드 수 30으로 변경
Kim0914 Jan 11, 2024
89435a4
refactor: Async ThreadPool 스레드 수 50으로 변경
Kim0914 Jan 11, 2024
95cc8e6
refactor: Async ThreadPool 스레드 수 40으로 변경
Kim0914 Jan 11, 2024
659ca56
refactor: Async ThreadPool 스레드 수 30으로 변경
Kim0914 Jan 11, 2024
1a13090
refactor: Async ThreadPool 스레드 수 100으로 변경
Kim0914 Jan 11, 2024
eb10b0c
refactor: Async ThreadPool 스레드 수 8으로 변경
Kim0914 Jan 16, 2024
33d1c88
refactor: Async 테스트 설정 변경
Kim0914 Jan 16, 2024
9544240
refactor: Async 테스트 스레드 풀 설정 15로 변경
Kim0914 Jan 16, 2024
81d17c3
refactor: Async 테스트 스레드 풀 설정 15로 변경
Kim0914 Jan 16, 2024
e437712
refactor: Async 테스트 스레드 풀 설정 25로 변경
Kim0914 Jan 16, 2024
63422eb
refactor: 램프 코드 반복 횟수 변경
Kim0914 Jan 16, 2024
837d2e1
refactor: 알림 허용 시 알림 발송 로직 추가
Kim0914 Jan 17, 2024
f2a1e6e
refactor: Async thread 30으로 변경
Kim0914 Jan 17, 2024
7def1fb
refactor: Async thread 35으로 변경
Kim0914 Jan 17, 2024
0db6553
refactor: Async thread 45으로 변경
Kim0914 Jan 17, 2024
c5e2efe
refactor: Async thread 50으로 변경
Kim0914 Jan 17, 2024
75d17e2
refactor: Async thread 40으로 변경
Kim0914 Jan 17, 2024
01c06a8
refactor: Async thread 50으로 변경
Kim0914 Jan 17, 2024
fcd29a3
test: 알림 구독 시 알림 발송 이벤트 테스트 추가
Kim0914 Jan 18, 2024
2b1b2cf
test: 알림 전송 메서드 테스트 추가
Kim0914 Jan 18, 2024
d2defe0
refactor: 알림 발송 로직 변경
Kim0914 Jan 18, 2024
3e8c068
test: 알림 이벤트 리스너 테스트 추가
Kim0914 Jan 18, 2024
0bee745
chore: 테스트 컨트롤러 비활성화
Kim0914 Jan 22, 2024
3724a82
chore: 비동기 스레드 풀 코어 수 40으로 설정
Kim0914 Jan 22, 2024
9fd8bdf
chore: 불필요한 주석 제거
Kim0914 Jan 22, 2024
a69624d
refactor: 사용하지 않는 이벤트 리스너 및 이벤트 객체 삭제
Kim0914 Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.official.pium.admin.service;

import com.official.pium.petPlant.domain.PetPlant;
import com.official.pium.petPlant.event.notification.NotificationEvent;
import com.official.pium.petPlant.repository.PetPlantRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class TestService {

private final PetPlantRepository petPlantRepository;
private final ApplicationEventPublisher publisher;

// public void sendWaterNotificationTest() {
// List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(7L);
// List<NotificationEvent> events = petPlants.stream()
// .map(plant -> NotificationEvent.builder()
// .title(plant.getNickname())
// .body("(테스트 중) 물을 줄 시간이에요!")
// .deviceToken(plant.getMember().getDeviceToken())
// .build()
// ).toList();
// log.info("동기 알림 테스트 시작. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
// publisher.publishEvent(NotificationEvents.from(events));
// log.info("동기 알림 테스트 종료. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
// }

public void sendWaterNotificationAsyncRampTest() {
List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(6L);
// List<NotificationEvent> events = petPlants.stream()
// .map(plant -> NotificationEvent.builder()
// .title(plant.getNickname())
// .body("(테스트 중) 물을 줄 시간이에요!")
// .deviceToken(plant.getMember().getDeviceToken())
// .build()
// ).toList();

for (int i = 0; i < 100; i++) {
PetPlant petPlant = petPlants.get(i);
NotificationEvent event = NotificationEvent.builder()
.title(petPlant.getNickname())
.body("물줘")
.deviceToken(petPlant.getMember().getDeviceToken())
.build();
publisher.publishEvent(event);
}

// log.info("비동기 테스트 램프업 시작");
// for (int i = 0; i < 100; i++) {
// NotificationEvent notificationEvent = events.get(i);
// publisher.publishEvent(notificationEvent);
// }
}

public void sendWaterNotificationAsyncTest() {
List<PetPlant> petPlants = petPlantRepository.findAllByMemberId(6L);
List<NotificationEvent> events = petPlants.stream()
.map(plant -> NotificationEvent.builder()
.title(plant.getNickname())
.body("(테스트 중) 물을 줄 시간이에요!")
.deviceToken(plant.getMember().getDeviceToken())
.build()
).toList();

int i = 1;
log.info("비동기 알림 테스트 시작. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
for (NotificationEvent event : events) {
log.info(i++ + "번째 알림 이벤트");
publisher.publishEvent(event);
}
log.info("비동기 알림 테스트 종료. Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.official.pium.member.domain.Member;
import com.official.pium.member.repository.MemberRepository;
import com.official.pium.notification.application.NotificationService;
import com.official.pium.petPlant.application.ReminderService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -44,7 +43,6 @@ public class AdminPageController {
private final MemberRepository memberRepository;
private final AdminService adminService;
private final NotificationService notificationService;
private final ReminderService reminderService;

@GetMapping("/**")
public String adminPage(@AdminAuth Admin admin, Model model) {
Expand Down Expand Up @@ -164,10 +162,4 @@ public ResponseEntity<Void> logout(HttpServletRequest request) {

return ResponseEntity.ok().build();
}

@GetMapping("/notifications")
public ResponseEntity<String> notificationTest() {
reminderService.sendWaterNotificationTest();
return ResponseEntity.ok("알림 기능 테스트 성공");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.official.pium.admin.ui;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/test")
public class TestController {

// private final TestService testService;

// @GetMapping("/notifications")
// public ResponseEntity<String> notificationTest() {
// testService.sendWaterNotificationTest();
// return ResponseEntity.ok("알림 기능 테스트 성공");
// }

// @GetMapping("/notifications/ramp")
// public ResponseEntity<String> notificationRampTest() {
// testService.sendWaterNotificationAsyncRampTest();
// return ResponseEntity.ok("비동기 알림 기능 테스트 램프업 성공");
// }
//
// @GetMapping("/notifications/async")
// public ResponseEntity<String> notificationAsyncTest() {
// testService.sendWaterNotificationAsyncTest();
// return ResponseEntity.ok("비동기 알림 기능 테스트 성공");
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.official.pium.config;

import java.util.concurrent.Executor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A

오 비동기 설정을 별도로 분리했군요! 👍


@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(40);
executor.setThreadNamePrefix("2024-Pium-Thread: ");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A

디버깅할때 용이하겠군요 👍

executor.initialize();
return executor;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.official.pium.member.application;

import com.official.pium.member.domain.Member;
import com.official.pium.petPlant.domain.PetPlant;
import com.official.pium.history.repository.HistoryRepository;
import com.official.pium.member.application.dto.OAuthProvider;
import com.official.pium.member.domain.Member;
import com.official.pium.member.repository.MemberRepository;
import com.official.pium.petPlant.repository.PetPlantRepository;
import com.official.pium.sessionGroup.repository.SessionGroupRepository;
import com.official.pium.notification.application.dto.NotificationCheckResponse;
import com.official.pium.notification.application.dto.NotificationSubscribeRequest;
import com.official.pium.member.application.dto.OAuthProvider;
import com.official.pium.petPlant.domain.PetPlant;
import com.official.pium.petPlant.event.notification.NotificationEvent;
import com.official.pium.petPlant.repository.PetPlantRepository;
import com.official.pium.sessionGroup.repository.SessionGroupRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -24,6 +26,7 @@ public class MemberService {
private final PetPlantRepository petPlantRepository;
private final SessionGroupRepository sessionGroupRepository;
private final OAuthProvider provider;
private final ApplicationEventPublisher publisher;

@Transactional
public void withdraw(Member member) {
Expand Down Expand Up @@ -51,6 +54,16 @@ public void subscribeNotification(Member member, NotificationSubscribeRequest re
throw new IllegalArgumentException("이미 알림을 구독하고 있습니다.");
}
member.updateDeviceToken(request.getToken());
sendAlarmNotification(member);
}

private void sendAlarmNotification(Member member) {
NotificationEvent event = NotificationEvent.builder()
.deviceToken(member.getDeviceToken())
.title("알림 설정 완료")
.body("리마인더 알림 받기 성공")
.build();
publisher.publishEvent(event);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.official.pium.member.ui;

import com.official.pium.member.domain.Member;
import com.official.pium.member.application.MemberService;
import com.official.pium.member.domain.Member;
import com.official.pium.notification.application.dto.NotificationCheckResponse;
import com.official.pium.notification.application.dto.NotificationSubscribeRequest;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -42,8 +42,10 @@ public ResponseEntity<NotificationCheckResponse> checkNotificationStatus(@Auth M
}

@PostMapping("/notification")
public ResponseEntity<Void> subscribeNotification(@Auth Member member,
@RequestBody @Valid NotificationSubscribeRequest request) {
public ResponseEntity<Void> subscribeNotification(
@Auth Member member,
@RequestBody @Valid NotificationSubscribeRequest request
) {
memberService.subscribeNotification(member, request);
return ResponseEntity.ok().build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,33 @@
import com.official.pium.petPlant.event.notification.NotificationEvent;
import com.official.pium.petPlant.event.notification.NotificationEvents;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class NotificationEventListener {

private final NotificationService notificationService;

@EventListener
@Async
public void handleNotificationEvent(NotificationEvent event) {
log.info("비동기 알림 START, Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
notificationService.sendNotification(event.getDeviceToken(), event.getTitle(), event.getBody());
log.info("비동기 알림 END, Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
}

@EventListener
@Async
public void handleNotificationEvents(NotificationEvents notificationEvents) {
log.info("동기 알림 START, Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A

잘 몰라서 여쭤봅니다~
@Async 가 붙어있는데도 동기적으로 동작하는건가요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지워야 하는 메소드인데 그대로 두고 있었군요...!
지금은 @EnableAsync 설정 때문에 본 메소드도 비동기적으로 동작하게 됩니다.
그러나 결국 이벤트 내부에서는 단일 스레드가 모든 notificationEvents를 처리하고 있어 여러개의 알림을 병렬적으로 처리할 수가 없는 상황인데 위 메소드의 단점이었어요ㅠㅠ
위 메소드는 지우도록 하겠습니다!

for (NotificationEvent event : notificationEvents.getNotificationEvents()) {
notificationService.sendNotification(event.getDeviceToken(), event.getTitle(), event.getBody());
}
log.info("동기 알림 END, Thread: " + Thread.currentThread().getId() + " " + Thread.currentThread().getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class NotificationService {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@
import com.official.pium.petPlant.domain.PetPlant;
import com.official.pium.petPlant.event.history.HistoryEvent;
import com.official.pium.petPlant.event.notification.NotificationEvent;
import com.official.pium.petPlant.event.notification.NotificationEvents;
import com.official.pium.petPlant.repository.PetPlantRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.NoSuchElementException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
Expand Down Expand Up @@ -81,27 +82,17 @@ public DataResponse<List<ReminderResponse>> readAll(Member member) {
@Scheduled(cron = "0 0 7 * * *")
public void sendWaterNotification() {
List<PetPlant> petPlants = petPlantRepository.findAllByWaterNotification(LocalDate.now());
List<NotificationEvent> events = petPlants.stream()
.map(plant -> NotificationEvent.builder()
.title(plant.getNickname())
.body("물을 줄 시간이에요!")
.deviceToken(plant.getMember().getDeviceToken())
.build()
).toList();

publisher.publishEvent(NotificationEvents.from(events));
log.info("[" + LocalDate.now() + " 물주기] " + "전체 알림: " + petPlants.size() + "개");
petPlants.forEach(this::sendNotification);

}

public void sendWaterNotificationTest() {
List<PetPlant> petPlants = petPlantRepository.findAll();
List<NotificationEvent> events = petPlants.stream()
.map(plant -> NotificationEvent.builder()
.title(plant.getNickname())
.body("(테스트 중) 물을 줄 시간이에요!")
.deviceToken(plant.getMember().getDeviceToken())
.build()
).toList();

publisher.publishEvent(NotificationEvents.from(events));
private void sendNotification(PetPlant petPlant) {
NotificationEvent.builder()
.title(petPlant.getNickname())
.body("물을 줄 시간이에요!")
.deviceToken(petPlant.getMember().getDeviceToken())
.build();
publisher.publishEvent(petPlant);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import com.official.pium.fixture.MemberFixture;
import com.official.pium.fixture.NotificationFixture;
import com.official.pium.notification.application.NotificationService;
import com.official.pium.petPlant.application.ReminderService;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.DisplayNameGeneration;
Expand Down Expand Up @@ -65,9 +64,6 @@ class AdminPageControllerTest extends UITest {
@MockBean
private NotificationService notificationService;

@MockBean
private ReminderService reminderService;

@Nested
class 페이지_정상_호출_ {

Expand Down
Loading
Loading