πŸ’Š Java & Kotlin & Spring/- spring framework +

[Spring] ApplicationEventPublisher λ₯Ό μ΄μš©ν•΄μ„œ Event λ₯Ό λ°œν–‰ν•˜λ©΄ μ–΄λ–€ 일이 μΌμ–΄λ‚ κΉŒ

Wonit 2022. 8. 15. 16:43

ν•΄λ‹Ή κΈ€μ˜ 전체 μ†ŒμŠ€μ½”λ“œλŠ” github μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. README λ₯Ό μ°Έμ‘°ν•˜μ„Έμš”

 

λͺ©μ°¨

  • 동기
  • There is No Magic in spring

 

동기

 

μ΅œκ·Όμ— DDD λ₯Ό ν•™μŠ΅ν•˜λ©΄μ„œ 도메인 μ΄λ²€νŠΈλΌλŠ” 것을 μ•Œκ²Œ λ˜μ—ˆλ‹€.

 

κ·ΈλŸ¬λ©΄μ„œ Spring μ—μ„œ ν•œ VM μ•ˆμ—μ„œ 이벀트λ₯Ό λ°œν–‰ν•˜κ³  ν•΄λ‹Ή 이벀트λ₯Ό handling ν•˜λŠ” μ½”λ“œλ₯Ό μ ‘ν–ˆκ³ , 그와 κ΄€λ ¨ν•΄μ„œ μ—¬λŸ¬ μ‹€μŠ΅μ„ ν•΄λ³΄λ˜ 쀑 Spring μ—μ„œ 무언가 Magic 이 μΌμ–΄λ‚˜κ³  μžˆλŠ”κ²ƒ 같은 λŠλ‚Œμ„ λ°›μ•˜λ‹€.

 

κΈ΄ 말 ν•„μš” 없이 μ•„λž˜μ˜ μ½”λ“œλ₯Ό ν™•μΈν•΄λ³΄μž.

 

@Entity(name = "orders")
public class Order {

    public static Order create() {
        return new Order();
    }

    @Id
    private Long id;
    private OrderStatus status = CREATED;

    public void cancel() {
        OrderCanceled event = OrderCanceled.of(id);
        Events.raise(event); // event trigger
        status = CANCELED;
    }
}

 

μœ„μ˜ μ½”λ“œλŠ” Order λΌλŠ” μ‹€μŠ΅μ„ μœ„ν•΄ νƒ„μƒν•œ κ°€μƒμ˜ 도메인이닀.

 

μ‹€μŠ΅μ˜ μ‹œλ‚˜λ¦¬μ˜€μ—μ„œλŠ” μ•„μ£Ό κ°„λ‹¨ν•œ λ‘œμ§λ§Œμ„ 가지고 μžˆλ‹€.

 

 

주문이 μ·¨μ†Œλ˜λ©΄ Membership 의 μƒνƒœλ₯Ό λ³€κ²½ν•˜λŠ” 것이 λ‹€λ‹€.

 

ν•΄λ‹Ή Order λΌλŠ” κ°€μƒμ˜ 도메인 μ½”λ“œμ—μ„œ 집쀑해야 ν•  뢀뢄은 cancel() μ΄λΌλŠ” λ©”μ„œλ“œκ°€ ν•˜λŠ” 일이닀.

 

Events λΌλŠ” μœ ν‹Έλ¦¬ν‹°μ„± 클래슀의 static λ©”μ„œλ“œμΈ raise() λ₯Ό ν˜ΈμΆœν•΄μ„œ 이벀트λ₯Ό λ°œν–‰ν•œλ‹€.

 

Events 클래슀λ₯Ό ν™•μΈν•΄λ³΄μž

 

public class Events {
    private static ApplicationEventPublisher publisher;

    protected static void setPublisher(ApplicationEventPublisher publisher) {
        Events.publisher = publisher; // initializing
    }

    public static void raise(DomainEvent event) {
        if (publisher == null) {
            throw new IllegalStateException();
        }
        publisher.publishEvent(event);
    }
}

 

Events ν΄λž˜μŠ€λŠ” Spring 의 EventDispatcher 인 ApplicationEventPublisher λ₯Ό 감싸고 μžˆλŠ” μΌμ’…μ˜ Facade 역할을 μˆ˜ν–‰ν•œλ‹€.

 

ν•΄λ‹Ή μ½”λ“œμ—μ„œλŠ” raise() λ₯Ό ν˜ΈμΆœν•˜κ²Œ λœλ‹€λ©΄ λ©€λ²„λ‘œ 가지고 μžˆλŠ” ApplicationEventPublisher 와 ν˜‘λ ₯ν•˜μ—¬ event λ₯Ό publish ν•œλ‹€.

 

ApplicationEventPublisher λŠ” μŠ€ν”„λ§μ—μ„œ μ œκ³΅ν•˜λŠ” event publisher 인데, Events λΌλŠ” ν΄λž˜μŠ€μ—μ„œλŠ” μ‚¬μš©μ„±μ„ 높이기 μœ„ν•΄μ„œ static 으둜 EventPublisher λ₯Ό μ™ΈλΆ€μ—μ„œ μ£Όμž…λ°›λŠ”λ‹€.

 

@Configuration
@RequiredArgsConstructor
public class EventsConfig {
    private final ApplicationContext applicationContext;

    @Bean
    public InitializingBean eventsInitializer() {
        return () -> Events.setPublisher(applicationContext);
    }
}

 

Events λ₯Ό μ΄ˆκΈ°ν™” ν•˜κΈ° μœ„ν•΄μ„œ InitializingBean 을 μ‚¬μš©ν–ˆλ‹€.

 

자.

 

이런 μ €λŸ° 과정듀을 ν•΄μ£Όκ³  ν•΄λ‹Ή 이벀트λ₯Ό λ°›κ³  μžˆλŠ” event handler 둜 κ°€λ³΄μž

 

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderCanceledEventHandler {
    private final MembershipTerminateService membershipTerminateService;

    @EventListener(OrderCanceled.class)
    public void handle(OrderCanceled event) {
        log.info("OrderCanceledEvent occurred !! => {}", event);
        membershipTerminateService.terminateBy(event.getOrderId());
    }
}

@EventListener μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•˜λ©΄ ν•΄λ‹Ή μ΄λ²€νŠΈκ°€ λ°œν–‰λ  λ•Œ Listening ν•˜μ—¬ νŠΉμ • λ‘œμ§μ„ μˆ˜ν–‰ν•  수 μžˆλ„λ‘ ν•œλ‹€.

μ–΄λ– ν•œ λ§ˆλ²•μ΄ μˆ¨μ–΄μžˆλŠ” κ²ƒμΌκΉŒ?

 

There is No Magic in spring

 

늘 κ·Έλ ‡λ“― There is No Magic in spring 이닀.

 

λ‚΄κ°€ κΆκΈˆν•œ 것은 μš°μ„ 

 

  1. μ™œ Config μ—μ„œ ApplicationContext λ₯Ό Events μ—κ²Œ set ν•˜λŠ”κ°€?
  2. @EventListener λŠ” μ–΄λ–»κ²Œ consuming ν•˜λŠ”κ°€

 

인데, ν•˜λ‚˜μ”© ν™•μΈν•΄λ³΄μž.

 

μ™œ Config μ—μ„œ ApplicationContext λ₯Ό Events μ—κ²Œ set ν•˜λŠ”κ°€?

 

EventPublisher λ₯Ό λ™μ μœΌλ‘œ set ν•΄μ£ΌλŠ” Config μ½”λ“œμ—μ„œ ApplicationContext λ₯Ό λ„˜κ²¨μ„œ Events ν΄λž˜μŠ€μ—κ²Œ ApplicationEventPublisher λ₯Ό μ„€μ •ν•΄μ€€λ‹€.

 

@Configuration
@RequiredArgsConstructor
public class EventsConfig {
    private final ApplicationContext applicationContext;

    @Bean
    public InitializingBean eventsInitializer() {
        return () -> Events.setPublisher(applicationContext);
    }
}

 

Events κ°€ μ›ν•˜λŠ” λ©€λ²„λŠ” ApplicationEventPublisher 인데 μ™œ ApplicationContext λ₯Ό λ„˜κΈΈκΉŒ?

 

public class Events {
    private static ApplicationEventPublisher publisher;

    protected static void setPublisher(ApplicationEventPublisher publisher) {
        Events.publisher = publisher;
    }

    public static void raise(DomainEvent event) {
        //...
    }
}

 

μš°λ¦¬κ°€ μ•Œκ³  μžˆλŠ” ApplicationContext λŠ” IoC Container λ‘œμ„œ Bean Factory 역할을 μˆ˜ν–‰ν•œλ‹€.


그런 ApplicationContext μ—λŠ” λ‹€μ–‘ν•œ κΈ°λŠ₯이 μ‘΄μž¬ν•˜λŠ”λ°, 그쀑 ν•˜λ‚˜κ°€ λ°”λ‘œ ApplicationEventPublishing 이닀.

 

 

μ‹€μ œ μŠ€ν”„λ§μ˜ μ½”λ“œλ₯Ό ν™•μΈν•˜λ”λΌλ„ λ™μΌν•˜λ‹€

 

 

그리고 ν•΄λ‹Ή ApplicationEventPublisher 의 κ΅¬ν˜„μ€ ApplicationContext λ₯Ό μƒμ†ν•˜κ³  μžˆλŠ” AbstractApplicationContext μ—μ„œ μ‘΄μž¬ν•œλ‹€.

 

κ²°κ΅­ Events μ—κ²Œ raise() λΌλŠ” ν˜‘λ ₯을 μš”μ²­ν•˜κ²Œ 되면 AbstractApplicationContext 에 μ‘΄μž¬ν•˜λŠ” publishEvent() κ°€ 호좜되게 λœλ‹€.

 

Event κ°€ Publish 되면 Spring μ—μ„œ μ œκ³΅ν•˜λŠ” ApplicationEvent νƒ€μž…μΈμ§€ λ¨Όμ € ν™•μΈν•˜κ³  그렇지 μ•Šλ‹€λ©΄, Custom ν•œ Event 라면 ApplicationEvent 의 κ΅¬ν˜„μ²΄μΈ PayloadApplicationEvent 둜 λ³€ν™˜ν•œλ‹€.

 

@EventListener λŠ” μ–΄λ–»κ²Œ consuming ν•˜λŠ”κ°€

 

이제 μ—¬κΈ°μ„œλΆ€ν„° @EventListener λŠ” μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€μ— λŒ€ν•œ μ‹€λ§ˆλ¦¬κ°€ λ‚˜μ˜¨λ‹€.

 

μ•„κΉŒ 봀던 AbstractApplicationContext μ—μ„œ 쑰금 더 λ‚΄λ €κ°€λ‹€ 보면 μ‹€μ œλ‘œ Event λ₯Ό multicast ν•˜λŠ” 곳이 λ“±μž₯ν•˜κ²Œ λœλ‹€.

 

ApplicationEventMulticaster λŠ” μ—¬λŸ¬ ApplicationListener 객체λ₯Ό κ΄€λ¦¬ν•˜κ³  이벀트λ₯Ό publish ν•  수 μžˆλŠ” 도둝 ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€μΈλ°,

 

μ‹€μ œλ‘œ ν˜ΈμΆœλ˜λŠ” κ΅¬ν˜„μ²΄λŠ” λ°”λ‘œ SimpleApplicationEventMulticaster 이닀.

 

 

그리고 ν•΄λ‹Ή κ΅¬ν˜„μ²΄μ˜ λ‚΄λΆ€μ—μ„œ @EventListener μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 ApplicationListener 듀을 λͺ¨λ‘ λΆˆλŸ¬μ™€μ„œ μ μ ˆν•œ Listener μ—κ²Œ event λ₯Ό μ „λ‹¬ν•˜κ²Œ λ˜λŠ” 것이닀.

 

 

References