EventStoreをMicroservicesとして実装するときのプロトコルの案


EventStoreはappend-only databaseの一種で、ドメインイベントを蓄積するためのストレージである。イベントは追加(append)だけでき、一旦追加したイベントは変更・削除することができない。イベントストアに蓄積された情報をもとに、読み取り専用のためのドメインモデルであるリードモデル(read model)やクエリーモデル(query model)を作ることを、イベントソーシング(event sourcing)と言う。

![](https://qiita-image-store.s3.amazonaws.com/0/889/ad4960df-6b18-f0ba-5c47-783f69bba562.png)

このドキュメントはEventStoreをMicroservicesとして提供することを念頭に、そのサービスが実装するであろうRESTful APIのプロトコルを検討するものだ。まだ未確定の部分がある。ご指摘などがあれば歓迎。

各URLの`/stores/:store_name `はマルチテナントのEventStoreを実装する場合に必要になるであろう名前空間だ。マルチテナントを実装しない場合は、この階層は不要になる。

## イベントを追加する

```
PUT /stores/:store_name/streams/:stream_name
```

### Input

JSON形式

Name | Type | Description
-----|------|-------------
`event_type` | `string` | **Required**. イベントの型。
`stream_version` | `int` | **Required**. イベントストリームのバージョン。楽観ロックのチェックに使う。
`body` | `string` | **Required**. イベントの中身。オブジェクトであればシリアライズされたもの。
`sha1` | `string` | **Required**. `body`のチェックサム。

### Response

```http
HTTP/1.1 204 No Content
```

### Errors

Status | Code | Description
-------|------|--------------
`400 Bad Request` | `missing_field` | 必須の入力がないとき。
`400 Bad Request` | `stream_version.must_be_numeric` | `stream_version`が正の整数でないとき。
`400 Bad Request` | `checksum.did_not_match` |  `checksum`の値が一致しなかったとき。
`409 Conflict` | `stream_version.conflict` | 与えられた`stream_version`が同一のイベントが既に追加されているとき。または、与えられた`stream_version`が連番でないとき。つまり、イベントストリームの最新のイベントの`stream_version`+1がリクエストの`stream_version`と同一でないとき。

## イベントストリームを取得する

```
GET /stores/:store_name/streams/:stream_name
```

### Input

クエリーストリング

Name | Type | Description
-----|------|-------------
`since` | `int` | イベントIDを指定すると、そのバージョン以降のイベントのみがレスポンスになる。

### Response

ストリーム全体をJSONとして返すと、クライアントは全て受け取ってから処理しなければならない。Server-sent events(SSE)や Multipartでレスポンスするように実装すると、逐次処理ができるのでクライアントに優しい。

SSEとMultipartの両方を実装するのであれば、`Accept`ヘッダーを見てレスポンスの内容を変える方法がある。

```http:SSEでほしいときのリクエストヘッダ
Accept: text/event-stream
```

#### Headers

Name | Type | Description
-----|------|-------------
`Stream-Name` | `string` | イベントストリーム名。
`Stream-Version` | `int` | イベントストリームバージョン。


#### Body

Name(SSE) | Name(Multipart) | Type | Description
----------|-----------------|------|-------------
`id` | 各パートの`Event-Id`ヘッダ | `int` | イベントID。
`event` | 各パートの`Event-Type`ヘッダ | `string` | イベントの型。
`data` | 各パートのBody部分 | `string` | イベントの中身。

#### Server-sent eventsの例

```http
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: text/event-stream
Transfer-Encoding: chunked
Stream-Name: 9dced0a4-2727-4e9a-ae65-378135c06565
Stream-Version: 2

id: 60
event: PostCreated
data: {
data:   "post_id": "9dced0a4-2727-4e9a-ae65-378135c06565",
data:   "title": "テスト",
data:   "created_by": 72,
data:   "occurred_on": "2014-11-18T12:07:15Z"
data: }

id: 87
event: TitleChanged
data: {
data:   "post_id": "9dced0a4-2727-4e9a-ae65-378135c06565",
data:   "title": "タイトル変更",
data:   "occurred_on": "2014-11-18T15:22:44Z"
data: }

```

#### Multipartの例

```http
HTTP/1.1 200 OK
Content-Length: 26814
Content-Type: multipart/mixed; boundary=t9bhpX6qJvUDzk7X6Acsf4TX
Stream-Name: 9dced0a4-2727-4e9a-ae65-378135c06565
Stream-Version: 2

--t9bhpX6qJvUDzk7X6Acsf4TX

Event-ID: 60
Event-Type: PostCreated
Content-Type: application/json; charset=UTF-8

{
  "post_id": "9dced0a4-2727-4e9a-ae65-378135c06565",
  "title": "テスト",
  "created_by": 72,
  "occurred_on": "2014-11-18T12:07:15Z"
}

--t9bhpX6qJvUDzk7X6Acsf4TX

Event-ID: 60
Event-Type: TitleChanged
Content-Type: application/json; charset=UTF-8

{
  "post_id": "9dced0a4-2727-4e9a-ae65-378135c06565",
  "title": "タイトル変更",
  "occurred_on": "2014-11-18T15:22:44Z"
}

--t9bhpX6qJvUDzk7X6Acsf4TX--

```

### Errors

Status | Code | Description
-------|------|--------------
`404 Not Found` | `missing` | イベントストリームが見つからない。つまり、イベントがひとつもないストリームについてリクエストしたとき。

## イベント全体を取得する

```
GET /stores/:store_name/events
```

### Input

クエリーストリング

Name | Type | Description
-----|------|-------------
`since` | `int` | イベントIDを指定すると、そのバージョン以降のイベントのみがレスポンスになる。

### Response

まだイベントがひとつもないときでも、`404`にはならない。

#### Body

Name(SSE) | Name(Multipart) | Type | Description
----------|-----------------|------|-------------
`id` | 各パートの`Event-Id`ヘッダ | `int` | イベントID。
`event` | 各パートの`Event-Type`ヘッダ | `string` | イベントの型。
`data` | 各パートのBody部分 | `string` | イベントの中身。

#### Server-sent eventsの例

```http
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: text/event-stream
Transfer-Encoding: chunked

id: 60
event: PostCreated
data: {
data:   "post_id": "9dced0a4-2727-4e9a-ae65-378135c06565",
data:   "title": "テスト",
data:   "created_by": 72,
data:   "occurred_on": "2014-11-18T12:07:15Z"
data: }

id: 61
event: PostCreated
data: {
data:   "post_id": "c85848e8-3223-444f-9305-b07ea10000f7",
data:   "title": "テスト",
data:   "created_by": 43,
data:   "occurred_on": "2014-11-18T12:11:23Z"
data: }

id: 62
event: PostDeleted
data: {
data:   "post_id": "c679ca51-3a70-4a37-8a62-42b88e5f9c02",
data:   "occurred_on": "2014-11-18T13:40:16Z"
data: }

```

#### Multipartの例

```http
HTTP/1.1 200 OK
Content-Length: 26814
Content-Type: multipart/mixed; boundary=t9bhpX6qJvUDzk7X6Acsf4TX

--t9bhpX6qJvUDzk7X6Acsf4TX

Event-ID: 60
Event-Type: PostCreated
Content-Type: application/json; charset=UTF-8

{
  "post_id": "9dced0a4-2727-4e9a-ae65-378135c06565",
  "title": "テスト",
  "created_by": 72,
  "occurred_on": "2014-11-18T12:07:15Z"
}

--t9bhpX6qJvUDzk7X6Acsf4TX

Event-ID: 61
Event-Type: PostCreated
Content-Type: application/json; charset=UTF-8

{
  "post_id": "c85848e8-3223-444f-9305-b07ea10000f7",
  "title": "テスト",
  "created_by": 43,
  "occurred_on": "2014-11-18T12:11:23Z"
}

--t9bhpX6qJvUDzk7X6Acsf4TX

Event-ID: 62
Event-Type: PostDeleted
Content-Type: application/json; charset=UTF-8
{
  "post_id": "c679ca51-3a70-4a37-8a62-42b88e5f9c02",
  "occurred_on": "2014-11-18T13:40:16Z"
}

--pX6qJvUDzk7X6Acsf4TX--

```

## イベントストアを空っぽにする (管理用)

```
POST /stores/:store_name/purge
```

### Response

```http
HTTP/1.1 204 No Content
```

## ストリームが存在するか？

```
HEAD /stores/:store_name/streams/:stream_name
```

### Response

todo

## ストリーム一覧 (管理用)


```
GET /stores/:store_name/streams
```

### Response

todo

## イベントストアを作る (管理用)

```
POST /stores
```

### Input

JSON形式

Name | Type | Description
-----|------|-------------
`store_name` | `string` | **Required**. イベントストア名。


### Response

todo

## イベントストア一覧 (管理用)

```
GET /stores
```

### Response

todo

## ??? (管理用)

```
GET /stores/:store_name
```

### Response

todo

## イベントストア名を変更する (管理用)

todo

## イベントストアを削除する (管理用)

```
DELETE /stores/:store_name
```