データが更新されたらXXXをしたい(メールの送信, etc...) みたいな要求は多いと思います。
springにもイベントのPub/Subをする仕組みは以前からありましたが、
spring-data-commons 1.13から集約ルート(JPAで言う所のEntity)からのドメインイベント発行という機能が追加されていました。
ちなみにspring-data-commons 1.13(リリーストレイン名はIngalls
)はspring-boot 1.5.0からサポートされたみたいです。
サンプルコード
- spring-data-jpaの場合
- spring-data-redisとかでも多分似たような感じで使える(はず)
Domain Service
before
@RequiredArgsConstructor
@Service
public class DocumentService {
private final DocumentRepository repository;
private final ApplicationEventPublisher eventPublisher;
public Document save() {
// 保存する
Document document = repository.save(Document.create("xxx"));
// イベントを発行する
eventPublisher.publishEvent(new Document.OnCreatedEvent(document));
return document;
}
}
after
@RequiredArgsConstructor
@Service
public class DocumentService {
private final DocumentRepository repository;
public Document save() {
return repository.save(Document.create("xxx"));
}
}
違い
-
ApplicationEventPublisher
をDIする必要がなくなった - どこでイベント発行してんの???となると思うのでさらに↓へ
Entity
before
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "document")
public class Document implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "name")
private String name;
public static Document create(String name) {
Document document = new Document();
document.name = name;
return document;
}
@RequiredArgsConstructor
@Getter
public static class OnCreatedEvent {
private final Document document;
}
}
after
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "document")
public class Document extends AbstractAggregateRoot implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "name")
private String name;
public static Document create(String name) {
Document document = new Document();
document.name = name;
document.registerEvent(new OnCreatedEvent(document));
return document;
}
@RequiredArgsConstructor
@Getter
public static class OnCreatedEvent {
private final Document document;
}
}
違い
-
org.springframework.data.domain.AbstractAggregateRoot
を継承- 継承したくない場合は
AbstractAggregateRoot
の中身と同じ実装をすればOK
- 継承したくない場合は
-
AbstractAggregateRoot#registerEvent
の呼び出し
Event Listener
// このクラスは特に変更の必要なし
@Slf4j
@Component
public class DocumentEventListener {
@EventListener
public void onCreated(Document.OnCreatedEvent event) {
Document document = event.getDocument();
log.info("document (id={}, name={}) created !", document.getId(), document.getName());
}
}
感想
- (イベントを登録する責務が移動したことで)ドメイン層がピュアになったような気がする
-
ApplicationEventPublisher
がないのになんでイベント発行できてんの???みたいな混乱はあるかもしれない- ※実際は内部で
Repository#save()
をトリガーにApplicationEventPublisher#publishEvent()
を呼んでいる(@see org.springframework.data.repository.core.support.EventPublishingRepositoryProxyPostProcessor
)
- ※実際は内部で
- IDEのサポートが受けられなくなる
- IntelliJ IDEAだと
ApplicationEventPublisher#publishEvent()
してるコードからイベントリスナーの該当するメソッドにジャンプ(+その逆も)できる神機能がある
- IntelliJ IDEAだと
ハマリポイント
- イベントの発行がRepositoryに寄ったことで、Respositoryをモックしてるテストコードでイベント発行されない!なんで???みたいになる可能性がある
サンプルコード
公式ドキュメント
http://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.domain-events