Skip to content

Commit f3efb1a

Browse files
committed
feat: 봉사 기록 등록 이벤트 구현
- 도메인 이벤트 타입에 봉사 시간 정산 이벤트 등록 - 발행될 봉사 시간 정산 이벤트 클래스 생성 - 봉사 시간 정산 이벤트 퍼블리셔 생성 - 구독을 위한 봉사 시간 정산 이벤트 RedisSubscriber 생성 - RedisListenerRegistrar애 구독 클래스 추가 - 봉사 시간 정산 이벤트 메세지 컨버터 생성 - 봉사 기록 생성 이벤트 핸들러 인터페이스 생성 - 봉사 기록 생성 이벤트 핸들러 구현체 생성 - 봉사 시간 정산 메서드에 봉사 기록 생성 이벤트 의존성 주입 - 테스트 코드 작성및 검증 완료
1 parent 59ca9d6 commit f3efb1a

File tree

10 files changed

+203
-0
lines changed

10 files changed

+203
-0
lines changed

src/main/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.somemore.domains.volunteerapply.event.VolunteerReviewRequestEvent;
1010
import com.somemore.domains.volunteerapply.usecase.SettleVolunteerApplyFacadeUseCase;
1111
import com.somemore.domains.volunteerapply.usecase.VolunteerApplyQueryUseCase;
12+
import com.somemore.domains.volunteerrecord.event.VolunteerRecordEventPublisher;
1213
import com.somemore.global.common.event.ServerEventPublisher;
1314
import com.somemore.global.common.event.ServerEventType;
1415
import com.somemore.global.exception.BadRequestException;
@@ -30,6 +31,7 @@ public class SettleVolunteerApplyFacadeService implements SettleVolunteerApplyFa
3031
private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
3132
private final UpdateVolunteerUseCase updateVolunteerUseCase;
3233
private final ServerEventPublisher serverEventPublisher;
34+
private final VolunteerRecordEventPublisher volunteerRecordEventPublisher;
3335

3436
@Override
3537
public void settleVolunteerApplies(VolunteerApplySettleRequestDto dto, UUID centerId) {
@@ -47,6 +49,7 @@ public void settleVolunteerApplies(VolunteerApplySettleRequestDto dto, UUID cent
4749
apply.changeAttended(true);
4850
updateVolunteerUseCase.updateVolunteerStats(apply.getVolunteerId(), hours);
4951
publishVolunteerReviewRequestEvent(apply, recruitBoard);
52+
volunteerRecordEventPublisher.publishVolunteerRecordCreateEvent(apply, recruitBoard);
5053
});
5154

5255
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.somemore.domains.volunteerrecord.event;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.somemore.global.common.event.DomainEventSubType;
6+
import com.somemore.global.common.event.ServerEvent;
7+
import com.somemore.global.common.event.ServerEventType;
8+
import lombok.Getter;
9+
import lombok.experimental.SuperBuilder;
10+
11+
import java.time.LocalDate;
12+
import java.time.LocalDateTime;
13+
import java.util.UUID;
14+
15+
@Getter
16+
@SuperBuilder
17+
public class VolunteerRecordCreateEvent extends ServerEvent<DomainEventSubType> {
18+
19+
private final UUID volunteerId;
20+
private final String title;
21+
private final LocalDate volunteerDate;
22+
private final int volunteerHours;
23+
24+
@JsonCreator
25+
private VolunteerRecordCreateEvent(
26+
@JsonProperty(value = "volunteerId", required = true) UUID volunteerId,
27+
@JsonProperty(value = "title", required = true) String title,
28+
@JsonProperty(value = "volunteerDate", required = true) LocalDate volunteerDate,
29+
@JsonProperty(value = "volunteerHours", required = true) int volunteerHours) {
30+
31+
super(ServerEventType.DOMAIN_EVENT, DomainEventSubType.VOLUNTEER_HOURS_SETTLE, LocalDateTime.now());
32+
33+
this.volunteerId = volunteerId;
34+
this.title = title;
35+
this.volunteerDate = volunteerDate;
36+
this.volunteerHours = volunteerHours;
37+
}
38+
39+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.somemore.domains.volunteerrecord.event;
2+
3+
import com.somemore.domains.recruitboard.domain.RecruitBoard;
4+
import com.somemore.domains.volunteerapply.domain.VolunteerApply;
5+
import com.somemore.global.common.event.DomainEventSubType;
6+
import com.somemore.global.common.event.ServerEventPublisher;
7+
import com.somemore.global.common.event.ServerEventType;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Component;
10+
11+
@RequiredArgsConstructor
12+
@Component
13+
public class VolunteerRecordEventPublisher {
14+
15+
private final ServerEventPublisher serverEventPublisher;
16+
17+
public void publishVolunteerRecordCreateEvent(VolunteerApply apply, RecruitBoard recruitBoard) {
18+
19+
VolunteerRecordCreateEvent event = VolunteerRecordCreateEvent
20+
.builder()
21+
.type(ServerEventType.DOMAIN_EVENT)
22+
.subType(DomainEventSubType.VOLUNTEER_HOURS_SETTLE)
23+
.volunteerId(apply.getVolunteerId())
24+
.title(recruitBoard.getTitle())
25+
.volunteerDate(recruitBoard.getRecruitmentInfo().getVolunteerEndDateTime().toLocalDate())
26+
.volunteerHours(recruitBoard.getRecruitmentInfo().getVolunteerHours())
27+
.build();
28+
29+
serverEventPublisher.publish(event);
30+
}
31+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.somemore.domains.volunteerrecord.event.convert;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
7+
import com.somemore.domains.volunteerrecord.event.VolunteerRecordCreateEvent;
8+
import com.somemore.global.common.event.DomainEventSubType;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.springframework.stereotype.Component;
12+
13+
@Slf4j
14+
@RequiredArgsConstructor
15+
@Component
16+
public class VolunteerRecordMessageConverter {
17+
18+
private static final String SUB_TYPE = "subType";
19+
private final ObjectMapper objectMapper;
20+
21+
public VolunteerRecord from(String message) {
22+
try {
23+
JsonNode rootNode = objectMapper.readTree(message);
24+
String eventType = rootNode.get(SUB_TYPE).asText();
25+
26+
return switch (DomainEventSubType.from(eventType)) {
27+
case VOLUNTEER_HOURS_SETTLE -> buildVolunteerRecordCreate(message);
28+
default -> {
29+
log.error("지원하지 않는 이벤트 타입입니다: {}", eventType);
30+
throw new IllegalArgumentException("지원하지 않는 이벤트 타입입니다: " + eventType);
31+
}
32+
};
33+
} catch (Exception e) {
34+
log.error("메시지 변환 실패: {}", e.getMessage());
35+
throw new IllegalStateException("메시지 변환 중 오류가 발생했습니다.", e);
36+
}
37+
}
38+
39+
private VolunteerRecord buildVolunteerRecordCreate(String message) throws JsonProcessingException {
40+
41+
VolunteerRecordCreateEvent volunteerRecordCreateEvent = objectMapper.readValue(message, VolunteerRecordCreateEvent.class);
42+
43+
return VolunteerRecord.create(
44+
volunteerRecordCreateEvent.getVolunteerId(),
45+
volunteerRecordCreateEvent.getTitle(),
46+
volunteerRecordCreateEvent.getVolunteerDate(),
47+
volunteerRecordCreateEvent.getVolunteerHours()
48+
);
49+
50+
}
51+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.somemore.domains.volunteerrecord.event.handler;
2+
3+
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
4+
5+
public interface SettleVolunteerHoursHandler {
6+
7+
void handle(VolunteerRecord volunteerRecord);
8+
9+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.somemore.domains.volunteerrecord.event.handler;
2+
3+
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
4+
import com.somemore.domains.volunteerrecord.usecase.VolunteerRecordCreateUseCase;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.transaction.annotation.Transactional;
8+
9+
@Component
10+
@RequiredArgsConstructor
11+
@Transactional
12+
public class SettleVolunteerHoursHandlerImpl implements SettleVolunteerHoursHandler {
13+
14+
private final VolunteerRecordCreateUseCase volunteerRecordCreateUseCase;
15+
16+
@Override
17+
public void handle(VolunteerRecord volunteerRecord) {
18+
19+
volunteerRecordCreateUseCase.create(volunteerRecord);
20+
}
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.somemore.domains.volunteerrecord.event.subscriber;
2+
3+
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
4+
import com.somemore.domains.volunteerrecord.event.convert.VolunteerRecordMessageConverter;
5+
import com.somemore.domains.volunteerrecord.event.handler.SettleVolunteerHoursHandler;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.data.redis.connection.Message;
8+
import org.springframework.data.redis.connection.MessageListener;
9+
import org.springframework.stereotype.Component;
10+
11+
@Component
12+
@RequiredArgsConstructor
13+
public class RedisSettleVolunteerHoursSubscriber implements MessageListener {
14+
15+
private final SettleVolunteerHoursHandler settleVolunteerHoursHandler;
16+
private final VolunteerRecordMessageConverter messageConverter;
17+
18+
@Override
19+
public void onMessage(Message message, byte[] pattern) {
20+
21+
VolunteerRecord volunteerRecord = messageConverter.from(
22+
new String(message.getBody())
23+
);
24+
25+
settleVolunteerHoursHandler.handle(volunteerRecord);
26+
}
27+
}

src/main/java/com/somemore/global/common/event/DomainEventSubType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@RequiredArgsConstructor
88
public enum DomainEventSubType {
99
CREATE_RECRUIT_BOARD("모집 글 등록"),
10+
VOLUNTEER_HOURS_SETTLE("봉사 시간 정산")
1011
;
1112

1213
private final String description;

src/main/java/com/somemore/global/redis/registrar/RedisListenerRegistrar.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.somemore.domains.interestcenter.event.subscriber.RedisCreateRecruitBoardSubscriber;
44
import com.somemore.domains.notification.event.subscriber.RedisNotificationSubscriber;
5+
import com.somemore.domains.volunteerrecord.event.subscriber.RedisSettleVolunteerHoursSubscriber;
56
import jakarta.annotation.PostConstruct;
67
import lombok.RequiredArgsConstructor;
78
import lombok.extern.slf4j.Slf4j;
@@ -17,6 +18,7 @@ public class RedisListenerRegistrar {
1718
private final RedisMessageListenerContainer container;
1819
private final RedisNotificationSubscriber redisNotificationSubscriber;
1920
private final RedisCreateRecruitBoardSubscriber redisCreateRecruitBoardSubscriber;
21+
private final RedisSettleVolunteerHoursSubscriber redisSettleVolunteerHoursSubscriber;
2022
private final ChannelTopic notificationTopic;
2123
private final ChannelTopic domainEventTopic;
2224

@@ -25,9 +27,11 @@ public void registerListeners() {
2527
registerNotificationListener();
2628
}
2729

30+
// 알림 리스너와 분리
2831
private void registerNotificationListener() {
2932
container.addMessageListener(redisNotificationSubscriber, notificationTopic);
3033
container.addMessageListener(redisCreateRecruitBoardSubscriber, domainEventTopic);
34+
container.addMessageListener(redisSettleVolunteerHoursSubscriber, domainEventTopic);
3135
log.info("리스너가 토픽에 성공적으로 등록되었습니다.");
3236
}
3337
}

src/test/java/com/somemore/domains/volunteerapply/service/SettleVolunteerApplyFacadeServiceTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.somemore.domains.volunteerapply.domain.VolunteerApply;
88
import com.somemore.domains.volunteerapply.dto.request.VolunteerApplySettleRequestDto;
99
import com.somemore.domains.volunteerapply.repository.VolunteerApplyRepository;
10+
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
11+
import com.somemore.domains.volunteerrecord.repository.VolunteerRecordJpaRepository;
1012
import com.somemore.global.exception.BadRequestException;
1113
import com.somemore.support.IntegrationTestSupport;
1214
import org.junit.jupiter.api.DisplayName;
@@ -40,6 +42,9 @@ class SettleVolunteerApplyFacadeServiceTest extends IntegrationTestSupport {
4042
@Autowired
4143
private VolunteerRepository volunteerRepository;
4244

45+
@Autowired
46+
private VolunteerRecordJpaRepository volunteerRecordJpaRepository;
47+
4348
@DisplayName("봉사 활동 지원을 정산할 수 있다.")
4449
@Test
4550
void settleVolunteerApplies() {
@@ -86,6 +91,18 @@ void settleVolunteerApplies() {
8691
assertThat(findVolunteer1.getTotalVolunteerCount()).isEqualTo(1);
8792
assertThat(findVolunteer2.getTotalVolunteerCount()).isEqualTo(1);
8893
assertThat(findVolunteer3.getTotalVolunteerCount()).isEqualTo(1);
94+
95+
// 봉사 기록 생성 확인
96+
List<VolunteerRecord> records = volunteerRecordJpaRepository.findAll();
97+
assertThat(records).hasSize(3);
98+
99+
assertThat(records)
100+
.extracting(VolunteerRecord::getVolunteerId)
101+
.containsExactlyInAnyOrder(volunteer1.getId(), volunteer2.getId(), volunteer3.getId());
102+
103+
assertThat(records)
104+
.extracting(VolunteerRecord::getVolunteerHours)
105+
.containsOnly(hour);
89106
}
90107

91108
@DisplayName("정산시, 지원 리스트에 존재하지 않는 지원이 있는 경우 에러가 발생한다.")

0 commit comments

Comments
 (0)