Domain Event on Springframework - redutan/redutan.github.io GitHub Wiki

Domain event sequence diagram example

Domain event sequence diagram example

Domain event configuration

๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ํ™˜๊ฒฝ

DomainEvent.java

/**
 * ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋งˆ์ปค ์ธํ„ฐํŽ˜์ด์Šค
 */
public interface DomainEvent {
}
  • ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” org.springframework.context.ApplicationEvent๋ฅผ ์ƒ์† ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ์„ ํƒ

๊ฐœ์ธ์ ์œผ๋กœ ์ƒ์†์„ ๋ณ„๋กœ ์ข‹์•„ํ•˜์ง€ ์•Š์•„์„œ ํ•„์š”ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•จ

Events.java

public class Events {
    private static ThreadLocal<ApplicationEventPublisher> publisherLocal = new ThreadLocal<>();

    public static void publish(DomainEvent event) {
        if (event == null) {
            return;
        }
        if (publisherLocal.get() != null) {
            publisherLocal.get().publishEvent(event);
        }
    }

    static void setPublisher(ApplicationEventPublisher publisher) {
        publisherLocal.set(publisher);
    }


    static void reset() {
        publisherLocal.remove();
    }
}
  • ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ํŽธ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ

EventPublisherAspect.java

@Aspect
@Component
@Slf4j
public class EventPublisherAspect implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;
    private ThreadLocal<Boolean> appliedLocal = new ThreadLocal<>();

    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object handleEvent(ProceedingJoinPoint joinPoint) throws Throwable {
        Boolean appliedValue = appliedLocal.get();
        boolean nested;
        if (appliedValue != null && appliedValue) {
            nested = true;
        } else {
            nested = false;
            appliedLocal.set(Boolean.TRUE);
        }
        if (!nested) Events.setPublisher(publisher);
        try {
            return joinPoint.proceed();
        } finally {
            if (!nested) {
                Events.reset();
                appliedLocal.remove();
            }
        }
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.publisher = eventPublisher;
    }
}
  • ThreadLocal๊ณผ ์Šคํ”„๋ง์˜ @Aspect๋ฅผ ์ด์šฉํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ด๋ฒคํŠธ ๋ฐœํ–‰ ๋ฐ ๊ตฌ๋…์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ

Publish, Subscribe and Event

์‹ค์ œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๊ณ  ๊ตฌ๋…

PartnerBlockedEvent.java

/**
 * ํ˜‘๋ ฅ์‚ฌ ์ฐจ๋‹จ ์ด๋ฒคํŠธ
 * <p>
 *     1. ๊ด€๋ฆฌํ•˜๋Š” ์—…์ฒด๋ฅผ ์ƒ์œ„ ํ˜‘๋ ฅ์‚ฌ๋กœ ์ด๊ด€
 * </p>
 * @see PartnerEventHandler#handle(PartnerBlockedEvent)
 */
@Value
public class PartnerBlockedEvent implements DomainEvent {
    @NonNull
    private Partner blockedPartner;
    @NonNull
    private User publishUser;
}
  • ์‹ค์ œ ๊ตฌ์ƒ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ์ •์˜

Partner.java

@Entity
public class Partner implements User {
    // ...
    /**
     * ํ˜„ ํ˜‘๋ ฅ์‚ฌ๋ฅผ ์ฐจ๋‹จ
     * @param user ์ฐจ๋‹จํ•˜๋Š” ์‚ฌ์šฉ์ž
     */
    public void block(User user) {
        // ์ž๊ธฐ์ž์‹ ์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์—†์Œ
        if (partnerNo.equals(user.getPartnerNo())) {
            throw new ImpossibleBlockException("Can't block myself : " + userId);
        }
        this.block = true;
        this.cau.update(user);    // ๋งˆ์ง€๋ง‰ ์ˆ˜์ •์ž, ์ˆ˜์ •์ผ์‹œ ๋ณ€๊ฒฝ
        // ์ฐจ๋‹จ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋ฐœํ–‰ !!!
        Events.publish(new PartnerBlockedEvent(this, user));
    }
}
  • Entity(๋„๋ฉ”์ธ ๊ฐ์ฒด) ๋‚ด์—์„œ ์‹ค์ œ ๊ตฌ์ƒ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰!!!

PartnerEventSubscriber.java

/**
 * ํ˜‘๋ ฅ์‚ฌ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๊ตฌ๋… ์ปดํฌ๋„ŒํŠธ
 */
@Component
@Slf4j
public class PartnerEventSubscriber {
    private final CompanyCommander companyCommander;

    @Autowired
    public PartnerEventSubscriber(CompanyCommander companyCommander) {
        this.companyCommander = companyCommander;
    }

    /**
     * ํ˜‘๋ ฅ์‚ฌ ์ฐจ๋‹จ ์ด๋ฒคํŠธ ๊ตฌ๋… ์ฒ˜๋ฆฌ - ๋™์ผ ํŠธ๋žœ์žญ์…˜ ๋‚ด ์ฒ˜๋ฆฌ(BEFORE_COMMIT)
     * <p>
     *     ํ˜‘๋ ฅ์‚ฌ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ํ•˜์œ„ ์—…์ฒด๋“ค์„ ์ƒ์œ„ ์—…์ฒด๋“ค๋กœ ์ด๊ด€
     * </p>
     * @param event ํ˜‘๋ ฅ์‚ฌ ์ฐจ๋‹จ ์ด๋ฒคํŠธ
     */
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    // @Async ๋น„๋™๊ธฐ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
    public void subscribe(PartnerBlockedEvent event) {
        // ์ฐจ๋‹จ๋œ ํ˜‘๋ ฅ์‚ฌ
        Partner blockedPartner = event.getBlockedPartner();
        companyCommander.moveCompanies(blockedPartner);
        // ...
        // ๊ตฌ๋… ๋‚ด ์ด๋ฒคํŠธ ๋ฐœํ–‰์ด๋ฏ€๋กœ ์ •์ƒ์ ์œผ๋กœ ๋ฐœํ–‰๋˜์ง€ ์•Š์Œ !!!
        anotherPatner.block(event.getPublishUser());
    }
}
  • ์‹ค์ œ ๊ตฌ์ƒ ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ๋…ํ•ด์„œ ์ฒ˜๋ฆฌ
  • ํ•„์š”์— ๋”ฐ๋ผ์„œ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ํŠธ๋žœ์žญ์…˜ ์™ธ(AFTER_COMMIT)๋กœ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ ์žˆ์Œ
    • ์œ„์™€ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์ด์ง€๋งŒ Mail, SMS ๋ฐœ์†ก์˜ ๊ฒฝ์šฐ์—๋Š” ํŠธ๋žœ์žญ์…˜ ์™ธ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.
  • ์žฌ๋ฏธ์žˆ๋Š” ์ ์€ ์ด๋ฒคํŠธ ๊ตฌ๋… ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ ๋‹ค์‹œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋ฐœํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.
  • handle ๋ฉ”์†Œ๋“œ์— @Async๋ฅผ ๋‹ฌ๋ฉด ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.
    • ๋ฌผ๋ก  @EnableAsync์™€ ๊ฐ™์€ ํ™˜๊ฒฝ์„ค์ •์ด ์šฐ์„  ๋˜์•ผํ•œ๋‹ค.
    • ํŠธ๋žœ์žญ์…˜ ๋‚ด ์ฒ˜๋ฆฌ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ•˜๋‚˜ ๊ฐœ์ธ์ ์œผ๋กœ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ฒ˜๋ฆฌ๊ฐ€ ์š”๊ตฌ๋˜๋ฉด ๋™๊ธฐ์ (non-async)์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.
    • ๋น„๋™๊ธฐ๋Š” ์•„๋ฌด๋ž˜๋„ ํŠธ๋žœ์žญ์…˜์˜ ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š” Mail, SMS ๋ฐœ์†ก์˜ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

Reference

PlanUml

Domain event sequence diagram example

@startuml
autonumber@startuml
autonumber
actor Client
entity Partner
participant  PartnerBlockedEvent
control PartnerEventSubscriber
Client->Partner:block()
activate Partner
Partner->Partner:domain logic
Partner->PartnerBlockedEvent:new
activate PartnerBlockedEvent
Partner<-PartnerBlockedEvent
deactivate PartnerBlockedEvent
Partner-->PartnerEventSubscriber:publish event
activate PartnerEventSubscriber
PartnerEventSubscriber->CompanyCommander:moveCompanies()
activate CompanyCommander
PartnerEventSubscriber<-CompanyCommander
deactivate CompanyCommander
deactivate PartnerEventSubscriber
deactivate Partner
@enduml
โš ๏ธ **GitHub.com Fallback** โš ๏ธ