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

feat: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전 #963

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from

Conversation

eun-byeol
Copy link
Contributor

@eun-byeol eun-byeol commented Jan 29, 2025

🚩 연관 이슈

close #941


📝 작업 내용

1. 스케줄링 알림 발송 & fallback 전략

구체적인 로직은 노션에 정리해두었어요.
설명한 부분이지만 간단히 정리하면, 로직의 큰 그림은 이렇습니다.

image
약속시간 30분 전, 약속의 topic으로 스케줄링용 FCM 알림(eta scheduling notice)을 보냅니다.
안드는 해당 FCM 알림을 받으면, 약속시간+1분까지 10초마다 ETA API 요청을 예약합니다.

image
ETA API가 정상적으로 서버로 들어오지 않는 경우, 스케줄링용 FCM 알림을 device 별로 재전송합니다.
EtaService.findAllMateEtas()가 호출될 때마다, 레디스에 데이터를 갱신하는데,
그 전에 TTL 만료되면, 재전송 로직이 트리거됩니다.

핵심 로직을 시나리오로 테스트했습니다.

@DisplayName("정상 시나리오 : 스케줄링 그룹 알림 전송 -> ETA 요청 -> 스케줄링 개인 알림 재전송 X")
@TestFactory
Stream<DynamicTest> normalCase() {
    LocalDateTime now = TimeUtil.nowWithTrim();
    Meeting meeting = fixtureGenerator.generateMeeting(now.plusMinutes(30));
    Mate mate = fixtureGenerator.generateMate(meeting);
    fixtureGenerator.generateEta(mate);

    return Stream.of(
            dynamicTest("참가자들에게 ETA 스케줄링 알림을 전송한다.", () -> {
                etaSchedulingService.sendNotice(meeting);
                verify(noticeService).send(any(EtaSchedulingNotice.class), any(GroupMessage.class));
            }),
            dynamicTest("TTL 만료 전에 ETA api 요청이 오면, 스케줄링 알림이 재전송되지 않는다", () -> {
                Thread.sleep(ttlMs - 200);
                etaService.findAllMateEtas(dtoGenerator.generateMateEtaRequest(), mate);
                Thread.sleep(ttlMs - 200);
                verify(noticeService, never()).send(any(EtaSchedulingNotice.class), any(DirectMessage.class));
            })
    );
}

ttl 만료 직전까지 Thread.sleep()을 하지 않고 바로 eta api를 호출하도록 해도 되지만, 현실적인 딜레이(?)를 테스트코드에 담고자 했습니다.

@DisplayName("재발송 시나리오 : 스케줄링 그룹 알림 전송 -> ETA 요청 X -> 스케줄링 개인 알림 재전송")
@TestFactory
Stream<DynamicTest> resendCase() {
    LocalDateTime now = TimeUtil.nowWithTrim();
    Meeting meeting = fixtureGenerator.generateMeeting(now.plusMinutes(30));
    Mate mate = fixtureGenerator.generateMate(meeting);
    fixtureGenerator.generateEta(mate);

    return Stream.of(
            dynamicTest("참자가들에게 ETA 스케줄링 알림을 전송한다.", () -> {
                etaSchedulingService.sendNotice(meeting);
                verify(noticeService).send(any(EtaSchedulingNotice.class), any(GroupMessage.class));
            }),
            dynamicTest("TTL 만료 전에 ETA api 요청이 안 오면, 스케줄링 알림이 재전송된다.", () -> {
                Thread.sleep(ttlMs + 200);
                verify(noticeService).send(any(EtaSchedulingNotice.class), any(DirectMessage.class));
            })
    );
}

2. 스케줄링 등록 예외 처리

세 가지 예외 사항이 있습니다.

  1. (처리) 서버 재배포로 스케줄링이 소실되는 경우 -> 서버가 다시 뜰 때, 스케줄링 재시도 로직 구현
  2. (처리) 스케줄링 이후에 새로운 당일 약속이 생기는 경우 -> 당일 약속이면, 개별로 스케줄링
  3. (처리X) 약속시간 30분 전 이후에 약속을 생성한 경우(ex. 약속시간: 10시, 생성시간: 9시 45분)
    -> 최초 스케줄링 알림은 Meeting 생성 시점에 Topic 단위로 발송되기 때문에, 즉시 발송시 참여한 mate가 없어 아무도 스케줄링 알림을 받지 못합니다.
    해당 경우 안드에서 예외처리를 할지(=스케줄링 알림을 기다리지 않고 바로 api 예약 진행), 백에서 예외처리할지 고민이 됩니다.
    백에서 처리하려면 mate 생성 코드에서 스케줄링 로직이 침범하는데 코드 복잡성이 늘어나는 상황입니다. ‼️ 이에, 의견 부탁드립니다~!

3. [리팩터링] Notice 관련 로직 분리

이유: 기존 NotificationService에서는 알림 발송 시 Notification을 생성하는 로직과 Notification 생성하지 않는 로직이 혼재되어 있었어요. 확장성과 유지보수를 위해 도메인과 서비스를 나눴습니다.

Notice Type
- ETA_NOTICE
- ETA_SCHEDULING_NOTICE

Notification Type
- ENTRY
- DEPARTURE_REMINDER
- NUDGE
- LEAVE
- MEMBER_DELETION

4. [리팩터링] GroupMessage, DirectMessage 추상화

그룹대상이냐 개인이냐 하는 전송 대상만 다를 뿐, NoticeEvent는 같은 로직이라
AbstractMessage로 추상화하고 제네릭 사용하여 리팩터링했습니다.

    @Async("fcmAsyncExecutor")
    @EventListener
    public <T extends AbstractMessage> void sendNoticeMessage(NoticeEvent<T> noticeEvent) {
        fcmPushSender.sendMessage(noticeEvent.getMessage());
        log.info(
                "{} 공지 알림 전송 | 전송 시간 : {}",
                noticeEvent.getNotice().getType(),
                Instant.now().atZone(TimeUtil.KST_OFFSET)
        );
    }

NoticeType, Message 대상에 관계 없이 모두 같은 로직으로 처리할 수 있습니다.


🏞️ 스크린샷 (선택)


🗣️ 리뷰 요구사항 (선택)

🙇‍♀️ 약속시간 30분 전 이후에 약속을 생성한 경우, 예외처리 부분 의견 부탁드립니다.
최대한 레거시가 되지 않도록 해보았는데, 그럼에도 불구하고 로직이 깔끔하게 떨어지진 않아요.ㅠㅜ
리팩터링을 나눠서 하지 못해, PR 규모도 커서 최대한 설명을 남겼는데
궁금한 점이나 개선사항이 있으면 적극적으로 말씀해주세요!

@eun-byeol eun-byeol changed the title feature: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전 feature: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전(리뷰X) Jan 29, 2025
@eun-byeol eun-byeol self-assigned this Jan 29, 2025
Copy link

github-actions bot commented Jan 29, 2025

Test Results

 67 files  + 6   67 suites  +6   12s ⏱️ ±0s
219 tests +15  215 ✅ +11  4 💤 +4  0 ❌ ±0 
220 runs  +15  216 ✅ +11  4 💤 +4  0 ❌ ±0 

Results for commit 7d914f3. ± Comparison against base commit a89b34c.

This pull request removes 7 and adds 22 tests. Note that renamed tests count towards both.
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [1] accessToken=com.ody.auth.token.AccessToken@6f04acf2
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [2] accessToken=com.ody.auth.token.AccessToken@7298a2c1
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [1] date=2025-01-28, time=21:17:06.399922716, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [2] date=2025-01-28, time=22:17:06.399941912, expected=true
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [3] date=2025-01-28, time=20:17:06.399932023, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [4] date=2025-01-29, time=21:17:06.399922716, expected=true
com.ody.meeting.service.MeetingServiceTest ‑ 약속 생성 후 약속 시간 30분 전에 ETA 공지 알림이 예약된다.
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [1] accessToken=com.ody.auth.token.AccessToken@2190fd9f
com.ody.auth.JwtTokenProviderTest$validateAccessToken ‑ [2] accessToken=com.ody.auth.token.AccessToken@3e1eeaa5
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [1] date=2025-02-03, time=01:29:01.822704162, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [2] date=2025-02-03, time=02:29:01.822729590, expected=true
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [3] date=2025-02-03, time=00:29:01.822716926, expected=false
com.ody.common.validator.FutureOrPresentDateTimeValidatorTest ‑ [4] date=2025-02-04, time=01:29:01.822704162, expected=true
com.ody.eta.service.EtaSchedulingKeyTest ‑ EtaSchedulingKey 구분자 형식이 맞지 않으면 예외가 발생한다.
com.ody.eta.service.EtaSchedulingKeyTest ‑ EtaSchedulingRedisTemplate key를 생성한다.
com.ody.eta.service.EtaSchedulingRedisTemplateTest ‑ 약속방 참여자 1명의 ETA 스케줄링 정보를 캐싱한다.
com.ody.eta.service.EtaSchedulingRedisTemplateTest ‑ 약속방 참여자 전원의 ETA 스케줄링 정보를 캐싱한다.
…

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Feb 2, 2025

📝 Test Coverage Report

Overall Project 81.68% -0.55% 🍏
Files changed 93.6% 🍏

File Coverage
PushEvent.java 100% 🍏
NoticeEvent.java 100% 🍏
EtaSchedulingKey.java 100% 🍏
GroupMessage.java 100% 🍏
AbstractMessage.java 100% 🍏
NoticeType.java 100% 🍏
EtaSchedulingNotice.java 100% 🍏
EtaNotice.java 100% 🍏
NotificationType.java 100% 🍏
RedisKeyExpiredListener.java 100% 🍏
NoticeService.java 100% 🍏
NotificationService.java 100% 🍏
EtaSchedulingService.java 99.27% -0.73% 🍏
EtaService.java 97.11% 🍏
MeetingService.java 95.74% 🍏
FcmPushSender.java 84.81% 🍏
EtaSchedulingRedisTemplate.java 83.72% -16.28% 🍏
FcmEventListener.java 76.34% -11.83%
Notice.java 66.67% -33.33%
DirectMessage.java 35.42% 🍏
RedisConfig.java 0% -88%

@eun-byeol eun-byeol requested a review from mzeong February 2, 2025 16:16
@eun-byeol eun-byeol changed the title feature: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전(리뷰X) feature: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전 Feb 2, 2025
@eun-byeol eun-byeol changed the title feature: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전 feat: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전 Feb 3, 2025
Copy link
Member

@mzeong mzeong left a comment

Choose a reason for hiding this comment

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

진행 상황 공유를 위해 중간 제출합니다
현재 8c0523c까지 확인했어요 (이 속도라면 목요일에는 끝날 거라고 예상(희망)합니다)
코드 리뷰 완료했습니다 ‼️ 조조의 노고가 느껴지는 코드였습니다 고생하셨습니다 🐥🐥

(커밋 단위로 확인한 후 전체 files changed 보는 순서로 코드 리뷰 진행하겠습니다!!
따라서 이미 수정된 과거 코드에 대해 리뷰를 남길 수도 있는데 리뷰 모두 완료하면 수정되어 있을 겁니다)

몇주째 기다리는 조조와 올리브 미안합니다 😓
이번 주말에는 조조가 꼭 코드 리뷰 반영 작업을 할 수 있기를..

.putData("meetingTime", meeting.getMeetingTime().format(FORMATTER))
.putData("type", etaSchedulingNotice.getType().name())
.putData("meetingId", String.valueOf(etaSchedulingNotice.getMeetingId()))
.putData("meetingTime", etaSchedulingNotice.getMeetingTime().format(FORMATTER))
.setTopic(fcmTopic.getValue())
.build();

return new GroupMessage(message);
}
Copy link
Member

Choose a reason for hiding this comment

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

[제안] Notice 타입마다 create 메서드를 하나씩 추가해야 한다는 점이 확장성 부분에서 아쉬운 것 같아 제안해봅니다

public static GroupMessage create(Notice notice, FcmTopic fcmTopic) {
        Message.Builder builder = Message.builder()
                .putData("type", notice.getType().name())
                .setTopic(fcmTopic.getValue());

        notice.getAdditionalData().forEach(builder::putData);

        return new GroupMessage(builder.build());
    }
public class EtaSchedulingNotice extends Notice {
    
    private Long meetingId;
    private LocalDateTime meetingTime;

    @Override
    public Map<String, String> getAdditionalData() {
        return Map.of(
            "meetingId", String.valueOf(meetingId),
            "meetingTime", meetingTime.format(FORMATTER)
        );
    }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

[사소한 의견]

확장성 측면에서는 제리의 제안에 동의해요. 그런데 그보다 저는 근본적으로 Notice라는 개념이 어떤 개념인지 통일하고 가는게 좋을 것 같다는 생각이 들었어요

저는 Notice라는 개념이 약속에 참여한 모든 유저에게 애플리케이션 상에서 실제로 전해지는 공지 느낌으로 생각했거든요. 그래서 Notice를 애플리케이션에서 공지가 전해지는 것과 무관하게 약속 topic을 대상으로 뿌리는 개념으로 추상화할건지, 아니면 실제 공지가 되는 내용인지의 기준을 세워야 할 것 같아요.

  1. 유저에게 전해지는가 ? -> 스케쥴링 로직은 안 전해짐 vs EtaNotice는 진짜 발송되는 공지용임
  2. 약속 내 참여한 모든 사용자에게 전해지는가? -> 이건 둘다 맞는 개념

2)번만으로 Notice에 대한 용어를 통일하고자 하면 제리가 제안한 구조로 해도 괜찮을 것 같고 만약 1번 까지 Notice의 개념 정의에 포함한다고 한다면 저는 Trigger로 Notice와 도메인을 구분하는 것도 좋을 것 같아요. 다만 리소스가 드는 일이니 Notice로 유지하는 방향이 나아보입니다!

Copy link
Member

Choose a reason for hiding this comment

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

TTL이 만료되면 디바이스 토큰을 이용해 개인에게 Notice를 하기 때문에 2번으로 정의하는 것도 부정확하지 않나요? 저는 로그에서 확인할 수 있는가를 기준으로 Notification, Notice를 나눴다고 이해했는데 조조의 정의가 궁금하네요

Comment on lines +72 to +74
private boolean isPast(LocalDateTime dateTime) {
return TimeUtil.nowWithTrim().isAfter(dateTime);
}
Copy link
Member

Choose a reason for hiding this comment

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

[제안] isUpcoming, isPast 메서드는 TimeUtil이 가지고 있어도 좋을 것 같네요!

@@ -36,7 +36,7 @@ public interface MeetingRepository extends JpaRepository<Meeting, Long> {
or (m.date = :endDate and m.time < :excludeEndTime)
"""
)
List<Meeting> findAllWithInDateTimeRange(
List<Meeting> findAllWithInDateTimeRange( // StartDateTime >= TargetDateTime > EndDateTime
Copy link
Member

Choose a reason for hiding this comment

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

[제안] 🤔🤔 이름을 고민해보겠습니다.. findAllByDateTimeInClosedOpenRange으로 네이밍하는 건 어떤가요? 주석 없이도 [Start, End)를 드러낼 수 있을 것 같아요

@@ -63,10 +65,23 @@ public class Fixture {
InviteCodeGenerator.generate()
);

public static Meeting Meeting(long meetingId, LocalDateTime dateTime) {
return new Meeting(meetingId, "조조와 새해 떡국", dateTime.toLocalDate(), dateTime.toLocalTime(), TARGET_LOCATION, ODY_MEETING.getInviteCode(), false);
Copy link
Member

Choose a reason for hiding this comment

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

😋

Copy link
Contributor

@coli-geonwoo coli-geonwoo left a comment

Choose a reason for hiding this comment

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

저도 84e82cc까지 리뷰했습니다! 추후 커밋 따라가면서 계속 갱신할게요!

.putData("meetingTime", meeting.getMeetingTime().format(FORMATTER))
.putData("type", etaSchedulingNotice.getType().name())
.putData("meetingId", String.valueOf(etaSchedulingNotice.getMeetingId()))
.putData("meetingTime", etaSchedulingNotice.getMeetingTime().format(FORMATTER))
.setTopic(fcmTopic.getValue())
.build();

return new GroupMessage(message);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

[사소한 의견]

확장성 측면에서는 제리의 제안에 동의해요. 그런데 그보다 저는 근본적으로 Notice라는 개념이 어떤 개념인지 통일하고 가는게 좋을 것 같다는 생각이 들었어요

저는 Notice라는 개념이 약속에 참여한 모든 유저에게 애플리케이션 상에서 실제로 전해지는 공지 느낌으로 생각했거든요. 그래서 Notice를 애플리케이션에서 공지가 전해지는 것과 무관하게 약속 topic을 대상으로 뿌리는 개념으로 추상화할건지, 아니면 실제 공지가 되는 내용인지의 기준을 세워야 할 것 같아요.

  1. 유저에게 전해지는가 ? -> 스케쥴링 로직은 안 전해짐 vs EtaNotice는 진짜 발송되는 공지용임
  2. 약속 내 참여한 모든 사용자에게 전해지는가? -> 이건 둘다 맞는 개념

2)번만으로 Notice에 대한 용어를 통일하고자 하면 제리가 제안한 구조로 해도 괜찮을 것 같고 만약 1번 까지 Notice의 개념 정의에 포함한다고 한다면 저는 Trigger로 Notice와 도메인을 구분하는 것도 좋을 것 같아요. 다만 리소스가 드는 일이니 Notice로 유지하는 방향이 나아보입니다!

//redis 실패시 재전송
public void sendNotice(long meetingId) {
meetingRepository.findById(meetingId)
.ifPresent(meeting -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

오.. 약속 검증까지.. 역시 꼼꼼하네요 조조 👍

@@ -27,4 +31,19 @@ public static DirectMessage createMessageToSelf(Notification notification) {

return new DirectMessage(message);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

[제안]

DirectMessage 클래스에서 createMessageToSelf는 더이상 쓰이는 곳이 없는 것 같아요!

Uploading image.png…


@Getter
@RequiredArgsConstructor
public abstract class Notice {
Copy link
Contributor

Choose a reason for hiding this comment

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

[사소한 제안]

이것도 Notice의 개념정의와 관련이 있다고 생각하는데, 만약 특정 약속 대상자 모두에게 뿌리는 공지를 Notice로 가정한다면 개념상 meetingId를 들고 있어도 괜찮다고 생각합니다.

private final FcmEventPublisher fcmEventPublisher;
private final TaskScheduler taskScheduler;

public <T extends AbstractMessage> void send(Notice notice, T t) {
Copy link
Contributor

Choose a reason for hiding this comment

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

제네릭을 통한 활용성 확장이 좋아요 👍

return new EtaSchedulingKey(
mate.getMember().getDeviceToken().getValue(),
mate.getMeeting().getId(),
LocalDateTime.of(mate.getMeeting().getDate(), mate.getMeeting().getTime())
Copy link
Contributor

Choose a reason for hiding this comment

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

[사소한 제안]

Meeting.getMeetingTime() 활용하시는 건 어떤가요?

);
}

public String serialize() {
Copy link
Contributor

Choose a reason for hiding this comment

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

[순수한 질문]

이건 정말 순수한 질문인데요. json 형태로 redis 에 저장하면 아마 Java 객체로 역직렬화 할 수 있을 것 같은데 delimiter를 이용해 String 값을 저장하고 파싱하도록 한 이유가 따로 있는지 궁금해요. 메모리 공간 때문인가요?

.set(etaSchedulingKey.serialize(), LocalDateTime.now().toString(), ttlMs, TimeUnit.MILLISECONDS);
}

public String get(EtaSchedulingKey etaSchedulingKey) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[제안]

어떤걸 가져오는지 잘 모르겠어요
getXXX로 의미를 부여해주면 더 잘 읽힐 것 같아요!

}

public void sendNotice(long meetingId) {
meetingRepository.findById(meetingId)
private void noticeGroupMessageAndCache(EtaSchedulingNotice notice) {
Copy link
Contributor

Choose a reason for hiding this comment

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

[사소한 제안]

파라미터도 EtaSchedulignNotice를 받고 있어서 그룹 메시지를 보내는 것보다 etaschedulingnotice를 보낸다는 의미를 더 담으면 좋을 것 같아요

sendEtaSchedulingNoticeAndCache 어떠신가요?


@DisplayName("재발송 시나리오 : 스케줄링 그룹 알림 전송 -> ETA 요청 X -> 스케줄링 개인 알림 재전송")
@TestFactory
Stream<DynamicTest> resendCase() {
Copy link
Contributor

Choose a reason for hiding this comment

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

다이나믹 테스트 좋네요 👍 덕분에 흐름이 잘 이해되었습니다.


@Transactional
public MeetingSaveResponseV1 saveV1(MeetingSaveRequestV1 meetingSaveRequestV1) {
String inviteCode = generateUniqueInviteCode();
Meeting meeting = meetingRepository.save(meetingSaveRequestV1.toMeeting(inviteCode));
scheduleEtaNotice(meeting);
scheduleNoticeIfUpcomingMeeting(meeting); // TODO: 예외상황) 약속시간 30분 이내 약속 생성의 경우, 참가한 Mate가 없어 스케줄링 사이클이 걸리지 않음. 스케줄링 없이 바로 오디창이 시작되는 경우 안드에서 처리할지, Mate save 시점에 엣지 케이스 둘지 고민.
Copy link
Member

Choose a reason for hiding this comment

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

이 부분 안드에서 처리하는 걸로 결론난 건가요? 그렇다면 투두는 지워도 될 것 같아요

}

private void scheduleNoticeIfUpcomingMeeting(Meeting meeting) {
LocalDateTime meetingDateTime = LocalDateTime.of(meeting.getDate(), meeting.getTime());
Copy link
Member

Choose a reason for hiding this comment

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

이곳에서도 getMeetingTime 사용해도 될 것 같아요~

@@ -6,8 +6,7 @@ public enum NotificationType {
DEPARTURE_REMINDER,
NUDGE,
LEAVE,
MEMBER_DELETION,
ETA_NOTICE,
MEMBER_DELETION
Copy link
Member

Choose a reason for hiding this comment

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

컴마는 남겨두어도 좋을 것 같아요 😙

Suggested change
MEMBER_DELETION
MEMBER_DELETION,

.putData("meetingTime", meeting.getMeetingTime().format(FORMATTER))
.putData("type", etaSchedulingNotice.getType().name())
.putData("meetingId", String.valueOf(etaSchedulingNotice.getMeetingId()))
.putData("meetingTime", etaSchedulingNotice.getMeetingTime().format(FORMATTER))
.setTopic(fcmTopic.getValue())
.build();

return new GroupMessage(message);
}
Copy link
Member

Choose a reason for hiding this comment

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

TTL이 만료되면 디바이스 토큰을 이용해 개인에게 Notice를 하기 때문에 2번으로 정의하는 것도 부정확하지 않나요? 저는 로그에서 확인할 수 있는가를 기준으로 Notification, Notice를 나눴다고 이해했는데 조조의 정의가 궁금하네요

Comment on lines +77 to +84
public static Member Member(long memberId, DeviceToken deviceToken) {
return new Member(memberId, MEMBER1.getAuthProvider(), MEMBER1.getNickname(), "imageUrl1", deviceToken, MEMBER1.getRefreshToken(), null);
}

public static Mate Mate(long mateId, Meeting meeting, Member member) {
return new Mate(mateId, meeting, member, member.getNickname(), ORIGIN_LOCATION, 60L, null);
}

Copy link
Member

Choose a reason for hiding this comment

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

[질문] 정적 필드보다 메서드 형태가 좋은가요?

@DisplayName("ETA 목록 조회 시 API 호출 카운팅 Redisson 분산락 동시성 테스트")
@Nested
public class RedissonDistributedLockTest {
class RedissonDistributedLockTest {
Copy link
Member

Choose a reason for hiding this comment

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

[질문] 해당 테스트 코드는 남겨지는 건가요 어떻게 되나요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature: 약속 30분 전 최초 스케줄링 책임 백엔드로 이전
3 participants