Edited at

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