Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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,4 @@
package kr.ac.kookmin.stream.common.event;

/** 회원이 행사 신청을 취소했다. 행사명은 알림 문구에 쓰려고 취소 시점의 값을 싣는다. */
public record EventApplicationCanceled(Long memberId, Long eventId, String eventTitle) implements DomainEvent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package kr.ac.kookmin.stream.common.event;

/** 회원이 행사를 신청했다. 행사명은 알림 문구에 쓰려고 신청 시점의 값을 싣는다. */
public record EventApplied(Long memberId, Long eventId, String eventTitle) implements DomainEvent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package kr.ac.kookmin.stream.common.event;

/** 회원이 사물함을 신청해 배정받았다. 신청과 배정이 동시에 일어난다. */
public record LockerAssigned(Long memberId, Long lockerPeriodId) implements DomainEvent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package kr.ac.kookmin.stream.common.event;

/** 운영진이 새 공지를 등록했다. 공지는 저장하는 즉시 공개된다. */
public record NoticeCreated(Long noticeId) implements DomainEvent {}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import java.util.stream.Collectors;
import kr.ac.kookmin.stream.common.BusinessException;
import kr.ac.kookmin.stream.common.CursorSliceResult;
import kr.ac.kookmin.stream.common.OutboxWriter;
import kr.ac.kookmin.stream.common.event.EventApplicationCanceled;
import kr.ac.kookmin.stream.common.event.EventApplied;
import kr.ac.kookmin.stream.event.domain.event.domain.Event;
import kr.ac.kookmin.stream.event.domain.event.domain.EventApplication;
import kr.ac.kookmin.stream.event.domain.event.domain.EventApplicationAnswer;
Expand All @@ -31,6 +34,7 @@ class EventApplicationServiceImpl implements EventApplicationService {

private final EventRepository eventRepository;
private final EventApplyAnswerValidator eventApplyAnswerValidator;
private final OutboxWriter outboxWriter;

@Override
@Transactional(readOnly = true)
Expand All @@ -55,6 +59,7 @@ public EventApplicationResult apply(Long eventId, Long memberId, EventApplyComma
EventApplication.create(eventId, memberId, LocalDateTime.now())
);
eventRepository.saveAnswers(toAnswers(application.getId(), command));
outboxWriter.record(new EventApplied(memberId, eventId, event.getTitle()));

return new EventApplicationResult(application.getId(), event);
}
Expand Down Expand Up @@ -119,6 +124,7 @@ public void cancelApplication(Long applicationId, Long memberId) {

application.cancel(now);
eventRepository.saveApplication(application);
outboxWriter.record(new EventApplicationCanceled(memberId, event.getId(), event.getTitle()));
}

/** 남의 신청은 없는 것으로 보여야 하므로 조회 단계에서 회원 식별자로 함께 거른다. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.util.function.Function;
import java.util.stream.Collectors;
import kr.ac.kookmin.stream.common.BusinessException;
import kr.ac.kookmin.stream.common.OutboxWriter;
import kr.ac.kookmin.stream.common.event.LockerAssigned;
import kr.ac.kookmin.stream.event.domain.locker.domain.Locker;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerApplication;
import kr.ac.kookmin.stream.event.domain.locker.domain.LockerApplicationResult;
Expand All @@ -25,6 +27,7 @@ class LockerApplicationServiceImpl implements LockerApplicationService {

private final LockerRepository lockerRepository;
private final LockerApplicationRepository lockerApplicationRepository;
private final OutboxWriter outboxWriter;

@Override
@Transactional
Expand All @@ -35,6 +38,7 @@ public LockerApplicationResult apply(Long memberId, LockerApplyCommand command)
LockerApplication application = lockerApplicationRepository.save(
LockerApplication.create(period.getId(), memberId, locker.getId(), LocalDateTime.now())
);
outboxWriter.record(new LockerAssigned(memberId, period.getId()));
return new LockerApplicationResult(application, locker, period);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ private static Locker locker(LockerStatus status) {
}

private static LockerApplicationServiceImpl service(FakeLockerRepository repository) {
return new LockerApplicationServiceImpl(repository, repository.applications);
// 아웃박스 기록은 이 테스트의 관심사가 아니라 아무것도 하지 않는다
return new LockerApplicationServiceImpl(repository, repository.applications, event -> {});
}

private static LockerApplyCommand command() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package kr.ac.kookmin.stream.member.domain.notification.service.impl;

import java.util.List;
import kr.ac.kookmin.stream.common.event.EventApplicationCanceled;
import kr.ac.kookmin.stream.common.event.EventApplied;
import kr.ac.kookmin.stream.common.event.LockerAssigned;
import kr.ac.kookmin.stream.common.event.NoticeCreated;
import kr.ac.kookmin.stream.member.domain.notification.domain.NotificationType;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
* 다른 도메인의 이벤트를 알림으로 바꾼다. 아웃박스 릴레이가 트랜잭션 밖에서 발행하므로 {@code @EventListener}로 받는다.
* 예외를 던지면 릴레이가 같은 이벤트를 다시 발행한다.
*/
@Component
@RequiredArgsConstructor
class NotificationEventListener {

private final NotificationDispatcher notificationDispatcher;

@EventListener
public void on(EventApplied event) {
notificationDispatcher.dispatch(
NotificationType.EVENT_APPLIED, List.of(event.eventTitle()), event.eventId(), List.of(event.memberId()));
}

@EventListener
public void on(EventApplicationCanceled event) {
notificationDispatcher.dispatch(
NotificationType.EVENT_APPLICATION_CANCELED, List.of(event.eventTitle()), event.eventId(), List.of(event.memberId()));
}

@EventListener
public void on(LockerAssigned event) {
notificationDispatcher.dispatch(
NotificationType.LOCKER_ASSIGNED, List.of(), event.lockerPeriodId(), List.of(event.memberId()));
}

// 공지는 활성 회원 전체에게 간다
@EventListener
public void on(NoticeCreated event) {
notificationDispatcher.dispatchToAll(NotificationType.NOTICE_CREATED, List.of(), event.noticeId(), List.of());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.time.temporal.ChronoUnit;
import kr.ac.kookmin.stream.common.BusinessException;
import kr.ac.kookmin.stream.common.CursorSliceResult;
import kr.ac.kookmin.stream.common.OutboxWriter;
import kr.ac.kookmin.stream.common.event.NoticeCreated;
import kr.ac.kookmin.stream.welfare.domain.notice.domain.Notice;
import kr.ac.kookmin.stream.welfare.domain.notice.domain.NoticeCategory;
import kr.ac.kookmin.stream.welfare.domain.notice.domain.NoticeCreateCommand;
Expand All @@ -21,6 +23,7 @@
class NoticeServiceImpl implements NoticeService {

private final NoticeRepository noticeRepository;
private final OutboxWriter outboxWriter;

@Override
@Transactional(readOnly = true)
Expand All @@ -39,9 +42,13 @@ public Notice getNotice(Long id) {
.orElseThrow(() -> new BusinessException(NoticeErrorCode.NOTICE_NOT_FOUND));
}

// 공지 저장과 알림 이벤트 기록이 함께 커밋되도록 트랜잭션을 건다
@Override
@Transactional
public Notice create(Long createdBy, NoticeCreateCommand command) {
return noticeRepository.save(Notice.create(createdBy, command));
Notice notice = noticeRepository.save(Notice.create(createdBy, command));
outboxWriter.record(new NoticeCreated(notice.getId()));
return notice;
}

@Override
Expand Down
Loading