refactor: push commands in to cmd and ev to root as library
This commit is contained in:
@@ -16,9 +16,9 @@ import (
|
||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/cache"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
"github.com/sour-is/ev/pkg/locker"
|
||||
@@ -41,8 +41,8 @@ type diskStore struct {
|
||||
m_disk_write syncint64.Counter
|
||||
}
|
||||
|
||||
const AppendOnly = es.AppendOnly
|
||||
const AllEvents = es.AllEvents
|
||||
const AppendOnly = ev.AppendOnly
|
||||
const AllEvents = ev.AllEvents
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
ctx, span := lg.Span(ctx)
|
||||
@@ -65,7 +65,7 @@ func Init(ctx context.Context) error {
|
||||
d.m_disk_write, err = m.SyncInt64().Counter("disk_write")
|
||||
errs = multierr.Append(errs, err)
|
||||
|
||||
es.Register(ctx, "file", d)
|
||||
ev.Register(ctx, "file", d)
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func (e *eventLog) Append(ctx context.Context, events event.Events, version uint
|
||||
}
|
||||
|
||||
if version != AppendOnly && version != last {
|
||||
err = fmt.Errorf("%w: current version wrong %d != %d", es.ErrWrongVersion, version, last)
|
||||
err = fmt.Errorf("%w: current version wrong %d != %d", ev.ErrWrongVersion, version, last)
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
@@ -411,7 +411,7 @@ func readStream(ctx context.Context, stream *wal.Log, index uint64) (event.Event
|
||||
b, err = stream.Read(index)
|
||||
if err != nil {
|
||||
if errors.Is(err, wal.ErrNotFound) || errors.Is(err, wal.ErrOutOfRange) {
|
||||
err = fmt.Errorf("%w: empty", es.ErrNotFound)
|
||||
err = fmt.Errorf("%w: empty", ev.ErrNotFound)
|
||||
}
|
||||
|
||||
span.RecordError(err)
|
||||
@@ -444,7 +444,7 @@ func readStreamN(ctx context.Context, stream *wal.Log, index ...uint64) (event.E
|
||||
b, err = stream.Read(idx)
|
||||
if err != nil {
|
||||
if errors.Is(err, wal.ErrNotFound) || errors.Is(err, wal.ErrOutOfRange) {
|
||||
err = fmt.Errorf("%w: empty", es.ErrNotFound)
|
||||
err = fmt.Errorf("%w: empty", ev.ErrNotFound)
|
||||
}
|
||||
|
||||
span.RecordError(err)
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
"github.com/sour-is/ev/pkg/locker"
|
||||
@@ -24,14 +24,14 @@ type memstore struct {
|
||||
state *locker.Locked[state]
|
||||
}
|
||||
|
||||
const AppendOnly = es.AppendOnly
|
||||
const AllEvents = es.AllEvents
|
||||
const AppendOnly = ev.AppendOnly
|
||||
const AllEvents = ev.AllEvents
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return es.Register(ctx, "mem", &memstore{})
|
||||
return ev.Register(ctx, "mem", &memstore{})
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*memstore)(nil)
|
||||
@@ -84,7 +84,7 @@ func (m *eventLog) Append(ctx context.Context, events event.Events, version uint
|
||||
|
||||
last := uint64(len(*stream))
|
||||
if version != AppendOnly && version != last {
|
||||
return fmt.Errorf("%w: current version wrong %d != %d", es.ErrWrongVersion, version, last)
|
||||
return fmt.Errorf("%w: current version wrong %d != %d", ev.ErrWrongVersion, version, last)
|
||||
}
|
||||
|
||||
for i := range events {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
)
|
||||
@@ -19,7 +19,7 @@ type projector struct {
|
||||
func New(_ context.Context, fns ...func(event.Event) []event.Event) *projector {
|
||||
return &projector{fns: fns}
|
||||
}
|
||||
func (p *projector) Apply(e *es.EventStore) {
|
||||
func (p *projector) Apply(e *ev.EventStore) {
|
||||
|
||||
up := e.Driver
|
||||
for up != nil {
|
||||
@@ -29,7 +29,7 @@ func (p *projector) Apply(e *es.EventStore) {
|
||||
return
|
||||
}
|
||||
|
||||
up = es.Unwrap(up)
|
||||
up = ev.Unwrap(up)
|
||||
}
|
||||
|
||||
p.up = e.Driver
|
||||
@@ -112,7 +112,7 @@ func (w *wrapper) Append(ctx context.Context, events event.Events, version uint6
|
||||
span.RecordError(err)
|
||||
continue
|
||||
}
|
||||
_, err = l.Append(ctx, event.NewEvents(e), es.AppendOnly)
|
||||
_, err = l.Append(ctx, event.NewEvents(e), ev.AppendOnly)
|
||||
span.RecordError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/driver/projecter"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
@@ -112,10 +112,10 @@ func TestProjecter(t *testing.T) {
|
||||
return mockEL, nil
|
||||
}
|
||||
|
||||
es.Init(ctx)
|
||||
es.Register(ctx, "mock", mock)
|
||||
ev.Init(ctx)
|
||||
ev.Register(ctx, "mock", mock)
|
||||
|
||||
es, err := es.Open(
|
||||
es, err := ev.Open(
|
||||
ctx,
|
||||
"mock:",
|
||||
projecter.New(ctx, projecter.DefaultProjection),
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
)
|
||||
@@ -18,7 +18,7 @@ func New() *resolvelinks {
|
||||
return &resolvelinks{}
|
||||
}
|
||||
|
||||
func (r *resolvelinks) Apply(es *es.EventStore) {
|
||||
func (r *resolvelinks) Apply(es *ev.EventStore) {
|
||||
r.up = es.Driver
|
||||
es.Driver = r
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (w *wrapper) Read(ctx context.Context, after int64, count int64) (event.Eve
|
||||
}
|
||||
ptr := ptrs[streamID]
|
||||
lis, err := d.ReadN(ctx, ids...)
|
||||
if err != nil && !errors.Is(err, es.ErrNotFound) {
|
||||
if err != nil && !errors.Is(err, ev.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
"github.com/sour-is/ev/pkg/locker"
|
||||
@@ -32,9 +32,9 @@ func New(ctx context.Context) *streamer {
|
||||
return &streamer{state: locker.New(&state{subscribers: map[string][]*subscription{}})}
|
||||
}
|
||||
|
||||
var _ es.Option = (*streamer)(nil)
|
||||
var _ ev.Option = (*streamer)(nil)
|
||||
|
||||
func (s *streamer) Apply(e *es.EventStore) {
|
||||
func (s *streamer) Apply(e *ev.EventStore) {
|
||||
s.up = e.Driver
|
||||
e.Driver = s
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func (s *streamer) Subscribe(ctx context.Context, streamID string, start int64)
|
||||
sub := &subscription{topic: streamID, events: events}
|
||||
sub.position = locker.New(&position{
|
||||
idx: start,
|
||||
size: es.AllEvents,
|
||||
size: ev.AllEvents,
|
||||
})
|
||||
sub.unsub = s.delete(streamID, sub)
|
||||
|
||||
@@ -232,7 +232,7 @@ func (s *subscription) Recv(ctx context.Context) <-chan bool {
|
||||
_, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
if position.size == es.AllEvents {
|
||||
if position.size == ev.AllEvents {
|
||||
return nil
|
||||
}
|
||||
if position.size == 0 {
|
||||
|
||||
415
pkg/es/es.go
415
pkg/es/es.go
@@ -1,415 +0,0 @@
|
||||
// package es implements an event store and drivers for extending its functionality.
|
||||
package es
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric/instrument/syncint64"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/es/driver"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
"github.com/sour-is/ev/pkg/locker"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
drivers map[string]driver.Driver
|
||||
}
|
||||
|
||||
const AppendOnly = ^uint64(0)
|
||||
const AllEvents = int64(AppendOnly >> 1)
|
||||
|
||||
var (
|
||||
drivers = locker.New(&config{drivers: make(map[string]driver.Driver)})
|
||||
)
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
m := lg.Meter(ctx)
|
||||
|
||||
var err, errs error
|
||||
Mes_open, err = m.SyncInt64().Counter("es_open")
|
||||
errs = multierr.Append(errs, err)
|
||||
|
||||
Mes_read, err = m.SyncInt64().Counter("es_read")
|
||||
errs = multierr.Append(errs, err)
|
||||
|
||||
Mes_load, err = m.SyncInt64().Counter("es_load")
|
||||
errs = multierr.Append(errs, err)
|
||||
|
||||
Mes_save, err = m.SyncInt64().Counter("es_save")
|
||||
errs = multierr.Append(errs, err)
|
||||
|
||||
Mes_append, err = m.SyncInt64().Counter("es_append")
|
||||
errs = multierr.Append(errs, err)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func Register(ctx context.Context, name string, d driver.Driver) error {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
return drivers.Modify(ctx, func(ctx context.Context, c *config) error {
|
||||
if _, set := c.drivers[name]; set {
|
||||
return fmt.Errorf("driver %s already set", name)
|
||||
}
|
||||
c.drivers[name] = d
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type EventStore struct {
|
||||
driver.Driver
|
||||
}
|
||||
|
||||
var (
|
||||
Mes_open syncint64.Counter
|
||||
Mes_read syncint64.Counter
|
||||
Mes_load syncint64.Counter
|
||||
Mes_save syncint64.Counter
|
||||
Mes_append syncint64.Counter
|
||||
)
|
||||
|
||||
func Open(ctx context.Context, dsn string, options ...Option) (*EventStore, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(attribute.String("dsn", dsn))
|
||||
|
||||
name, _, ok := strings.Cut(dsn, ":")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: no scheme", ErrNoDriver)
|
||||
}
|
||||
|
||||
var d driver.Driver
|
||||
c, err := drivers.Copy(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d, ok = c.drivers[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%w: %s not registered", ErrNoDriver, name)
|
||||
}
|
||||
|
||||
conn, err := d.Open(ctx, dsn)
|
||||
|
||||
es := &EventStore{Driver: conn}
|
||||
es.Option(options...)
|
||||
|
||||
Mes_open.Add(ctx, 1)
|
||||
|
||||
return es, err
|
||||
}
|
||||
|
||||
func (es *EventStore) Option(options ...Option) {
|
||||
for _, o := range options {
|
||||
o.Apply(es)
|
||||
}
|
||||
}
|
||||
|
||||
type Option interface {
|
||||
Apply(*EventStore)
|
||||
}
|
||||
|
||||
func (es *EventStore) Save(ctx context.Context, agg event.Aggregate) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
events := agg.Events(true)
|
||||
if len(events) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("agg.type", event.TypeOf(agg)),
|
||||
attribute.String("agg.streamID", agg.StreamID()),
|
||||
attribute.Int64("agg.version", int64(agg.StreamVersion())),
|
||||
)
|
||||
|
||||
l, err := es.EventLog(ctx, agg.StreamID())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count, err := l.Append(ctx, events, agg.StreamVersion())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
Mes_save.Add(ctx, int64(count))
|
||||
|
||||
agg.Commit()
|
||||
return count, err
|
||||
}
|
||||
func (es *EventStore) Load(ctx context.Context, agg event.Aggregate) error {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("agg.type", event.TypeOf(agg)),
|
||||
attribute.String("agg.streamID", agg.StreamID()),
|
||||
)
|
||||
|
||||
l, err := es.Driver.EventLog(ctx, agg.StreamID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
events, err := l.Read(ctx, 0, AllEvents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Mes_load.Add(ctx, events.Count())
|
||||
event.Append(agg, events...)
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.Int64("agg.version", int64(agg.StreamVersion())),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
func (es *EventStore) Read(ctx context.Context, streamID string, after, count int64) (event.Events, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("streamID", streamID),
|
||||
attribute.Int64("after", after),
|
||||
attribute.Int64("count", count),
|
||||
)
|
||||
|
||||
l, err := es.Driver.EventLog(ctx, streamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := l.Read(ctx, after, count)
|
||||
Mes_read.Add(ctx, events.Count())
|
||||
|
||||
return events, err
|
||||
}
|
||||
func (es *EventStore) ReadN(ctx context.Context, streamID string, index ...uint64) (event.Events, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
lis := make([]int64, len(index))
|
||||
for i, j := range index {
|
||||
lis[i] = int64(j)
|
||||
}
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("streamID", streamID),
|
||||
attribute.Int64Slice("index", lis),
|
||||
)
|
||||
|
||||
l, err := es.Driver.EventLog(ctx, streamID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := l.ReadN(ctx, index...)
|
||||
Mes_read.Add(ctx, events.Count())
|
||||
|
||||
return events, err
|
||||
}
|
||||
func (es *EventStore) Append(ctx context.Context, streamID string, events event.Events) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
Mes_append.Add(ctx, 1)
|
||||
span.SetAttributes(
|
||||
attribute.String("ev.streamID", streamID),
|
||||
)
|
||||
|
||||
l, err := es.Driver.EventLog(ctx, streamID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return l.Append(ctx, events, AppendOnly)
|
||||
}
|
||||
func (es *EventStore) FirstIndex(ctx context.Context, streamID string) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("ev.streamID", streamID),
|
||||
)
|
||||
|
||||
l, err := es.Driver.EventLog(ctx, streamID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return l.FirstIndex(ctx)
|
||||
}
|
||||
func (es *EventStore) LastIndex(ctx context.Context, streamID string) (uint64, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("ev.streamID", streamID),
|
||||
)
|
||||
|
||||
l, err := es.Driver.EventLog(ctx, streamID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return l.LastIndex(ctx)
|
||||
}
|
||||
func (es *EventStore) EventStream() driver.EventStream {
|
||||
if es == nil {
|
||||
return nil
|
||||
}
|
||||
d := es.Driver
|
||||
for d != nil {
|
||||
if d, ok := d.(driver.EventStream); ok {
|
||||
return d
|
||||
}
|
||||
|
||||
d = Unwrap(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (es *EventStore) Truncate(ctx context.Context, streamID string, index int64) error {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
up, err := es.Driver.EventLog(ctx, streamID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for up != nil {
|
||||
if up, ok := up.(driver.EventLogWithTruncate); ok {
|
||||
err = up.Truncate(ctx, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
up = Unwrap(up)
|
||||
}
|
||||
|
||||
return ErrNoDriver
|
||||
}
|
||||
|
||||
func Unwrap[T any](t T) T {
|
||||
if unwrap, ok := any(t).(interface{ Unwrap() T }); ok {
|
||||
return unwrap.Unwrap()
|
||||
} else {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
}
|
||||
|
||||
var ErrNoDriver = errors.New("no driver")
|
||||
var ErrWrongVersion = errors.New("wrong version")
|
||||
var ErrShouldExist = event.ErrShouldExist
|
||||
var ErrShouldNotExist = event.ErrShouldNotExist
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type PA[T any] interface {
|
||||
event.Aggregate
|
||||
*T
|
||||
}
|
||||
type PE[T any] interface {
|
||||
event.Event
|
||||
*T
|
||||
}
|
||||
|
||||
// Create uses fn to create a new aggregate and store in db.
|
||||
func Create[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string, fn func(context.Context, T) error) (agg T, err error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
agg = new(A)
|
||||
agg.SetStreamID(streamID)
|
||||
span.SetAttributes(
|
||||
attribute.String("agg.streamID", streamID),
|
||||
)
|
||||
|
||||
if err = es.Load(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = event.NotExists(agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = fn(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var i uint64
|
||||
if i, err = es.Save(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
span.AddEvent(fmt.Sprint("wrote events = ", i))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Update uses fn to update an exsisting aggregate and store in db.
|
||||
func Update[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string, fn func(context.Context, T) error) (agg T, err error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
agg = new(A)
|
||||
agg.SetStreamID(streamID)
|
||||
span.SetAttributes(
|
||||
attribute.String("agg.streamID", streamID),
|
||||
)
|
||||
|
||||
if err = es.Load(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = event.ShouldExist(agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = fn(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = es.Save(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Update uses fn to update an exsisting aggregate and store in db.
|
||||
func Upsert[A any, T PA[A]](ctx context.Context, es *EventStore, streamID string, fn func(context.Context, T) error) (agg T, err error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
agg = new(A)
|
||||
agg.SetStreamID(streamID)
|
||||
span.SetAttributes(
|
||||
attribute.String("agg.streamID", streamID),
|
||||
)
|
||||
|
||||
if err = es.Load(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = fn(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = event.ShouldExist(agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = es.Save(ctx, agg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
package es_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"github.com/sour-is/ev/app/peerfinder"
|
||||
"github.com/sour-is/ev/pkg/es"
|
||||
memstore "github.com/sour-is/ev/pkg/es/driver/mem-store"
|
||||
"github.com/sour-is/ev/pkg/es/driver/projecter"
|
||||
resolvelinks "github.com/sour-is/ev/pkg/es/driver/resolve-links"
|
||||
"github.com/sour-is/ev/pkg/es/driver/streamer"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
)
|
||||
|
||||
var (
|
||||
_ event.Event = (*ValueSet)(nil)
|
||||
_ event.Aggregate = (*Thing)(nil)
|
||||
)
|
||||
|
||||
type Thing struct {
|
||||
Name string
|
||||
Value string
|
||||
|
||||
event.AggregateRoot
|
||||
}
|
||||
|
||||
func (a *Thing) ApplyEvent(lis ...event.Event) {
|
||||
for _, e := range lis {
|
||||
switch e := e.(type) {
|
||||
case *ValueSet:
|
||||
a.Value = e.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
func (a *Thing) OnSetValue(value string) error {
|
||||
event.Raise(a, &ValueSet{Value: value})
|
||||
return nil
|
||||
}
|
||||
|
||||
type ValueSet struct {
|
||||
Value string
|
||||
|
||||
eventMeta event.Meta
|
||||
}
|
||||
|
||||
func (e *ValueSet) EventMeta() event.Meta {
|
||||
if e == nil {
|
||||
return event.Meta{}
|
||||
}
|
||||
return e.eventMeta
|
||||
}
|
||||
func (e *ValueSet) SetEventMeta(eventMeta event.Meta) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.eventMeta = eventMeta
|
||||
}
|
||||
func (e *ValueSet) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(e)
|
||||
}
|
||||
func (e *ValueSet) UnmarshalBinary(b []byte) error {
|
||||
return json.Unmarshal(b, e)
|
||||
}
|
||||
|
||||
func TestES(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
err := event.Register(ctx, &ValueSet{})
|
||||
is.NoErr(err)
|
||||
|
||||
{
|
||||
store, err := es.Open(ctx, "mem")
|
||||
is.True(errors.Is(err, es.ErrNoDriver))
|
||||
is.True(store.EventStream() == nil)
|
||||
}
|
||||
|
||||
{
|
||||
_, err := es.Open(ctx, "bogo:")
|
||||
is.True(errors.Is(err, es.ErrNoDriver))
|
||||
}
|
||||
|
||||
store, err := es.Open(ctx, "mem:", streamer.New(ctx), projecter.New(ctx))
|
||||
is.NoErr(err)
|
||||
|
||||
thing := &Thing{Name: "time"}
|
||||
err = store.Load(ctx, thing)
|
||||
is.NoErr(err)
|
||||
|
||||
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
||||
|
||||
err = thing.OnSetValue(time.Now().String())
|
||||
is.NoErr(err)
|
||||
|
||||
thing.SetStreamID("thing-time")
|
||||
i, err := store.Save(ctx, thing)
|
||||
is.NoErr(err)
|
||||
|
||||
t.Log(thing.StreamVersion(), thing.Name, thing.Value)
|
||||
t.Log("Wrote: ", i)
|
||||
|
||||
i, err = store.Append(ctx, "thing-time", event.NewEvents(&ValueSet{Value: "xxx"}))
|
||||
is.NoErr(err)
|
||||
is.Equal(i, uint64(1))
|
||||
|
||||
events, err := store.Read(ctx, "thing-time", -1, -11)
|
||||
is.NoErr(err)
|
||||
|
||||
for i, e := range events {
|
||||
t.Logf("event %d %d - %v\n", i, e.EventMeta().Position, e)
|
||||
}
|
||||
|
||||
first, err := store.FirstIndex(ctx, "thing-time")
|
||||
is.NoErr(err)
|
||||
is.Equal(first, uint64(1))
|
||||
|
||||
last, err := store.LastIndex(ctx, "thing-time")
|
||||
is.NoErr(err)
|
||||
is.Equal(last, uint64(2))
|
||||
|
||||
stream := store.EventStream()
|
||||
is.True(stream != nil)
|
||||
}
|
||||
|
||||
func TestESOperations(t *testing.T) {
|
||||
is := is.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
store, err := es.Open(ctx, "mem:", streamer.New(ctx), projecter.New(ctx))
|
||||
is.NoErr(err)
|
||||
|
||||
thing, err := es.Create(ctx, store, "thing-1", func(ctx context.Context, agg *Thing) error {
|
||||
return agg.OnSetValue("foo")
|
||||
})
|
||||
|
||||
is.NoErr(err)
|
||||
is.Equal(thing.Version(), uint64(1))
|
||||
is.Equal(thing.Value, "foo")
|
||||
|
||||
thing, err = es.Update(ctx, store, "thing-1", func(ctx context.Context, agg *Thing) error {
|
||||
return agg.OnSetValue("bar")
|
||||
})
|
||||
|
||||
is.NoErr(err)
|
||||
is.Equal(thing.Version(), uint64(2))
|
||||
is.Equal(thing.Value, "bar")
|
||||
|
||||
thing, err = es.Upsert(ctx, store, "thing-2", func(ctx context.Context, agg *Thing) error {
|
||||
return agg.OnSetValue("bin")
|
||||
})
|
||||
|
||||
is.NoErr(err)
|
||||
is.Equal(thing.Version(), uint64(1))
|
||||
is.Equal(thing.Value, "bin")
|
||||
|
||||
thing, err = es.Upsert(ctx, store, "thing-2", func(ctx context.Context, agg *Thing) error {
|
||||
return agg.OnSetValue("baz")
|
||||
})
|
||||
|
||||
is.NoErr(err)
|
||||
is.Equal(thing.Version(), uint64(2))
|
||||
is.Equal(thing.Value, "baz")
|
||||
|
||||
}
|
||||
|
||||
func TestUnwrap(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
err := errors.New("foo")
|
||||
werr := fmt.Errorf("wrap: %w", err)
|
||||
|
||||
is.Equal(es.Unwrap(werr), err)
|
||||
is.Equal(es.Unwrap("test"), "")
|
||||
}
|
||||
|
||||
func TestUnwrapProjector(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
ctx, stop := context.WithCancel(context.Background())
|
||||
defer stop()
|
||||
|
||||
es, err := es.Open(
|
||||
ctx,
|
||||
"mem:",
|
||||
resolvelinks.New(),
|
||||
streamer.New(ctx),
|
||||
projecter.New(
|
||||
ctx,
|
||||
projecter.DefaultProjection,
|
||||
peerfinder.Projector,
|
||||
),
|
||||
)
|
||||
is.NoErr(err)
|
||||
|
||||
stream := es.EventStream()
|
||||
is.True(stream != nil)
|
||||
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
ctx, stop := context.WithCancel(context.Background())
|
||||
defer stop()
|
||||
|
||||
err := multierr.Combine(
|
||||
es.Init(ctx),
|
||||
event.Init(ctx),
|
||||
memstore.Init(ctx),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
m.Run()
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sour-is/ev"
|
||||
"github.com/sour-is/ev/internal/lg"
|
||||
"github.com/sour-is/ev/pkg/es/event"
|
||||
"github.com/sour-is/ev/pkg/gql"
|
||||
@@ -23,13 +24,17 @@ type contextKey struct {
|
||||
|
||||
var esKey = contextKey{"event-store"}
|
||||
|
||||
type EventStore struct {
|
||||
*ev.EventStore
|
||||
}
|
||||
|
||||
func (es *EventStore) IsResolver() {}
|
||||
func (es *EventStore) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) {
|
||||
ctx, span := lg.Span(ctx)
|
||||
defer span.End()
|
||||
|
||||
lis, err := es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30))
|
||||
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||
if err != nil && !errors.Is(err, ev.ErrNotFound) {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user