Skip to content
Merged
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
Expand Up @@ -23,6 +23,7 @@ public CreateRecruitBoardEvent from(String message) {

return switch (DomainEventSubType.from(eventType)) {
case CREATE_RECRUIT_BOARD -> parseCreateRecruitBoardEvent(message);
case VOLUNTEER_HOURS_SETTLE -> null;
Copy link
Collaborator

Choose a reason for hiding this comment

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

조치해주셔서 감사합니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

여기 null인 이유는 추후 구현인건가요?
로직을 몰라서 여쭤봅니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

네 구조 자체가 조금 잘못돼서 추후에 수정해야하는 부분입니다.

};
} catch (Exception e) {
log.error(e.getMessage());
Expand All @@ -34,4 +35,5 @@ private CreateRecruitBoardEvent parseCreateRecruitBoardEvent(String message) thr

return objectMapper.readValue(message, CreateRecruitBoardEvent.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.somemore.domains.volunteerapply.event.VolunteerReviewRequestEvent;
import com.somemore.domains.volunteerapply.usecase.SettleVolunteerApplyFacadeUseCase;
import com.somemore.domains.volunteerapply.usecase.VolunteerApplyQueryUseCase;
import com.somemore.domains.volunteerrecord.event.VolunteerRecordEventPublisher;
import com.somemore.global.common.event.ServerEventPublisher;
import com.somemore.global.common.event.ServerEventType;
import com.somemore.global.exception.BadRequestException;
Expand All @@ -30,6 +31,7 @@ public class SettleVolunteerApplyFacadeService implements SettleVolunteerApplyFa
private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
private final UpdateVolunteerUseCase updateVolunteerUseCase;
private final ServerEventPublisher serverEventPublisher;
private final VolunteerRecordEventPublisher volunteerRecordEventPublisher;

@Override
public void settleVolunteerApplies(VolunteerApplySettleRequestDto dto, UUID centerId) {
Expand All @@ -47,6 +49,7 @@ public void settleVolunteerApplies(VolunteerApplySettleRequestDto dto, UUID cent
apply.changeAttended(true);
updateVolunteerUseCase.updateVolunteerStats(apply.getVolunteerId(), hours);
publishVolunteerReviewRequestEvent(apply, recruitBoard);
volunteerRecordEventPublisher.publishVolunteerRecordCreateEvent(apply, recruitBoard);
Copy link
Collaborator

Choose a reason for hiding this comment

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

serverEventPublisher를 활용해서 추상화하는 것이 좋을 것 같습니다.

혹시 봉사 기록 이벤트만을 위한 퍼블리셔를 만들고 의존하신 특별한 이유가 있나요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

혹은 봉사 기록 이벤트 내부에서 정적 팩토리 메서드를 활용할 수 있을 것 같습니다.

Copy link
Collaborator Author

@7zrv 7zrv Jan 10, 2025

Choose a reason for hiding this comment

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

처음에 이벤트에 대한 개념이 확실히 잡히지 않아서
봉사 시간 정산시 봉사 기록 생성 이벤트를 발행한다 라는 의미로 생각하고 메서드를 만들었습니다

현재는 봉사 시간 정산 이벤트가 발생하고 이를 구독하는 클래스에서 자신들이 필요한 로직을 수행하도록
이벤트 발행과 구독을 완전히 분리하는 방향을 생각하고 있는데요

봉사 시간 정산 이벤트가 발생하면 RedisSettleVolunteerHoursSubscriber 에서 이를 구독하여
그 안에서 메세지를 핸들링 하는것을 구상중인데 혹시 이 부분에 대해서는 어떻게 생각하시나요

Copy link
Collaborator

Choose a reason for hiding this comment

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

publishVolunteerReviewRequestEvent(apply, recruitBoard);
volunteerRecordEventPublisher.publishVolunteerRecordCreateEvent(apply, recruitBoard);

이거 방식이 달라서 상의해서 한가지로 통합하는게 좋아보입니다.

Copy link
Collaborator

@m-a-king m-a-king Jan 11, 2025

Choose a reason for hiding this comment

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

@7zrv 좋습니다. 봉사 시간 정산 이벤트가 발생하면 그것을 봉사 시간 정산 이벤트 구독자가 받고 그제야 봉사 시간 정산 이벤트로부터 발생해야 하는 리뷰 요청 알림 이벤트와 봉사 기록 생성 이벤트로 나누어져서 두 번의 이벤트를 발생시키면 좋을 것 같습니다.

두 번의 이벤트가 발생하는 시점에서 범수 님이 말해주신 것 중에서 골라야 하는데, 제 생각에는 이벤트를 발생시킨다는 개념은 serverEventPublisher에게 맡기고 이벤트는 이벤트 클래스의 정적 메서드를 활용해서 생성하는 게 좋을 것 같다고 생각했습니다.
@leebs0521

-> publishVolunteerReviewRequestEvent(apply, recruitBoard) 메서드를 사용하자. 하지만 빌더 패턴을 드러내지 말자 (이벤트 클래스의 정적 팩토리 메서드를 활용하자)
-> 별도의 클래스(volunteerRecordEventPublisher)에서 이벤트를 생성하고 그것을 의존하는 현재 구조는 과하지 않을까?

라고 생각했습니다!

});

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.somemore.domains.volunteerrecord.domain;

import com.somemore.global.common.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.util.UUID;

import static jakarta.persistence.GenerationType.IDENTITY;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "volunteer_record")
public class VolunteerRecord extends BaseEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;

@Column(name = "volunteer_id", nullable = false)
private UUID volunteerId;

@Column(name = "title", nullable = false)
private String title;

@Column(name = "volunteer_date", nullable = false)
private LocalDate volunteerDate;

@Column(name = "volunteer_hours", nullable = false)
private int volunteerHours;

@Builder
private VolunteerRecord(UUID volunteerId, String title, LocalDate volunteerDate, int volunteerHours) {
this.volunteerId = volunteerId;
this.title = title;
this.volunteerDate = volunteerDate;
this.volunteerHours = volunteerHours;
}

public static VolunteerRecord create(UUID volunteerId, String title, LocalDate volunteerDate, int volunteerHours) {
return VolunteerRecord.builder()
.volunteerId(volunteerId)
.title(title)
.volunteerDate(volunteerDate)
.volunteerHours(volunteerHours)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.somemore.domains.volunteerrecord.event;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.somemore.global.common.event.DomainEventSubType;
import com.somemore.global.common.event.ServerEvent;
import com.somemore.global.common.event.ServerEventType;
import lombok.Getter;
import lombok.experimental.SuperBuilder;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;

@Getter
@SuperBuilder
public class VolunteerRecordCreateEvent extends ServerEvent<DomainEventSubType> {

private final UUID volunteerId;
private final String title;
private final LocalDate volunteerDate;
private final int volunteerHours;

@JsonCreator
private VolunteerRecordCreateEvent(
@JsonProperty(value = "volunteerId", required = true) UUID volunteerId,
@JsonProperty(value = "title", required = true) String title,
@JsonProperty(value = "volunteerDate", required = true) LocalDate volunteerDate,
@JsonProperty(value = "volunteerHours", required = true) int volunteerHours) {

super(ServerEventType.DOMAIN_EVENT, DomainEventSubType.VOLUNTEER_HOURS_SETTLE, LocalDateTime.now());

this.volunteerId = volunteerId;
this.title = title;
this.volunteerDate = volunteerDate;
this.volunteerHours = volunteerHours;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.somemore.domains.volunteerrecord.event;

import com.somemore.domains.recruitboard.domain.RecruitBoard;
import com.somemore.domains.volunteerapply.domain.VolunteerApply;
import com.somemore.global.common.event.DomainEventSubType;
import com.somemore.global.common.event.ServerEventPublisher;
import com.somemore.global.common.event.ServerEventType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class VolunteerRecordEventPublisher {

private final ServerEventPublisher serverEventPublisher;

public void publishVolunteerRecordCreateEvent(VolunteerApply apply, RecruitBoard recruitBoard) {

VolunteerRecordCreateEvent event = VolunteerRecordCreateEvent
.builder()
.type(ServerEventType.DOMAIN_EVENT)
.subType(DomainEventSubType.VOLUNTEER_HOURS_SETTLE)
.volunteerId(apply.getVolunteerId())
.title(recruitBoard.getTitle())
.volunteerDate(recruitBoard.getRecruitmentInfo().getVolunteerEndDateTime().toLocalDate())
.volunteerHours(recruitBoard.getRecruitmentInfo().getVolunteerHours())
.build();

serverEventPublisher.publish(event);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.somemore.domains.volunteerrecord.event.convert;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import com.somemore.domains.volunteerrecord.event.VolunteerRecordCreateEvent;
import com.somemore.global.common.event.DomainEventSubType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@RequiredArgsConstructor
@Component
public class VolunteerRecordMessageConverter {

private static final String SUB_TYPE = "subType";
Copy link
Collaborator

Choose a reason for hiding this comment

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

좋네요~

private final ObjectMapper objectMapper;

public VolunteerRecord from(String message) {
try {
JsonNode rootNode = objectMapper.readTree(message);
String eventType = rootNode.get(SUB_TYPE).asText();

return switch (DomainEventSubType.from(eventType)) {
case VOLUNTEER_HOURS_SETTLE -> convertToVolunteerRecord(message);
default -> {
log.error("지원하지 않는 이벤트 타입입니다: {}", eventType);
throw new IllegalArgumentException("지원하지 않는 이벤트 타입입니다: " + eventType);
}
};
} catch (Exception e) {
log.error("메시지 변환 실패: {}", e.getMessage());
throw new IllegalStateException("메시지 변환 중 오류가 발생했습니다.", e);
}
}

private VolunteerRecord convertToVolunteerRecord(String message) throws JsonProcessingException {

VolunteerRecordCreateEvent volunteerRecordCreateEvent = objectMapper.readValue(message, VolunteerRecordCreateEvent.class);

return VolunteerRecord.create(
volunteerRecordCreateEvent.getVolunteerId(),
volunteerRecordCreateEvent.getTitle(),
volunteerRecordCreateEvent.getVolunteerDate(),
volunteerRecordCreateEvent.getVolunteerHours()
);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.somemore.domains.volunteerrecord.event.handler;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;

public interface SettleVolunteerHoursHandler {

void handle(VolunteerRecord volunteerRecord);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.somemore.domains.volunteerrecord.event.handler;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import com.somemore.domains.volunteerrecord.usecase.VolunteerRecordCreateUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
@Transactional
public class SettleVolunteerHoursHandlerImpl implements SettleVolunteerHoursHandler {

private final VolunteerRecordCreateUseCase volunteerRecordCreateUseCase;

@Override
public void handle(VolunteerRecord volunteerRecord) {

volunteerRecordCreateUseCase.create(volunteerRecord);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.somemore.domains.volunteerrecord.event.subscriber;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import com.somemore.domains.volunteerrecord.event.convert.VolunteerRecordMessageConverter;
import com.somemore.domains.volunteerrecord.event.handler.SettleVolunteerHoursHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class RedisSettleVolunteerHoursSubscriber implements MessageListener {

private final SettleVolunteerHoursHandler settleVolunteerHoursHandler;
private final VolunteerRecordMessageConverter messageConverter;

@Override
public void onMessage(Message message, byte[] pattern) {

VolunteerRecord volunteerRecord = messageConverter.from(
new String(message.getBody())
);

settleVolunteerHoursHandler.handle(volunteerRecord);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.somemore.domains.volunteerrecord.repository;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import org.springframework.data.jpa.repository.JpaRepository;

public interface VolunteerRecordJpaRepository extends JpaRepository<VolunteerRecord, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.somemore.domains.volunteerrecord.repository;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;

public interface VolunteerRecordRepository {
void save(VolunteerRecord volunteerRecord);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.somemore.domains.volunteerrecord.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@RequiredArgsConstructor
@Repository
public class VolunteerRecordRepositoryImpl implements VolunteerRecordRepository {

private final JPAQueryFactory queryFactory;
private final VolunteerRecordJpaRepository volunteerRecordJpaRepository;

@Override
public void save(VolunteerRecord volunteerRecord) {
volunteerRecordJpaRepository.save(volunteerRecord);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.somemore.domains.volunteerrecord.service;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;
import com.somemore.domains.volunteerrecord.repository.VolunteerRecordRepository;
import com.somemore.domains.volunteerrecord.usecase.VolunteerRecordCreateUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
@Transactional
public class VolunteerRecordCreateService implements VolunteerRecordCreateUseCase {

Copy link
Collaborator

Choose a reason for hiding this comment

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

다른 서비스 클래스들은 CreateDomainService 이런식으로 지었는데 여기만 DomainCreateService로 지은 이유가 있으신가요?

private final VolunteerRecordRepository volunteerRecordRepository;

public void create(VolunteerRecord volunteerRecord) {
volunteerRecordRepository.save(volunteerRecord);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.somemore.domains.volunteerrecord.usecase;

import com.somemore.domains.volunteerrecord.domain.VolunteerRecord;

public interface VolunteerRecordCreateUseCase {
void create(VolunteerRecord volunteerRecord);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@RequiredArgsConstructor
public enum DomainEventSubType {
CREATE_RECRUIT_BOARD("모집 글 등록"),
VOLUNTEER_HOURS_SETTLE("봉사 시간 정산")
;

private final String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.somemore.domains.interestcenter.event.subscriber.RedisCreateRecruitBoardSubscriber;
import com.somemore.domains.notification.event.subscriber.RedisNotificationSubscriber;
import com.somemore.domains.volunteerrecord.event.subscriber.RedisSettleVolunteerHoursSubscriber;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -17,6 +18,7 @@ public class RedisListenerRegistrar {
private final RedisMessageListenerContainer container;
private final RedisNotificationSubscriber redisNotificationSubscriber;
private final RedisCreateRecruitBoardSubscriber redisCreateRecruitBoardSubscriber;
private final RedisSettleVolunteerHoursSubscriber redisSettleVolunteerHoursSubscriber;
private final ChannelTopic notificationTopic;
private final ChannelTopic domainEventTopic;

Expand All @@ -25,9 +27,11 @@ public void registerListeners() {
registerNotificationListener();
}

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