initial commit

This commit is contained in:
Jon Lundy
2022-08-04 14:37:51 -06:00
commit 1010657a02
14 changed files with 1199 additions and 0 deletions

124
pkg/es/event/aggregate.go Normal file
View File

@@ -0,0 +1,124 @@
package event
import (
"errors"
"fmt"
"sync"
)
type Aggregate interface {
// ApplyEvent applies the event to the aggrigate state
ApplyEvent(...Event)
StreamID() string
AggregateRootInterface
}
// Raise adds new uncommitted events
func Raise(a Aggregate, lis ...Event) {
lis = NewEvents(lis...)
SetStreamID(a.StreamID(), lis...)
a.raise(lis...)
a.ApplyEvent(lis...)
}
// Append adds new committed events
func Append(a Aggregate, lis ...Event) {
a.append(lis...)
a.ApplyEvent(lis...)
}
// CheckVersion returns an error if the version does not match.
func CheckVersion(a Aggregate, version uint64) error {
if version != uint64(a.StreamVersion()) {
return fmt.Errorf("version wrong, got (proposed) %d != (expected) %d", version, a.StreamVersion())
}
return nil
}
// NotExists returns error if there are no events present.
func NotExists(a Aggregate) error {
if a.StreamVersion() != 0 {
return fmt.Errorf("%w, got version == %d", ErrShouldNotExist, a.StreamVersion())
}
return nil
}
type AggregateRootInterface interface {
// Events returns the aggrigate events
// pass true for only uncommitted events
Events(bool) Events
// StreamVersion returns last commit events
StreamVersion() uint64
// Version returns the current aggrigate version. (committed + uncommitted)
Version() uint64
raise(lis ...Event)
append(lis ...Event)
Commit()
}
var _ AggregateRootInterface = &AggregateRoot{}
type AggregateRoot struct {
events Events
streamVersion uint64
mu sync.RWMutex
}
func (a *AggregateRoot) Commit() {
a.streamVersion = uint64(len(a.events))
}
func (a *AggregateRoot) StreamVersion() uint64 {
return a.streamVersion
}
func (a *AggregateRoot) Events(new bool) Events {
a.mu.RLock()
defer a.mu.RUnlock()
events := a.events
if new {
events = events[a.streamVersion:]
}
lis := make(Events, len(events))
copy(lis, events)
return lis
}
func (a *AggregateRoot) Version() uint64 {
return uint64(len(a.events))
}
//lint:ignore U1000 is called by embeded interface
func (a *AggregateRoot) raise(lis ...Event) { //nolint
a.mu.Lock()
defer a.mu.Unlock()
a.posStartAt(lis...)
a.events = append(a.events, lis...)
}
//lint:ignore U1000 is called by embeded interface
func (a *AggregateRoot) append(lis ...Event) {
a.mu.Lock()
defer a.mu.Unlock()
a.posStartAt(lis...)
a.events = append(a.events, lis...)
a.streamVersion += uint64(len(lis))
}
func (a *AggregateRoot) posStartAt(lis ...Event) {
for i, e := range lis {
m := e.EventMeta()
m.Position = a.streamVersion + uint64(i) + 1
e.SetEventMeta(m)
}
}
var ErrShouldNotExist = errors.New("should not exist")

129
pkg/es/event/events.go Normal file
View File

@@ -0,0 +1,129 @@
package event
import (
"bytes"
"crypto/rand"
"fmt"
"io"
"strings"
"sync"
"time"
ulid "github.com/oklog/ulid/v2"
)
var pool = sync.Pool{
New: func() interface{} { return ulid.Monotonic(rand.Reader, 0) },
}
func getULID() ulid.ULID {
var entropy io.Reader = rand.Reader
if e, ok := pool.Get().(io.Reader); ok {
entropy = e
defer pool.Put(e)
}
return ulid.MustNew(ulid.Now(), entropy)
}
type Event interface {
EventMeta() Meta
SetEventMeta(Meta)
}
// Events is a list of events
type Events []Event
func NewEvents(lis ...Event) Events {
for i, e := range lis {
meta := e.EventMeta()
meta.Position = uint64(i)
meta.EventID = getULID()
e.SetEventMeta(meta)
}
return lis
}
func (lis Events) StreamID() string {
if len(lis) == 0 {
return ""
}
return lis.First().EventMeta().StreamID
}
func (lis Events) SetStreamID(streamID string) {
SetStreamID(streamID, lis...)
}
func (lis Events) First() Event {
if len(lis) == 0 {
return nil
}
return lis[0]
}
func (lis Events) Rest() Events {
if len(lis) == 0 {
return nil
}
return lis[1:]
}
func (lis Events) MarshalText() ([]byte, error) {
b := &bytes.Buffer{}
for i := range lis {
txt, err := MarshalText(lis[i])
if err != nil {
return nil, err
}
b.Write(txt)
}
return b.Bytes(), nil
}
func TypeOf(e Event) string {
if ie, ok := e.(interface{ UnwrapEvent() Event }); ok {
e = ie.UnwrapEvent()
}
if e, ok := e.(interface{ EventType() string }); ok {
return e.EventType()
}
// Default to printed representation for unnamed types
return strings.Trim(fmt.Sprintf("%T", e), "*")
}
type streamID string
func (s streamID) StreamID() string {
return string(s)
}
func StreamID(e Event) streamID {
return streamID(e.EventMeta().StreamID)
}
func SetStreamID(id string, lis ...Event) {
for _, e := range lis {
meta := e.EventMeta()
meta.StreamID = id
e.SetEventMeta(meta)
}
}
func EventID(e Event) ulid.ULID {
return e.EventMeta().EventID
}
func SetEventID(e Event, id ulid.ULID) {
meta := e.EventMeta()
meta.EventID = id
e.SetEventMeta(meta)
}
func SetPosition(e Event, i uint64) {
meta := e.EventMeta()
meta.Position = i
e.SetEventMeta(meta)
}
type Meta struct {
EventID ulid.ULID
StreamID string
Position uint64
}
func (m Meta) Time() time.Time {
return ulid.Time(m.EventID.Time())
}

View File

@@ -0,0 +1,59 @@
package event_test
import (
"bytes"
"testing"
"github.com/matryer/is"
"github.com/sour-is/ev/pkg/es/event"
)
type DummyEvent struct {
Value string
eventMeta event.Meta
}
func (e *DummyEvent) EventMeta() event.Meta {
if e == nil {
return event.Meta{}
}
return e.eventMeta
}
func (e *DummyEvent) SetEventMeta(eventMeta event.Meta) {
if e == nil {
return
}
e.eventMeta = eventMeta
}
func TestEventEncode(t *testing.T) {
is := is.New(t)
event.Register(&DummyEvent{})
var lis event.Events = event.NewEvents(
&DummyEvent{Value: "testA"},
&DummyEvent{Value: "testB"},
&DummyEvent{Value: "testC"},
)
lis.SetStreamID("test")
blis, err := event.EncodeEvents(lis...)
is.NoErr(err)
for _, b := range blis {
sp := bytes.SplitN(b, []byte{'\t'}, 4)
is.Equal(len(sp), 4)
is.Equal(string(sp[1]), "test")
is.Equal(string(sp[2]), "event_test.DummyEvent")
}
chk, err := event.DecodeEvents(blis...)
is.NoErr(err)
for i := range chk {
is.Equal(lis[i], chk[i])
}
}

201
pkg/es/event/reflect.go Normal file
View File

@@ -0,0 +1,201 @@
package event
import (
"bytes"
"encoding"
"encoding/json"
"fmt"
"io"
"net/url"
"reflect"
"strings"
"sync"
)
var (
eventTypes sync.Map
)
type UnknownEvent struct {
eventType string
values map[string]json.RawMessage
eventMeta Meta
}
var _ Event = (*UnknownEvent)(nil)
var _ json.Marshaler = (*UnknownEvent)(nil)
var _ json.Unmarshaler = (*UnknownEvent)(nil)
func NewUnknownEventFromValues(eventType string, meta Meta, values url.Values) *UnknownEvent {
jsonValues := make(map[string]json.RawMessage, len(values))
for k, v := range values {
switch len(v) {
case 0:
jsonValues[k] = []byte("null")
case 1:
jsonValues[k] = embedJSON(v[0])
default:
parts := make([][]byte, len(v))
for i := range v {
parts[i] = embedJSON(v[i])
}
jsonValues[k] = append([]byte("["), bytes.Join(parts, []byte(","))...)
jsonValues[k] = append(jsonValues[k], ']')
}
}
return &UnknownEvent{eventType: eventType, eventMeta: meta, values: jsonValues}
}
func NewUnknownEventFromRaw(eventType string, meta Meta, values map[string]json.RawMessage) *UnknownEvent {
return &UnknownEvent{eventType: eventType, eventMeta: meta, values: values}
}
func (u UnknownEvent) EventMeta() Meta { return u.eventMeta }
func (u UnknownEvent) EventType() string { return u.eventType }
func (u *UnknownEvent) SetEventMeta(em Meta) {
u.eventMeta = em
}
func (u *UnknownEvent) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, &u.values)
}
func (u *UnknownEvent) MarshalJSON() ([]byte, error) {
return json.Marshal(u.values)
}
// Register a type container for Unmarshalling values into. The type must implement Event and not be a nil value.
func Register(lis ...Event) {
for _, e := range lis {
if e == nil {
panic(fmt.Sprintf("can't register event.Event of type=%T with value=%v", e, e))
}
value := reflect.ValueOf(e)
if value.IsNil() {
panic(fmt.Sprintf("can't register event.Event of type=%T with value=%v", e, e))
}
value = reflect.Indirect(value)
typ := value.Type()
eventTypes.LoadOrStore(TypeOf(e), typ)
}
}
func GetContainer(s string) Event {
if typ, ok := eventTypes.Load(s); ok {
if typ, ok := typ.(reflect.Type); ok {
newType := reflect.New(typ)
newInterface := newType.Interface()
if typ, ok := newInterface.(Event); ok {
return typ
}
}
}
return &UnknownEvent{eventType: s}
}
func MarshalText(e Event) (txt []byte, err error) {
b := &bytes.Buffer{}
if _, err = writeMarshaler(b, e.EventMeta().EventID); err != nil {
return nil, err
}
b.WriteRune('\t')
if _, err = b.WriteString(e.EventMeta().StreamID); err != nil {
return nil, err
}
b.WriteRune('\t')
if _, err = b.WriteString(TypeOf(e)); err != nil {
return nil, err
}
b.WriteRune('\t')
if enc, ok := e.(encoding.TextMarshaler); ok {
if txt, err = enc.MarshalText(); err != nil {
return nil, err
}
} else {
if txt, err = json.Marshal(e); err != nil {
return nil, err
}
}
_, err = b.Write(txt)
return b.Bytes(), err
}
func UnmarshalText(txt []byte, pos uint64) (e Event, err error) {
sp := bytes.SplitN(txt, []byte{'\t'}, 4)
if len(sp) != 4 {
return nil, fmt.Errorf("invalid format. expected=4, got=%d", len(sp))
}
m := Meta{}
if err = m.EventID.UnmarshalText(sp[0]); err != nil {
return nil, err
}
m.StreamID = string(sp[1])
m.Position = pos
eventType := string(sp[2])
e = GetContainer(eventType)
if enc, ok := e.(encoding.TextUnmarshaler); ok {
if err = enc.UnmarshalText(sp[3]); err != nil {
return nil, err
}
} else {
if err = json.Unmarshal(sp[3], e); err != nil {
return nil, err
}
}
e.SetEventMeta(m)
return e, nil
}
func writeMarshaler(out io.Writer, in encoding.TextMarshaler) (int, error) {
if b, err := in.MarshalText(); err != nil {
return 0, err
} else {
return out.Write(b)
}
}
// DecodeEvents unmarshals the byte list into Events.
func DecodeEvents(lis ...[]byte) (Events, error) {
elis := make([]Event, len(lis))
var err error
for i, txt := range lis {
elis[i], err = UnmarshalText(txt, uint64(i))
if err != nil {
return nil, err
}
}
return elis, nil
}
func EncodeEvents(events ...Event) (lis [][]byte, err error) {
lis = make([][]byte, len(events))
for i, txt := range events {
lis[i], err = MarshalText(txt)
if err != nil {
return nil, err
}
}
return lis, nil
}
func embedJSON(s string) json.RawMessage {
if len(s) > 1 && s[0] == '{' && s[len(s)-1] == '}' {
return []byte(s)
}
if len(s) > 1 && s[0] == '[' && s[len(s)-1] == ']' {
return []byte(s)
}
return []byte(fmt.Sprintf(`"%s"`, strings.Replace(s, `"`, `\"`, -1)))
}