Skip to content

Commit

Permalink
FEATURE: Major rework part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
bwaidelich committed Aug 2, 2023
1 parent 92c3443 commit a6068e2
Show file tree
Hide file tree
Showing 49 changed files with 1,823 additions and 1,198 deletions.
181 changes: 181 additions & 0 deletions Specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
## EventStore

### Reading

Expects a [StreamQuery](#StreamQuery) and an optional starting [SequenceNumber](#SequenceNumber) and returns an [EventStream](#EventStream).

**Note:** The EventStore should also allow for _backwards_ iteration on the EventStream in order to support cursor based pagination.

### Writing

Expects a set of [Events](#Events) and an [AppendCondition](#AppendCondition) and returns the last appended [SequenceNumber](#SequenceNumber).

#### Potential API

```
EventStore {
read(query: StreamQuery, from?: SequenceNumber): EventStream
readBackwards(query: StreamQuery, from?: SequenceNumber): EventStream
append(events: Events, condition: AppendCondition): SequenceNumber
}
```

## StreamQuery

The `StreamQuery` describes constraints that must be matched by [Event](#Event)s in the [EventStore](#EventStore).

* It _MAY_ contain a set of [StreamQuery Criteria](#StreamQuery-Criterion)

**Note:** All criteria of a StreamQuery are merged into a *logical disjunction*, so the example below matches all events, that match the first **OR** the second criterion.

#### Potential serialization format

```json
{
"version": "1.0",
"criteria": [{
"type": "EventTypes",
"properties": {
"event_types": ["EventType1", "EventType2"]
}
}, {
"type": "Tags",
"properties": {
"tags": ["foo:bar", "baz:foos"],
}
}, {
"type": "EventTypesAndTags",
"properties": {
"event_types": ["EventType2", "EventType3"],
"tags": ["foo:bar", "foo:baz"],
}
}]
}
```


## StreamQuery Criterion

In v1 the only supported criteria types are:

* `Tags` – allows to target one or more [Tags](#Tags)
* `EventTypes` – allows to target one or more [EventType](#EventType)s
* `EventTypesAndTags` – allows to target one or more [Tags](#Tags) and one or more [EventType](#EventType)s

## SequenceNumber

When an [Event](#Eventd) is appended to the [EventStore](#EventStore) a `SequenceNumber` is assigned to it.

It...
* _MUST_ be unique for one EventStore
* _MUST_ be monotonic increasing by `1`
* _MUST_ have an allowed minimum value of `1`
* _SHOULD_ have a reasonably high maximum value (depending on programming language and environment)


## EventStream

When reading from the [EventStore](#EventStore) an `EventStream` is returned.

It...
* It _MUST_ be iterable
* It _MUST_ return an [EventEnvelope](#EventEnvelope) for every iteration
* It _MUST_ include new events if they occur during iteration (i.e. it should be a stream, not a fixed set)
* Individual [EventEnvelope](#EventEnvelope) instances _MAY_ be converted during iteration for performance optimization
* Batches of events _MAY_ be loaded from the underlying storage at once for performance optimization

## EventEnvelope

Each item in the [EventStream](#EventStream) is an `EventEnvelope` that consists of the underlying event and metadata, like the [SequenceNumber](#SequenceNumber) that was added during the `append()` call.

```json
{
"event": {
"id": "15aaa216-4179-46d9-999a-75516e21a1c6",
"type": "SomeEventType",
"data": "{\"some\":\"data\"}"
"tags": ["type1:value1", "type2:value2"]
},
"sequence_number": 1234
}
```

## Events

A set of [Event](#Event) instances that is passed to the `append()` method of the [EventStore](#EventStore)

It...
* _MUST_ not be empty
* _MUST_ be iterable, each iteration returning an [Event](#Event)

## Event

* It _MUST_ contain a globally unique [EventId](#EventId)
* It _MUST_ contain an [EventType](#EventType)
* It _MUST_ contain [EventData](#EventData)
* It _MAY_ contain [Tags](#Tags)

#### Potential serialization format

```json
{
"id": "15aaa216-4179-46d9-999a-75516e21a1c6",
"type": "SomeEventType",
"data": "{\"some\":\"data\"}"
"tags": ["key1:value1", "key1:value2"]
}
```

## EventId

String based globally unique identifier of an [Event](#Event)

* It _MUST_ satisfy the regular expression `^[\w\-]{1,100}$`
* It _MAY_ be implemented as a [UUID](https://www.ietf.org/rfc/rfc4122.txt)

## EventType

String based type of an event

* It _MUST_ satisfy the regular expression `^[\w\.\:\-]{1,200}$`

## EventData

String based, opaque payload of an [Event](#Event)

* It _SHOULD_ have a reasonable large enough maximum length (depending on language and environment)
* It _MAY_ contain [JSON](https://www.json.org/)
* It _MAY_ be serialized into an empty string

## Tags

A set of [Tag](#Tag) instances.

* It _MUST_ contain at least one [Tag](#Tag)
* It _MAY_ contain multiple [Tag](#Tag)s with the same value
* It _SHOULD_ not contain muliple [Tag](#Tag)s with the same key/value pair

## Tag

A `Tag` can add domain specific metadata (usually the ids of an entity or concept of the core domain) to an event allowing for custom partitioning

**NOTE:** If the `value` is not specified, all tags of the given `key` will match (wildcard)

* It _MUST_ contain a `key` that satisfies the regular expression `^[[:alnum:]\-\_]{1,50}$`
* It _CAN_ contain a `value` that satisfies the regular expression `^[[:alnum:]\-\_]{1,50}$`

## AppendCondition

* It _MUST_ contain a [StreamQuery](#StreamQuery)
* It _MUST_ contain a [ExpectedHighestSequenceNumber](#ExpectedHighestSequenceNumber)

## ExpectedHighestSequenceNumber

Can _either_ be an instance of [SequenceNumber](#SequenceNumber)
Or one of:
* `NONE` – No event must match the specified [StreamQuery](#StreamQuery)
* `ANY` – Any event matches (= wildcard [AppendCondition](#AppendCondition))
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"require": {
"php": ">=8.2",
"ramsey/uuid": "^4.7",
"psr/clock": "^1",
"webmozart/assert": "^1.11"
},
"require-dev": {
Expand Down
176 changes: 176 additions & 0 deletions schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$ref": "#/$defs/AppendCondition",
"$defs": {
"AppendCondition": {
"type": "object",
"additionalProperties": false,
"properties": {
"query": {
"$ref": "#/$defs/StreamQuery"
},
"expected_highest_sequence_number": {
"oneOf": [
{
"const": "NONE"
},
{
"const": "ANY"
},
{
"$ref": "#/$defs/SequenceNumber"
}
]
}
},
"required": [
"query",
"expected_highest_sequence_number"
]
},
"StreamQuery": {
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+$"
},
"criterias": {
"type": "array",
"items": {
"$ref": "#/$defs/StreamQueryCriteria"
}
}
},
"required": [
"version",
"criterias"
]
},
"StreamQueryCriteria": {
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"EventTypesAndTags",
"EventTypes",
"Tags"
]
},
"properties": {
"type": "object",
"additionalProperties": false,
"properties": {
"tags": {
"type": "array",
"items": {
"$ref": "#/$defs/Tag"
}
},
"event_types": {
"type": "array",
"items": {
"$ref": "#/$defs/EventType"
}
}
},
"required": [
"tags",
"event_types"
]
}
},
"required": [
"type",
"criteria"
]
},
"EventEnvelope": {
"type": "object",
"additionalProperties": false,
"properties": {
"event": {
"$ref": "#/$defs/Event"
},
"sequence_number": {
"$ref": "#/$defs/SequenceNumber"
}
},
"required": [
"event",
"sequence_number"
]
},
"Event": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/$defs/EventId"
},
"type": {
"$ref": "#/$defs/EventType"
},
"data": {
"$ref": "#/$defs/EventData"
},
"tags": {
"type": "array",
"items": {
"$ref": "#/$defs/Tag"
}
}
},
"required": [
"id",
"type",
"data",
"tags"
]
},
"EventId": {
"type": "string",
"pattern": "^[\\w\\-]+$",
"minLength": 1,
"maxLength": 100
},
"EventType": {
"type": "string",
"pattern": "^[\\w\\.\\:\\-]+$",
"minLength": 1,
"maxLength": 200
},
"EventData": {
"type": "string"
},
"Tag": {
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"type": "string",
"minLength": 1,
"maxLength": 50,
"pattern": "^[A-Za-z0-9\\-\\_]+$"
},
"value": {
"minLength": 1,
"maxLength": 50,
"type": "string",
"pattern": "^[A-Za-z0-9\\-\\_]+$"
}
},
"required": [
"key",
"value"
]
},
"SequenceNumber": {
"type": "integer",
"minimum": 1
}
}
}
19 changes: 0 additions & 19 deletions src/EventNormalizer.php

This file was deleted.

Loading

0 comments on commit a6068e2

Please sign in to comment.