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
ヘッダーを見てレスポンスの内容を変える方法がある。
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