feat(eventstore): sdk (#39)

* sdk

* fix(sdk): return correct error type

* AppendEventError instead of Aggregater error

* fix(tests): tests

* fix(tests): wantErr to is error func
This commit is contained in:
Silvan 2020-04-07 18:36:37 +02:00 committed by GitHub
parent 970586dfc9
commit 191690d905
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 524 additions and 234 deletions

View File

@ -17,11 +17,11 @@ type AlreadyExistsError struct {
} }
func ThrowAlreadyExists(parent error, id, message string) error { func ThrowAlreadyExists(parent error, id, message string) error {
return &AlreadyExistsError{createCaosError(parent, id, message)} return &AlreadyExistsError{CreateCaosError(parent, id, message)}
} }
func ThrowAlreadyExistsf(parent error, id, format string, a ...interface{}) error { func ThrowAlreadyExistsf(parent error, id, format string, a ...interface{}) error {
return &AlreadyExistsError{createCaosError(parent, id, fmt.Sprintf(format, a...))} return &AlreadyExistsError{CreateCaosError(parent, id, fmt.Sprintf(format, a...))}
} }
func (err *AlreadyExistsError) IsAlreadyExists() {} func (err *AlreadyExistsError) IsAlreadyExists() {}

View File

@ -13,10 +13,10 @@ type CaosError struct {
} }
func ThrowError(parent error, id, message string) error { func ThrowError(parent error, id, message string) error {
return createCaosError(parent, id, message) return CreateCaosError(parent, id, message)
} }
func createCaosError(parent error, id, message string) *CaosError { func CreateCaosError(parent error, id, message string) *CaosError {
return &CaosError{ return &CaosError{
Parent: parent, Parent: parent,
ID: id, ID: id,

View File

@ -19,7 +19,7 @@ type DeadlineExceededError struct {
} }
func ThrowDeadlineExceeded(parent error, id, message string) error { func ThrowDeadlineExceeded(parent error, id, message string) error {
return &DeadlineExceededError{createCaosError(parent, id, message)} return &DeadlineExceededError{CreateCaosError(parent, id, message)}
} }
func ThrowDeadlineExceededf(parent error, id, format string, a ...interface{}) error { func ThrowDeadlineExceededf(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type {{.ErrorName}}Error struct {
} }
func Throw{{.ErrorName}}(parent error, id, message string) error { func Throw{{.ErrorName}}(parent error, id, message string) error {
return &{{.ErrorName}}Error{createCaosError(parent, id, message)} return &{{.ErrorName}}Error{CreateCaosError(parent, id, message)}
} }
func Throw{{.ErrorName}}f(parent error, id, format string, a ...interface{}) error { func Throw{{.ErrorName}}f(parent error, id, format string, a ...interface{}) error {

View File

@ -12,7 +12,7 @@ import (
func Test{{.ErrorName}}Error(t *testing.T) { func Test{{.ErrorName}}Error(t *testing.T) {
var err interface{} var err interface{}
err = new(caos_errs.{{.ErrorName}}Error) err = new(caos_errs.{{.ErrorName}}Error)
_, ok := err.(caos_errs.{{.ErrorName}}) _, ok := err.(*caos_errs.{{.ErrorName}})
assert.True(t, ok) assert.True(t, ok)
} }

View File

@ -19,7 +19,7 @@ type InternalError struct {
} }
func ThrowInternal(parent error, id, message string) error { func ThrowInternal(parent error, id, message string) error {
return &InternalError{createCaosError(parent, id, message)} return &InternalError{CreateCaosError(parent, id, message)}
} }
func ThrowInternalf(parent error, id, format string, a ...interface{}) error { func ThrowInternalf(parent error, id, format string, a ...interface{}) error {

View File

@ -17,7 +17,7 @@ type InvalidArgumentError struct {
} }
func ThrowInvalidArgument(parent error, id, message string) error { func ThrowInvalidArgument(parent error, id, message string) error {
return &InvalidArgumentError{createCaosError(parent, id, message)} return &InvalidArgumentError{CreateCaosError(parent, id, message)}
} }
func ThrowInvalidArgumentf(parent error, id, format string, a ...interface{}) error { func ThrowInvalidArgumentf(parent error, id, format string, a ...interface{}) error {

View File

@ -12,7 +12,7 @@ type NotFoundError struct {
} }
func ThrowNotFound(parent error, id, message string) error { func ThrowNotFound(parent error, id, message string) error {
return &NotFoundError{createCaosError(parent, id, message)} return &NotFoundError{CreateCaosError(parent, id, message)}
} }
func ThrowNotFoundf(parent error, id, format string, a ...interface{}) error { func ThrowNotFoundf(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type PermissionDeniedError struct {
} }
func ThrowPermissionDenied(parent error, id, message string) error { func ThrowPermissionDenied(parent error, id, message string) error {
return &PermissionDeniedError{createCaosError(parent, id, message)} return &PermissionDeniedError{CreateCaosError(parent, id, message)}
} }
func ThrowPermissionDeniedf(parent error, id, format string, a ...interface{}) error { func ThrowPermissionDeniedf(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type PreconditionFailedError struct {
} }
func ThrowPreconditionFailed(parent error, id, message string) error { func ThrowPreconditionFailed(parent error, id, message string) error {
return &PreconditionFailedError{createCaosError(parent, id, message)} return &PreconditionFailedError{CreateCaosError(parent, id, message)}
} }
func ThrowPreconditionFailedf(parent error, id, format string, a ...interface{}) error { func ThrowPreconditionFailedf(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type UnauthenticatedError struct {
} }
func ThrowUnauthenticated(parent error, id, message string) error { func ThrowUnauthenticated(parent error, id, message string) error {
return &UnauthenticatedError{createCaosError(parent, id, message)} return &UnauthenticatedError{CreateCaosError(parent, id, message)}
} }
func ThrowUnauthenticatedf(parent error, id, format string, a ...interface{}) error { func ThrowUnauthenticatedf(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type UnavailableError struct {
} }
func ThrowUnavailable(parent error, id, message string) error { func ThrowUnavailable(parent error, id, message string) error {
return &UnavailableError{createCaosError(parent, id, message)} return &UnavailableError{CreateCaosError(parent, id, message)}
} }
func ThrowUnavailablef(parent error, id, format string, a ...interface{}) error { func ThrowUnavailablef(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type UnimplementedError struct {
} }
func ThrowUnimplemented(parent error, id, message string) error { func ThrowUnimplemented(parent error, id, message string) error {
return &UnimplementedError{createCaosError(parent, id, message)} return &UnimplementedError{CreateCaosError(parent, id, message)}
} }
func ThrowUnimplementedf(parent error, id, format string, a ...interface{}) error { func ThrowUnimplementedf(parent error, id, format string, a ...interface{}) error {

View File

@ -19,7 +19,7 @@ type UnknownError struct {
} }
func ThrowUnknown(parent error, id, message string) error { func ThrowUnknown(parent error, id, message string) error {
return &UnknownError{createCaosError(parent, id, message)} return &UnknownError{CreateCaosError(parent, id, message)}
} }
func ThrowUnknownf(parent error, id, format string, a ...interface{}) error { func ThrowUnknownf(parent error, id, format string, a ...interface{}) error {

View File

@ -33,7 +33,7 @@ func (es *eventstore) PushAggregates(ctx context.Context, aggregates ...*models.
} }
for _, event := range aggregate.Events { for _, event := range aggregate.Events {
if err = event.Validate(); err != nil { if err = event.Validate(); err != nil {
return err return errors.ThrowInvalidArgument(err, "EVENT-tzIhl", "validate event failed")
} }
} }
} }
@ -42,12 +42,6 @@ func (es *eventstore) PushAggregates(ctx context.Context, aggregates ...*models.
return err return err
} }
for _, aggregate := range aggregates {
if aggregate.Appender != nil {
aggregate.Appender(aggregate.Events...)
}
}
return nil return nil
} }

View File

@ -24,11 +24,8 @@ type Aggregate struct {
editorUser string editorUser string
resourceOwner string resourceOwner string
Events []*Event Events []*Event
Appender appender
} }
type appender func(...*Event)
func (a *Aggregate) AppendEvent(typ EventType, payload interface{}) (*Aggregate, error) { func (a *Aggregate) AppendEvent(typ EventType, payload interface{}) (*Aggregate, error) {
if string(typ) == "" { if string(typ) == "" {
return a, errors.ThrowInvalidArgument(nil, "MODEL-TGoCb", "no event type") return a, errors.ThrowInvalidArgument(nil, "MODEL-TGoCb", "no event type")
@ -81,8 +78,3 @@ func (a *Aggregate) Validate() error {
return nil return nil
} }
func (a *Aggregate) SetAppender(appendFn appender) *Aggregate {
a.Appender = appendFn
return a
}

View File

@ -0,0 +1,36 @@
package sdk
import (
"fmt"
"github.com/caos/zitadel/internal/errors"
)
var (
_ AppendEventError = (*appendEventError)(nil)
_ errors.Error = (*appendEventError)(nil)
)
type AppendEventError interface {
error
IsAppendEventError()
}
type appendEventError struct {
*errors.CaosError
}
func ThrowAppendEventError(parent error, id, message string) error {
return &appendEventError{errors.CreateCaosError(parent, id, message)}
}
func ThrowAggregaterf(parent error, id, format string, a ...interface{}) error {
return ThrowAppendEventError(parent, id, fmt.Sprintf(format, a...))
}
func (err *appendEventError) IsAppendEventError() {}
func IsAppendEventError(err error) bool {
_, ok := err.(AppendEventError)
return ok
}

View File

@ -0,0 +1,31 @@
package sdk
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAppendEventError(t *testing.T) {
var err interface{}
err = new(appendEventError)
_, ok := err.(*appendEventError)
assert.True(t, ok)
}
func TestThrowAppendEventErrorf(t *testing.T) {
err := ThrowAggregaterf(nil, "id", "msg")
_, ok := err.(*appendEventError)
assert.True(t, ok)
}
func TestIsAppendEventError(t *testing.T) {
err := ThrowAppendEventError(nil, "id", "msg")
ok := IsAppendEventError(err)
assert.True(t, ok)
err = errors.New("i am found")
ok = IsAppendEventError(err)
assert.False(t, ok)
}

View File

@ -0,0 +1,72 @@
package sdk
import (
"context"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
type filterFunc func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error)
type appendFunc func(...*es_models.Event) error
type aggregateFunc func(context.Context) (*es_models.Aggregate, error)
type pushFunc func(context.Context, ...*es_models.Aggregate) error
func Filter(ctx context.Context, filter filterFunc, appender appendFunc, query *es_models.SearchQuery) error {
events, err := filter(ctx, query)
if err != nil {
return err
}
if len(events) == 0 {
return errors.ThrowNotFound(nil, "EVENT-8due3", "no events found")
}
err = appender(events...)
if err != nil{
return ThrowAppendEventError(err, "SDK-awiWK", "appender failed")
}
return nil
}
// Push creates the aggregates from aggregater
// and pushes the aggregates to the given pushFunc
// the given events are appended by the appender
func Push(ctx context.Context, push pushFunc, appender appendFunc, aggregaters ...aggregateFunc) (err error) {
if len(aggregaters) < 1 {
return errors.ThrowPreconditionFailed(nil, "SDK-q9wjp", "no aggregaters passed")
}
aggregates, err := makeAggregates(ctx, aggregaters)
if err != nil {
return err
}
err = push(ctx, aggregates...)
if err != nil {
return err
}
return appendAggregates(appender, aggregates)
}
func appendAggregates(appender appendFunc, aggregates []*models.Aggregate) error {
for _, aggregate := range aggregates {
err := appender(aggregate.Events...)
if err != nil {
return ThrowAppendEventError(err, "SDK-o6kzK", "aggregator failed")
}
}
return nil
}
func makeAggregates(ctx context.Context, aggregaters []aggregateFunc) (aggregates []*models.Aggregate, err error) {
aggregates = make([]*models.Aggregate, len(aggregaters))
for i, aggregater := range aggregaters {
aggregates[i], err = aggregater(ctx)
if err != nil {
return nil, err
}
}
return aggregates, nil
}

View File

@ -0,0 +1,193 @@
package sdk
import (
"context"
"testing"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
func TestFilter(t *testing.T) {
type args struct {
filter filterFunc
appender appendFunc
}
tests := []struct {
name string
args args
wantErr func(error) bool
}{
{
name: "filter error",
args: args{
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
return nil, errors.ThrowInternal(nil, "test-46VX2", "test error")
},
appender: nil,
},
wantErr: errors.IsInternal,
},
{
name: "no events found",
args: args{
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
return []*es_models.Event{}, nil
},
appender: nil,
},
wantErr: errors.IsNotFound,
},
{
name: "append fails",
args: args{
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
return []*es_models.Event{&es_models.Event{}}, nil
},
appender: func(...*es_models.Event) error {
return errors.ThrowInvalidArgument(nil, "SDK-DhBzl", "test error")
},
},
wantErr: IsAppendEventError,
},
{
name: "filter correct",
args: args{
filter: func(context.Context, *es_models.SearchQuery) ([]*es_models.Event, error) {
return []*es_models.Event{&es_models.Event{}}, nil
},
appender: func(...*es_models.Event) error {
return nil
},
},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Filter(context.Background(), tt.args.filter, tt.args.appender, nil)
if tt.wantErr == nil && err != nil {
t.Errorf("no error expected %v", err)
}
if tt.wantErr != nil && !tt.wantErr(err) {
t.Errorf("no error has wrong type %v", err)
}
})
}
}
func TestPush(t *testing.T) {
type args struct {
push pushFunc
appender appendFunc
aggregaters []aggregateFunc
}
tests := []struct {
name string
args args
wantErr func(error) bool
}{
{
name: "no aggregates",
args: args{
push: nil,
appender: nil,
aggregaters: nil,
},
wantErr: errors.IsPreconditionFailed,
},
{
name: "aggregater fails",
args: args{
push: nil,
appender: nil,
aggregaters: []aggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return nil, errors.ThrowInternal(nil, "SDK-Ec5x2", "test err")
},
},
},
wantErr: errors.IsInternal,
},
{
name: "push fails",
args: args{
push: func(context.Context, ...*es_models.Aggregate) error {
return errors.ThrowInternal(nil, "SDK-0g4gW", "test error")
},
appender: nil,
aggregaters: []aggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{}, nil
},
},
},
wantErr: errors.IsInternal,
},
{
name: "append aggregates fails",
args: args{
push: func(context.Context, ...*es_models.Aggregate) error {
return nil
},
appender: func(...*es_models.Event) error {
return errors.ThrowInvalidArgument(nil, "SDK-BDhcT", "test err")
},
aggregaters: []aggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},
},
},
wantErr: IsAppendEventError,
},
{
name: "correct one aggregate",
args: args{
push: func(context.Context, ...*es_models.Aggregate) error {
return nil
},
appender: func(...*es_models.Event) error {
return nil
},
aggregaters: []aggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},
},
},
wantErr: nil,
},
{
name: "correct multiple aggregate",
args: args{
push: func(context.Context, ...*es_models.Aggregate) error {
return nil
},
appender: func(...*es_models.Event) error {
return nil
},
aggregaters: []aggregateFunc{
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},
func(context.Context) (*es_models.Aggregate, error) {
return &es_models.Aggregate{Events: []*es_models.Event{&es_models.Event{}}}, nil
},
},
},
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Push(context.Background(), tt.args.push, tt.args.appender, tt.args.aggregaters...)
if tt.wantErr == nil && err != nil {
t.Errorf("no error expected %v", err)
}
if tt.wantErr != nil && !tt.wantErr(err) {
t.Errorf("no error has wrong type %v", err)
}
})
}
}

View File

@ -2,6 +2,7 @@ package eventsourcing
import ( import (
"context" "context"
proj_model "github.com/caos/zitadel/internal/project/model" proj_model "github.com/caos/zitadel/internal/project/model"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing" proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
) )
@ -26,11 +27,7 @@ func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (project *p
func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) { func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) {
project := &proj_model.Project{Name: name} project := &proj_model.Project{Name: name}
project, err := repo.ProjectEvents.CreateProject(ctx, project) return repo.ProjectEvents.CreateProject(ctx, project)
if err != nil {
return nil, err
}
return project, nil
} }
func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) { func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
@ -39,11 +36,7 @@ func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.
return nil, err return nil, err
} }
project, err = repo.ProjectEvents.UpdateProject(ctx, existingProject, project) return repo.ProjectEvents.UpdateProject(ctx, existingProject, project)
if err != nil {
return nil, err
}
return project, err
} }
func (repo *ProjectRepo) DeactivateProject(ctx context.Context, id string) (*proj_model.Project, error) { func (repo *ProjectRepo) DeactivateProject(ctx context.Context, id string) (*proj_model.Project, error) {
@ -52,11 +45,7 @@ func (repo *ProjectRepo) DeactivateProject(ctx context.Context, id string) (*pro
return nil, err return nil, err
} }
project, err = repo.ProjectEvents.DeactivateProject(ctx, project) return repo.ProjectEvents.DeactivateProject(ctx, project)
if err != nil {
return nil, err
}
return project, err
} }
func (repo *ProjectRepo) ReactivateProject(ctx context.Context, id string) (*proj_model.Project, error) { func (repo *ProjectRepo) ReactivateProject(ctx context.Context, id string) (*proj_model.Project, error) {
@ -65,9 +54,5 @@ func (repo *ProjectRepo) ReactivateProject(ctx context.Context, id string) (*pro
return nil, err return nil, err
} }
project, err = repo.ProjectEvents.ReactivateProject(ctx, project) return repo.ProjectEvents.ReactivateProject(ctx, project)
if err != nil {
return nil, err
}
return project, err
} }

View File

@ -2,8 +2,10 @@ package eventsourcing
import ( import (
"context" "context"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
es_int "github.com/caos/zitadel/internal/eventstore" es_int "github.com/caos/zitadel/internal/eventstore"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
proj_model "github.com/caos/zitadel/internal/project/model" proj_model "github.com/caos/zitadel/internal/project/model"
) )
@ -20,22 +22,17 @@ func StartProject(conf ProjectConfig) (*ProjectEventstore, error) {
} }
func (es *ProjectEventstore) ProjectByID(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) { func (es *ProjectEventstore) ProjectByID(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
filter, err := ProjectByIDQuery(project.ID, project.Sequence) query, err := ProjectByIDQuery(project.ID, project.Sequence)
if err != nil { if err != nil {
return nil, err return nil, err
} }
events, err := es.Eventstore.FilterEvents(ctx, filter)
p := ProjectFromModel(project)
err = es_sdk.Filter(ctx, es.FilterEvents, p.AppendEvents, query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(events) == 0 { return ProjectToModel(p), nil
return nil, caos_errs.ThrowNotFound(nil, "EVENT-8due3", "Could not find project events")
}
foundProject, err := ProjectFromEvents(nil, events...)
if err != nil {
return nil, err
}
return ProjectToModel(foundProject), nil
} }
func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) { func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
@ -44,34 +41,29 @@ func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_mo
} }
project.State = proj_model.Active project.State = proj_model.Active
repoProject := ProjectFromModel(project) repoProject := ProjectFromModel(project)
projectAggregate, err := ProjectCreateAggregate(ctx, es.Eventstore.AggregateCreator(), repoProject)
if err != nil { createAggregate := ProjectCreateAggregate(es.AggregateCreator(), repoProject)
return nil, err err := es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, createAggregate)
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil { if err != nil {
return nil, err return nil, err
} }
repoProject.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoProject), nil return ProjectToModel(repoProject), nil
} }
func (es *ProjectEventstore) UpdateProject(ctx context.Context, existing *proj_model.Project, new *proj_model.Project) (*proj_model.Project, error) { func (es *ProjectEventstore) UpdateProject(ctx context.Context, existingProject *proj_model.Project, project *proj_model.Project) (*proj_model.Project, error) {
if !new.IsValid() { if !project.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required") return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
} }
repoExisting := ProjectFromModel(existing) repoExisting := ProjectFromModel(existingProject)
repoNew := ProjectFromModel(new) repoNew := ProjectFromModel(project)
projectAggregate, err := ProjectUpdateAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew)
updateAggregate := ProjectUpdateAggregate(es.AggregateCreator(), repoExisting, repoNew)
err := es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil return ProjectToModel(repoExisting), nil
} }
@ -79,16 +71,10 @@ func (es *ProjectEventstore) DeactivateProject(ctx context.Context, existing *pr
if !existing.IsActive() { if !existing.IsActive() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be active") return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be active")
} }
repoExisting := ProjectFromModel(existing) repoExisting := ProjectFromModel(existing)
projectAggregate, err := ProjectDeactivateAggregate(ctx, es.AggregateCreator(), repoExisting) aggregate := ProjectDeactivateAggregate(es.AggregateCreator(), repoExisting)
if err != nil { es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil return ProjectToModel(repoExisting), nil
} }
@ -96,15 +82,9 @@ func (es *ProjectEventstore) ReactivateProject(ctx context.Context, existing *pr
if existing.IsActive() { if existing.IsActive() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be inactive") return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be inactive")
} }
repoExisting := ProjectFromModel(existing) repoExisting := ProjectFromModel(existing)
projectAggregate, err := ProjectReactivateAggregate(ctx, es.AggregateCreator(), repoExisting) aggregate := ProjectReactivateAggregate(es.AggregateCreator(), repoExisting)
if err != nil { es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil return ProjectToModel(repoExisting), nil
} }

View File

@ -2,11 +2,48 @@ package eventsourcing
import ( import (
"encoding/json" "encoding/json"
"github.com/caos/logging" "github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
) )
const (
projectVersion = "v1"
)
type Project struct {
es_models.ObjectRoot
Name string `json:"name,omitempty"`
State int32 `json:"-"`
}
func ProjectFromModel(project *model.Project) *Project {
return &Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ObjectRoot.ID,
Sequence: project.Sequence,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
},
Name: project.Name,
State: model.ProjectStateToInt(project.State),
}
}
func ProjectToModel(project *Project) *model.Project {
return &model.Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ID,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
Sequence: project.Sequence,
},
Name: project.Name,
State: model.ProjectStateFromInt(project.State),
}
}
func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project, error) { func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project, error) {
if project == nil { if project == nil {
project = &Project{} project = &Project{}
@ -15,6 +52,14 @@ func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project,
return project, project.AppendEvents(events...) return project, project.AppendEvents(events...)
} }
func (p *Project) Changes(changed *Project) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if changed.Name != "" && p.Name != changed.Name {
changes["name"] = changed.Name
}
return changes
}
func (p *Project) AppendEvents(events ...*es_models.Event) error { func (p *Project) AppendEvents(events ...*es_models.Event) error {
for _, event := range events { for _, event := range events {
if err := p.AppendEvent(event); err != nil { if err := p.AppendEvent(event); err != nil {

View File

@ -2,9 +2,10 @@ package eventsourcing
import ( import (
"encoding/json" "encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
"testing"
) )
func TestProjectFromEvents(t *testing.T) { func TestProjectFromEvents(t *testing.T) {
@ -167,3 +168,47 @@ func TestAppendReactivatedEvent(t *testing.T) {
}) })
} }
} }
func TestChanges(t *testing.T) {
type args struct {
existing *Project
new *Project
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "project name changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "NameChanged"},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "Name"},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existing.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}

View File

@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
"github.com/sony/sonyflake" "github.com/sony/sonyflake"
@ -12,50 +13,6 @@ import (
var idGenerator = sonyflake.NewSonyflake(sonyflake.Settings{}) var idGenerator = sonyflake.NewSonyflake(sonyflake.Settings{})
const (
projectVersion = "v1"
)
type Project struct {
es_models.ObjectRoot
Name string `json:"name,omitempty"`
State int32 `json:"-"`
}
func (p *Project) Changes(changed *Project) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if changed.Name != "" && p.Name != changed.Name {
changes["name"] = changed.Name
}
return changes
}
func ProjectFromModel(project *model.Project) *Project {
return &Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ObjectRoot.ID,
Sequence: project.Sequence,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
},
Name: project.Name,
State: model.ProjectStateToInt(project.State),
}
}
func ProjectToModel(project *Project) *model.Project {
return &model.Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ID,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
Sequence: project.Sequence,
},
Name: project.Name,
State: model.ProjectStateFromInt(project.State),
}
}
func ProjectByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) { func ProjectByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
if id == "" { if id == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dke74", "id should be filled") return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dke74", "id should be filled")
@ -74,58 +31,61 @@ func ProjectAggregate(ctx context.Context, aggCreator *es_models.AggregateCreato
return aggCreator.NewAggregate(ctx, id, model.ProjectAggregate, projectVersion, sequence) return aggCreator.NewAggregate(ctx, id, model.ProjectAggregate, projectVersion, sequence)
} }
func ProjectCreateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, project *Project) (*es_models.Aggregate, error) { func ProjectCreateAggregate(aggCreator *es_models.AggregateCreator, project *Project) func(ctx context.Context) (*es_models.Aggregate, error) {
if project == nil { return func(ctx context.Context) (*es_models.Aggregate, error) {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie6", "project should not be nil") if project == nil {
} return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie6", "project should not be nil")
var err error }
id, err := idGenerator.NextID() var err error
if err != nil { id, err := idGenerator.NextID()
return nil, err if err != nil {
} return nil, err
project.ID = strconv.FormatUint(id, 10) }
project.ID = strconv.FormatUint(id, 10)
agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence) agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return agg.AppendEvent(model.ProjectAdded, project) return agg.AppendEvent(model.ProjectAdded, project)
}
} }
func ProjectUpdateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project, new *Project) (*es_models.Aggregate, error) { func ProjectUpdateAggregate(aggCreator *es_models.AggregateCreator, existing *Project, new *Project) func(ctx context.Context) (*es_models.Aggregate, error) {
if existing == nil { return func(ctx context.Context) (*es_models.Aggregate, error) {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk93d", "existing project should not be nil") if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk93d", "existing project should not be nil")
}
if new == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "new project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
changes := existing.Changes(new)
return agg.AppendEvent(model.ProjectChanged, changes)
} }
if new == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "new project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
changes := existing.Changes(new)
return agg.AppendEvent(model.ProjectChanged, changes)
} }
func ProjectDeactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) { func ProjectDeactivateAggregate(aggCreator *es_models.AggregateCreator, project *Project) func(ctx context.Context) (*es_models.Aggregate, error) {
if existing == nil { return projectStateAggregate(aggCreator, project, model.ProjectDeactivated)
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ueh45", "existing project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.ProjectDeactivated, nil)
} }
func ProjectReactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) { func ProjectReactivateAggregate(aggCreator *es_models.AggregateCreator, project *Project) func(ctx context.Context) (*es_models.Aggregate, error) {
if existing == nil { return projectStateAggregate(aggCreator, project, model.ProjectReactivated)
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-37dur", "existing project should not be nil") }
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence) func projectStateAggregate(aggCreator *es_models.AggregateCreator, project *Project, state models.EventType) func(ctx context.Context) (*es_models.Aggregate, error) {
if err != nil { return func(ctx context.Context) (*es_models.Aggregate, error) {
return nil, err if project == nil {
} return nil, errors.ThrowPreconditionFailed(nil, "EVENT-37dur", "existing project should not be nil")
return agg.AppendEvent(model.ProjectReactivated, nil) }
agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(state, nil)
}
} }

View File

@ -2,57 +2,14 @@ package eventsourcing
import ( import (
"context" "context"
"testing"
"github.com/caos/zitadel/internal/api/auth" "github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
"testing"
) )
func TestChanges(t *testing.T) {
type args struct {
existing *Project
new *Project
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "project name changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "NameChanged"},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "Name"},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existing.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestProjectByIDQuery(t *testing.T) { func TestProjectByIDQuery(t *testing.T) {
type args struct { type args struct {
id string id string
@ -231,7 +188,7 @@ func TestProjectCreateAggregate(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectCreateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.new) agg, err := ProjectCreateAggregate(tt.args.aggCreator, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
@ -312,7 +269,7 @@ func TestProjectUpdateAggregate(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectUpdateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new) agg, err := ProjectUpdateAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
@ -376,7 +333,7 @@ func TestProjectDeactivateAggregate(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectDeactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing) agg, err := ProjectDeactivateAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
@ -437,7 +394,7 @@ func TestProjectReactivateAggregate(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectReactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing) agg, err := ProjectReactivateAggregate(tt.args.aggCreator, tt.args.existing)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen { if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events)) t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))