zitadel/internal/eventstore/v2/eventstore.go

217 lines
6.3 KiB
Go
Raw Normal View History

2020-09-24 08:52:10 +02:00
package eventstore
import (
"context"
2020-10-05 22:02:59 +02:00
"encoding/json"
"reflect"
2020-09-30 10:00:05 +02:00
"sync"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v2/repository"
2020-09-24 08:52:10 +02:00
)
2020-10-06 20:19:56 +02:00
//Eventstore abstracts all functions needed to store valid events
// and filters the stored events
2020-09-30 10:00:05 +02:00
type Eventstore struct {
2020-10-27 16:03:17 +01:00
repo repository.Repository
interceptorMutex sync.Mutex
eventInterceptors map[EventType]eventTypeInterceptors
2020-09-30 10:00:05 +02:00
}
type eventTypeInterceptors struct {
2020-11-06 17:25:07 +01:00
eventMapper func(*repository.Event) (EventReader, error)
2020-10-23 16:16:46 +02:00
}
func NewEventstore(repo repository.Repository) *Eventstore {
return &Eventstore{
2020-10-27 16:03:17 +01:00
repo: repo,
eventInterceptors: map[EventType]eventTypeInterceptors{},
interceptorMutex: sync.Mutex{},
2020-10-23 16:16:46 +02:00
}
2020-09-30 10:00:05 +02:00
}
//Health checks if the eventstore can properly work
// It checks if the repository can serve load
func (es *Eventstore) Health(ctx context.Context) error {
return es.repo.Health(ctx)
}
2020-11-27 13:29:35 +01:00
//PushAggregate pushes the aggregate and reduces the new events on the aggregate
func (es *Eventstore) PushAggregate(ctx context.Context, writeModel queryReducer, aggregate aggregater) error {
events, err := es.PushAggregates(ctx, aggregate)
if err != nil {
return err
}
writeModel.AppendEvents(events...)
return writeModel.Reduce()
2020-10-06 20:19:56 +02:00
}
2020-09-30 10:00:05 +02:00
//PushAggregates maps the events of all aggregates to an eventstore event
// based on the pushMapper
2020-11-06 13:47:27 +01:00
func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...aggregater) ([]EventReader, error) {
2020-09-30 10:00:05 +02:00
events, err := es.aggregatesToEvents(aggregates)
if err != nil {
return nil, err
}
err = es.repo.Push(ctx, events...)
if err != nil {
return nil, err
}
return es.mapEvents(events)
}
func (es *Eventstore) aggregatesToEvents(aggregates []aggregater) ([]*repository.Event, error) {
events := make([]*repository.Event, 0, len(aggregates))
for _, aggregate := range aggregates {
var previousEvent *repository.Event
for _, event := range aggregate.Events() {
2020-10-05 22:02:59 +02:00
data, err := eventData(event)
if err != nil {
return nil, err
}
2020-09-30 10:00:05 +02:00
events = append(events, &repository.Event{
2020-10-21 19:00:41 +02:00
AggregateID: aggregate.ID(),
AggregateType: repository.AggregateType(aggregate.Type()),
ResourceOwner: aggregate.ResourceOwner(),
EditorService: event.EditorService(),
EditorUser: event.EditorUser(),
Type: repository.EventType(event.Type()),
Version: repository.Version(aggregate.Version()),
PreviousEvent: previousEvent,
PreviousSequence: aggregate.PreviousSequence(),
Data: data,
CheckPreviousSequence: event.CheckPrevious(),
2020-09-30 10:00:05 +02:00
})
previousEvent = events[len(events)-1]
}
}
return events, nil
}
//FilterEvents filters the stored events based on the searchQuery
// and maps the events to the defined event structs
2020-12-01 14:44:19 +01:00
func (es *Eventstore) FilterEvents(ctx context.Context, queryFactory *SearchQueryBuilder) ([]EventReader, error) {
2020-10-06 21:28:09 +02:00
query, err := queryFactory.build()
2020-09-30 10:00:05 +02:00
if err != nil {
return nil, err
}
events, err := es.repo.Filter(ctx, query)
if err != nil {
return nil, err
}
return es.mapEvents(events)
}
2020-11-06 13:47:27 +01:00
func (es *Eventstore) mapEvents(events []*repository.Event) (mappedEvents []EventReader, err error) {
mappedEvents = make([]EventReader, len(events))
2020-09-30 10:00:05 +02:00
es.interceptorMutex.Lock()
defer es.interceptorMutex.Unlock()
for i, event := range events {
2020-10-27 16:03:17 +01:00
interceptors, ok := es.eventInterceptors[EventType(event.Type)]
2020-10-23 16:16:46 +02:00
if !ok || interceptors.eventMapper == nil {
mappedEvents[i] = BaseEventFromRepo(event)
2020-11-26 13:14:07 +01:00
//TODO: return error if unable to map event
continue
// return nil, errors.ThrowPreconditionFailed(nil, "V2-usujB", "event mapper not defined")
2020-09-30 10:00:05 +02:00
}
2020-10-23 16:16:46 +02:00
mappedEvents[i], err = interceptors.eventMapper(event)
2020-09-30 10:00:05 +02:00
if err != nil {
return nil, err
}
}
return mappedEvents, nil
}
2020-10-06 20:19:56 +02:00
type reducer interface {
//Reduce handles the events of the internal events list
// it only appends the newly added events
Reduce() error
//AppendEvents appends the passed events to an internal list of events
2020-11-25 14:12:44 +01:00
AppendEvents(...EventReader)
2020-09-30 10:00:05 +02:00
}
2020-10-06 20:19:56 +02:00
//FilterToReducer filters the events based on the search query, appends all events to the reducer and calls it's reduce function
2020-12-01 14:44:19 +01:00
func (es *Eventstore) FilterToReducer(ctx context.Context, searchQuery *SearchQueryBuilder, r reducer) error {
2020-09-30 10:00:05 +02:00
events, err := es.FilterEvents(ctx, searchQuery)
if err != nil {
return err
}
2020-11-25 14:12:44 +01:00
r.AppendEvents(events...)
2020-09-30 10:00:05 +02:00
2020-10-06 20:19:56 +02:00
return r.Reduce()
2020-09-30 10:00:05 +02:00
}
2020-10-06 20:19:56 +02:00
//LatestSequence filters the latest sequence for the given search query
2020-12-01 14:44:19 +01:00
func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (uint64, error) {
2020-10-06 21:28:09 +02:00
query, err := queryFactory.build()
2020-10-05 22:02:59 +02:00
if err != nil {
return 0, err
}
return es.repo.LatestSequence(ctx, query)
2020-09-30 10:00:05 +02:00
}
2020-11-23 11:36:58 +01:00
type queryReducer interface {
reducer
//Query returns the SearchQueryFactory for the events needed in reducer
2020-12-01 14:44:19 +01:00
Query() *SearchQueryBuilder
2020-11-23 11:36:58 +01:00
}
//FilterToQueryReducer filters the events based on the search query of the query function,
// appends all events to the reducer and calls it's reduce function
func (es *Eventstore) FilterToQueryReducer(ctx context.Context, r queryReducer) error {
events, err := es.FilterEvents(ctx, r.Query())
if err != nil {
return err
}
2020-11-25 14:12:44 +01:00
r.AppendEvents(events...)
2020-11-23 11:36:58 +01:00
return r.Reduce()
}
2020-10-06 20:19:56 +02:00
//RegisterFilterEventMapper registers a function for mapping an eventstore event to an event
2020-11-06 17:25:07 +01:00
func (es *Eventstore) RegisterFilterEventMapper(eventType EventType, mapper func(*repository.Event) (EventReader, error)) *Eventstore {
2020-10-27 15:42:18 +01:00
if mapper == nil || eventType == "" {
return es
}
2020-09-30 10:00:05 +02:00
es.interceptorMutex.Lock()
defer es.interceptorMutex.Unlock()
2020-10-27 16:03:17 +01:00
interceptor := es.eventInterceptors[eventType]
2020-10-23 16:16:46 +02:00
interceptor.eventMapper = mapper
2020-10-27 16:03:17 +01:00
es.eventInterceptors[eventType] = interceptor
2020-09-30 10:00:05 +02:00
2020-10-23 16:16:46 +02:00
return es
2020-09-30 10:00:05 +02:00
}
2020-11-06 13:47:27 +01:00
func eventData(event EventPusher) ([]byte, error) {
2020-10-05 22:02:59 +02:00
switch data := event.Data().(type) {
case nil:
return nil, nil
case []byte:
if json.Valid(data) {
return data, nil
}
return nil, errors.ThrowInvalidArgument(nil, "V2-6SbbS", "data bytes are not json")
}
dataType := reflect.TypeOf(event.Data())
if dataType.Kind() == reflect.Ptr {
dataType = dataType.Elem()
}
if dataType.Kind() == reflect.Struct {
dataBytes, err := json.Marshal(event.Data())
if err != nil {
return nil, errors.ThrowInvalidArgument(err, "V2-xG87M", "could not marhsal data")
}
return dataBytes, nil
}
return nil, errors.ThrowInvalidArgument(nil, "V2-91NRm", "wrong type of event data")
}