diff --git a/backend/v3/domain/invoke.go b/backend/v3/domain/invoke.go index 963a8fafa86..81caddcc22f 100644 --- a/backend/v3/domain/invoke.go +++ b/backend/v3/domain/invoke.go @@ -1,128 +1,133 @@ package domain -import "context" +import ( + "context" + "fmt" -// import ( -// "context" -// "fmt" + "github.com/zitadel/zitadel/backend/v3/storage/eventstore" +) -// "github.com/zitadel/zitadel/backend/v3/storage/eventstore" -// ) +// Invoke provides a way to execute commands within the domain package. +// It uses a chain of responsibility pattern to handle the command execution. +// The default chain includes logging, tracing, and event publishing. +// If you want to invoke multiple commands in a single transaction, you can use the [commandBatch]. +func Invoke(ctx context.Context, cmd Commander) error { + invoker := newEventStoreInvoker( + newLoggingInvoker( + newTraceInvoker( + newValidatorInvoker(nil), + ), + ), + ) + opts := &CommandOpts{ + Invoker: invoker.collector, + DB: pool, + OrgRepository: nil, + } + return invoker.Invoke(ctx, cmd, opts) +} -// // Invoke provides a way to execute commands within the domain package. -// // It uses a chain of responsibility pattern to handle the command execution. -// // The default chain includes logging, tracing, and event publishing. -// // If you want to invoke multiple commands in a single transaction, you can use the [commandBatch]. -// func Invoke(ctx context.Context, cmd Commander) error { -// invoker := newEventStoreInvoker(newLoggingInvoker(newTraceInvoker(nil))) -// opts := &CommandOpts{ -// Invoker: invoker.collector, -// DB: pool, -// } -// return invoker.Invoke(ctx, cmd, opts) -// } +// eventStoreInvoker checks if the command implements the [eventer] interface. +// If it does, it collects the events and publishes them to the event store. +type eventStoreInvoker struct { + collector *eventCollector +} -// // eventStoreInvoker checks if the command implements the [eventer] interface. -// // If it does, it collects the events and publishes them to the event store. -// type eventStoreInvoker struct { -// collector *eventCollector -// } +func newEventStoreInvoker(next Invoker) *eventStoreInvoker { + return &eventStoreInvoker{collector: &eventCollector{next: next}} +} -// func newEventStoreInvoker(next Invoker) *eventStoreInvoker { -// return &eventStoreInvoker{collector: &eventCollector{next: next}} -// } +func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { + err = i.collector.Invoke(ctx, command, opts) + if err != nil { + return err + } + if len(i.collector.events) > 0 { + err = eventstore.Publish(ctx, i.collector.events, opts.DB) + if err != nil { + return err + } + } + return nil +} -// func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { -// err = i.collector.Invoke(ctx, command, opts) -// if err != nil { -// return err -// } -// if len(i.collector.events) > 0 { -// err = eventstore.Publish(ctx, i.collector.events, opts.DB) -// if err != nil { -// return err -// } -// } -// return nil -// } +// eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed. +type eventCollector struct { + next Invoker + events []*eventstore.Event +} -// // eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed. -// type eventCollector struct { -// next Invoker -// events []*eventstore.Event -// } +type eventer interface { + Events() []*eventstore.Event +} -// type eventer interface { -// Events() []*eventstore.Event -// } +func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { + if e, ok := command.(eventer); ok && len(e.Events()) > 0 { + // we need to ensure all commands are executed in the same transaction + close, err := opts.EnsureTx(ctx) + if err != nil { + return err + } + defer func() { err = close(ctx, err) }() -// func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { -// if e, ok := command.(eventer); ok && len(e.Events()) > 0 { -// // we need to ensure all commands are executed in the same transaction -// close, err := opts.EnsureTx(ctx) -// if err != nil { -// return err -// } -// defer func() { err = close(ctx, err) }() + i.events = append(i.events, e.Events()...) + } + if i.next != nil { + return i.next.Invoke(ctx, command, opts) + } + return command.Execute(ctx, opts) +} -// i.events = append(i.events, e.Events()...) -// } -// if i.next != nil { -// return i.next.Invoke(ctx, command, opts) -// } -// return command.Execute(ctx, opts) -// } +// traceInvoker decorates each command with tracing. +type traceInvoker struct { + next Invoker +} -// // traceInvoker decorates each command with tracing. -// type traceInvoker struct { -// next Invoker -// } +func newTraceInvoker(next Invoker) *traceInvoker { + return &traceInvoker{next: next} +} -// func newTraceInvoker(next Invoker) *traceInvoker { -// return &traceInvoker{next: next} -// } +func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { + ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", command)) + defer func() { + if err != nil { + span.RecordError(err) + } + span.End() + }() -// func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { -// ctx, span := tracer.Start(ctx, fmt.Sprintf("%T", command)) -// defer func() { -// if err != nil { -// span.RecordError(err) -// } -// span.End() -// }() + if i.next != nil { + return i.next.Invoke(ctx, command, opts) + } + return command.Execute(ctx, opts) +} -// if i.next != nil { -// return i.next.Invoke(ctx, command, opts) -// } -// return command.Execute(ctx, opts) -// } +// loggingInvoker decorates each command with logging. +// It is an example implementation and logs the command name at the beginning and success or failure after the command got executed. +type loggingInvoker struct { + next Invoker +} -// // loggingInvoker decorates each command with logging. -// // It is an example implementation and logs the command name at the beginning and success or failure after the command got executed. -// type loggingInvoker struct { -// next Invoker -// } +func newLoggingInvoker(next Invoker) *loggingInvoker { + return &loggingInvoker{next: next} +} -// func newLoggingInvoker(next Invoker) *loggingInvoker { -// return &loggingInvoker{next: next} -// } +func (i *loggingInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { + logger.InfoContext(ctx, "Invoking command", "command", command.String()) -// func (i *loggingInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) (err error) { -// logger.InfoContext(ctx, "Invoking command", "command", command.String()) + if i.next != nil { + err = i.next.Invoke(ctx, command, opts) + } else { + err = command.Execute(ctx, opts) + } -// if i.next != nil { -// err = i.next.Invoke(ctx, command, opts) -// } else { -// err = command.Execute(ctx, opts) -// } - -// if err != nil { -// logger.ErrorContext(ctx, "Command invocation failed", "command", command.String(), "error", err) -// return err -// } -// logger.InfoContext(ctx, "Command invocation succeeded", "command", command.String()) -// return nil -// } + if err != nil { + logger.ErrorContext(ctx, "Command invocation failed", "command", command.String(), "error", err) + return err + } + logger.InfoContext(ctx, "Command invocation succeeded", "command", command.String()) + return nil +} type noopInvoker struct { next Invoker @@ -135,6 +140,26 @@ func (i *noopInvoker) Invoke(ctx context.Context, command Commander, opts *Comma return command.Execute(ctx, opts) } +type validatorInvoker struct { + next Invoker +} + +func newValidatorInvoker(next Invoker) *validatorInvoker { + return &validatorInvoker{next: next} +} + +func (i *validatorInvoker) Invoke(ctx context.Context, command Commander, opts *CommandOpts) error { + if err := command.Validate(); err != nil { + return err + } + + if i.next != nil { + return i.next.Invoke(ctx, command, opts) + } + + return command.Execute(ctx, opts) +} + // // cacheInvoker could be used in the future to do the caching. // // My goal would be to have two interfaces: // // - cacheSetter: which caches an object