• 옵저버 패턴을 사용한 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;

		// 이벤트 공급자 객체 DI
    private final ApplicationEventPublisher publisher;

		@Transactional
		public RsData<LikeablePerson> like(Member actor, String username, int attractiveTypeCode) {
		
		    ...
		
				// 이벤트 발송
				// this 는 역할이 없지만 관례적으로 값을 넣어준다.
		    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 {

		// 이벤트 처리를 위한 객체 DI
    private final InstaMemberService instaMemberService;

		// 다양한 Event 객체 중에서 
		// InstaMember 객체가 관심있는 event 만 구현해주면 된다.

    @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);
}
profile
잘못된 내용 PR 환영

0개의 댓글