-
Notifications
You must be signed in to change notification settings - Fork 0
Domain Event on Springframework
정명주(myeongju.jung) edited this page Oct 14, 2017
·
6 revisions
도메인 이벤트가 발행가능하게 하는 환경
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
JAVA
JPA
- JPA-Create-And-Update
- Optional-Eager
- QueryDsl-Configuration
- QueryDsl-More-Type-safety
- QueryDsl-SubQuery
DDD
Install
Spring
Spring-Boot
- Swagger2-Configuration
- Spring-Restdocs-Configuration
- Spring-Page-Jackson
- JSR310-Guide
- logback-spring.xml
- WebMvcUtils.java
- Spring-Boot-Properties
- Spring-Boot-Hidden-Gems
- Spring-Boot-Config
Spring-Cloud
- Spring-Cloud-Zuul
- Spring-Cloud-Feign
- Spring-Cloud-Hystrix
- Spring-Cloud-Consul
- Spring-Cloud-Ribbon
- Spring-Cloud-Circuit-Breaker
JavaScript
Gradle
Test
Linux
Etc
TODO http://zoltanaltfatter.com/2017/06/09/publishing-domain-events-from-aggregate-roots/