Domain Event on Springframework - redutan/redutan.github.io GitHub Wiki
๋๋ฉ์ธ ์ด๋ฒคํธ๊ฐ ๋ฐํ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๊ฒฝ
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
๋ฅผ ์ด์ฉํ์ฌ ํธ๋์ญ์ ๋ด์์ ์ด๋ฒคํธ ๋ฐํ ๋ฐ ๊ตฌ๋ ์ด ๊ฐ๋ฅํ๊ฒ ์ฒ๋ฆฌํ๋ ์ปดํฌ๋ํธ
์ค์ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๊ณ ๊ตฌ๋
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 ๋ฐ์ก์ ๊ฒฝ์ฐ์ ์ฌ์ฉํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค.
- ๋ฌผ๋ก
@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