DDD
Server-Sent-Events
microservices
EventSourcing
EventStore

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

More than 3 years have passed since last update.

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

このドキュメントは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/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ヘッダーを見てレスポンスの内容を変える方法がある。

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/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/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/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/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/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