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

データが更新されたら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
```java
@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
```java
@RequiredArgsConstructor
@Service
public class DocumentService {

  private final DocumentRepository repository;

  public Document save() {

    return repository.save(Document.create("xxx"));
  }
}
```

### 違い
- `ApplicationEventPublisher`をDIする必要がなくなった
- どこでイベント発行してんの？？？となると思うのでさらに↓へ

## Entity

### before

```java
@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

```java
@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

```java
// このクラスは特に変更の必要なし
@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