3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

spring-data-commons 1.13から導入されたイベント発行の仕組み

Last updated at Posted at 2017-02-02

データが更新されたら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()してるコードからイベントリスナーの該当するメソッドにジャンプ(+その逆も)できる神機能がある

ハマリポイント

  • イベントの発行がRepositoryに寄ったことで、Respositoryをモックしてるテストコードでイベント発行されない!なんで???みたいになる可能性がある

サンプルコード

https://github.com/sis-yoshiday/spring-data-domain-event

公式ドキュメント

http://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.domain-events

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?