- 옵저버 패턴을 사용한 Event 기능 구현하기
✏️ 전체적인 순서
LikeablePersonService (공급자) : 특정 이벤트가 발생하면 이벤트를 호출함
|
Event (특정 이벤트) : 공급자에게 이벤트 data 를 주입받음
|
V
InstaMemberEventListener (구독자 구독 리스트) : 구독자를 호출해 주입받은 값을 전달
|
V
InstaMemberService (구독자) : 전달받은 data 로 비즈니스 로직을 수행
✏️ Publisher Service 계층
- 변화가 발생할 때 구독자에게 이벤트를 보내주는 역할
ApplicationEventPublisher
- 이벤트 공급자 기능을 수행하기 위한 Spring boot 의 라이브러리 객체
publishEvent
- 구독자에게 이벤트를 발생을 알리기 위한 메서드
- 구독자가 이벤트를 처리하기 위한 값을 변수로 넘겨준다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class LikeablePersonService {
private final LikeablePersonRepository likeablePersonRepository;
private final InstaMemberService instaMemberService;
private final ApplicationEventPublisher publisher;
@Transactional
public RsData<LikeablePerson> like(Member actor, String username, int attractiveTypeCode) {
...
publisher.publishEvent(new EventAfterLike(this, likeablePerson));
return RsData.of("S-1", "입력하신 인스타유저(%s)를 호감상대로 등록되었습니다.".formatted(username), likeablePerson);
}
✏️ Event 계층
ApplicationEvent
를 상속받아 원하는 이벤트 객체를 생성할 수 있다.
- 이벤트 처리에 필요한 필드를 생성 후 생성자 주입방식으로 값을 얻을 수 있게 만들어준다.
- 공급자에 특정 이벤트가 발생될경우 Event 객체의 생성자가 호출된다.
- 이벤트 처리에 필요한 필드값을 선언하고, 생성자 주입으로 값을 채워준다.
super(source)
- 관례적으로 선언해주기 때문에 별도의 역할은 없다.
package com.ll.gramgram.base.event;
import com.ll.gramgram.boundedContext.likeablePerson.entity.LikeablePerson;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class EventAfterLike extends ApplicationEvent {
private final LikeablePerson likeablePerson;
public EventAfterLike(Object source, LikeablePerson likeablePerson) {
super(source);
this.likeablePerson = likeablePerson;
}
}
- 이 외에도 필요하다고 생각되는 이벤트 객체를 원하는 만큼 생성할 수 있음
package com.ll.gramgram.base.event;
import com.ll.gramgram.boundedContext.likeablePerson.entity.LikeablePerson;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class EventAfterModifyAttractiveType extends ApplicationEvent {
private final LikeablePerson likeablePerson;
private final int oldAttractiveTypeCode;
public EventAfterModifyAttractiveType(Object source, LikeablePerson likeablePerson, int oldAttractiveTypeCode, int newAttractiveTypeCode) {
super(source);
this.likeablePerson = likeablePerson;
this.oldAttractiveTypeCode = oldAttractiveTypeCode;
}
}
✏️ EventListener 계층
- 공급자가 보낸 이벤트를 받는 핸들러
- 외부의 정보를 받는다는 점에 있어서 컨트롤러와 같은 개념이라고 할 수 있다.
- Controller - 클라이언트가 요청한 http 메시지를 받음
- EventListener - 공급자가 보낸 event 를 받음
- 구독자가 관심있어하는 Event 만 method 를 생성해주면 된다.
@EventListener
Listen
- 구독자가 구독하고 있는 event 를 매핑해주는 메서드
- 원하는 Event 를 변수값으로 받고 DI 받은 Sevice 객체로 비즈니스 로직을 수행한다.
package com.ll.gramgram.boundedContext.instaMember.eventListener;
import com.ll.gramgram.base.event.EventAfterFromInstaMemberChangeGender;
import com.ll.gramgram.base.event.EventAfterLike;
import com.ll.gramgram.base.event.EventAfterModifyAttractiveType;
import com.ll.gramgram.base.event.EventBeforeCancelLike;
import com.ll.gramgram.boundedContext.instaMember.service.InstaMemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
@RequiredArgsConstructor
public class InstaMemberEventListener {
private final InstaMemberService instaMemberService;
@EventListener
@Transactional
public void listen(EventAfterModifyAttractiveType event) {
instaMemberService.whenAfterModifyAttractiveType(event.getLikeablePerson(), event.getOldAttractiveTypeCode());
}
@EventListener
public void listen(EventAfterLike event) {
instaMemberService.whenAfterLike(event.getLikeablePerson());
}
@EventListener
public void listen(EventBeforeCancelLike event) {
instaMemberService.whenBeforeCancelLike(event.getLikeablePerson());
}
@EventListener
public void listen(EventAfterFromInstaMemberChangeGender event) {
instaMemberService.whenAfterFromInstaMemberChangeGender(event.getInstaMember(), event.getOldGender());
}
}
✏️ Subscriber Service 계층
InstaMemberEventListener
이 이벤트 로직을 수행할 수 있게 실질적인 비즈니스 로직을 수행한다.
- 지금 코드에서는 값을 처리하고 스냅샷을 생성해 값의 변화를 기록하고있다.
- 스냅샷의 설명은 아래쪽에 기록해뒀다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class InstaMemberService {
...
public void whenAfterLike(LikeablePerson likeablePerson) {
InstaMember fromInstaMember = likeablePerson.getFromInstaMember();
InstaMember toInstaMember = likeablePerson.getToInstaMember();
toInstaMember.increaseLikesCount(fromInstaMember.getGender(), likeablePerson.getAttractiveTypeCode());
InstaMemberSnapshot snapshot = toInstaMember.snapshot("Like");
saveSnapshot(snapshot);
}
✏️ 번외 - 스냅샷
- 특정 Entity 의 값변화를 모두 기록하는 객체
📍 Entity 계층
- 원본 객체를 기준으로 1 : N 관계에 있는 entity 객체이다.
- 변경사항을 기록하고 싶은 필드를 생성해주면 된다.
- 실질적으로 변화되는 값을 반영하는 로직은
구독자 Entity
쪽에 businis method
로 생성하면 된다.
- 예시코드에서는 공통되는 값을 Base Entity 로 만들어 리팩토링했다.
- 🔗 Base Entity 사용법
package com.ll.gramgram.boundedContext.instaMember.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
@Entity
@Getter
@NoArgsConstructor
@SuperBuilder
@ToString(callSuper = true)
public class InstaMemberSnapshot extends InstaMemberBase {
private String eventTypeCode;
private String username;
@ToString.Exclude
@ManyToOne
private InstaMember instaMember;
}
📍 구독자 Service
- ⚠️ enttity 를 사용하기 위해서 Repository 가 필요하다.
- 이밴트를 처리하는 로직에서 호출할 수있도록 스넵샷을 저장하기 위한 메서드를 생성해준다.
private void saveSnapshot(InstaMemberSnapshot snapshot) {
instaMemberSnapshotRepository.save(snapshot);
}