mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
This pull request refactors the command execution and transaction handling logic within the `domain` package. The primary goal is to simplify the invoker chain, clarify transaction management, and improve the overall structure for better maintainability and readability. ### Key Changes: - **Simplified Invoker Chain**: The previous chain of invokers has been refactored. A new `transactionInvoker` is introduced to explicitly manage database transactions for commands that require them. This responsibility is no longer spread across different parts of the invocation flow. - **`Transactional` Interface**: A new `Transactional` interface has been introduced. Commands that need to run within a database transaction now implement this interface. This provides a clear and explicit contract for transactional operations. - **Consolidated Transaction Logic**: The logic for starting, committing, and rolling back transactions is now centralized within the `transactionInvoker` and the `eventStoreInvoker`. The `EnsureTx` method on `InvokeOpts` has been removed, and transaction handling is now managed automatically by the invocation framework. - **Improved Event Handling**: The `eventStoreInvoker` and its `eventCollector` have been rewritten to be more robust and to correctly handle nested and batched commands, ensuring that all events are collected and published within the same transaction. - **Code Cleanup**: A significant amount of now-unused code, including the old `invoker_test.go`, has been removed. Various comments and variable declarations have also been cleaned up. - **Updated Command Implementations**: All commands in the `domain` package (e.g., `ActivateOrgCommand`, `DeactivateOrgCommand`, `DeleteOrgCommand`, `UpdateOrgCommand`) have been updated to use the new `Transactional` interface where appropriate, removing manual transaction management. - **Enhanced Testing**: Tests have been added and updated to cover the new transaction and event handling logic, ensuring the refactoring is sound. - **Use `PrimaryKeyCondition`**: Use the `repository.PrimaryKeyCondition(...)` method instead of using `database.And(...)` where possible. These changes lead to a cleaner, more decoupled architecture for command handling in ZITADEL. --------- Co-authored-by: Marco A. <[email protected]>
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
package domain
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
)
|
|
|
|
//go:generate mockgen -typed -package domainmock -destination ./mock/executor.mock.go . Executor
|
|
type Executor interface {
|
|
Execute(ctx context.Context, opts *InvokeOpts) (err error)
|
|
fmt.Stringer
|
|
}
|
|
|
|
// Commander is all that is needed to implement the command pattern.
|
|
// It is the interface all manipulations need to implement.
|
|
// If possible it should also be used for queries. We will find out if this is possible in the future.
|
|
//
|
|
//go:generate mockgen -typed -package domainmock -destination ./mock/commander.mock.go . Commander
|
|
type Commander interface {
|
|
Validator
|
|
EventProducer
|
|
Executor
|
|
}
|
|
|
|
// Querier used to query data.
|
|
//
|
|
//go:generate mockgen -typed -package domainmock -destination ./mock/querier.mock.go . Querier
|
|
type Querier[T any] interface {
|
|
Validator
|
|
Executor
|
|
// Result returns the result of the query.
|
|
// If `Execute` returns an error, the result is nil.
|
|
Result() T
|
|
}
|
|
|
|
// 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, event publishing, transaction handling and validation.
|
|
// If you want to invoke multiple commands in a single transaction, you can use the [BatchExecutors] helper.
|
|
func Invoke(ctx context.Context, executor Executor, opts ...InvokeOpt) error {
|
|
invokeOpts := DefaultOpts(
|
|
NewLoggingInvoker(
|
|
NewTraceInvoker(
|
|
NewEventStoreInvoker(
|
|
NewTransactionInvoker(
|
|
NewValidatorInvoker(nil),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
for _, opt := range opts {
|
|
opt(invokeOpts)
|
|
}
|
|
return invokeOpts.Invoke(ctx, executor)
|
|
}
|
|
|
|
// Invoker is part of the command pattern.
|
|
// It is the interface that is used to execute commands.
|
|
type Invoker interface {
|
|
Invoke(ctx context.Context, command Executor, opts *InvokeOpts) error
|
|
}
|
|
|
|
type invoker struct {
|
|
next Invoker
|
|
}
|
|
|
|
func (i invoker) execute(ctx context.Context, executor Executor, opts *InvokeOpts) error {
|
|
if i.next != nil {
|
|
return i.next.Invoke(ctx, executor, opts)
|
|
}
|
|
return executor.Execute(ctx, opts)
|
|
}
|
|
|
|
// ensureTx ensures that the InvokeOpts has a transaction.
|
|
//
|
|
// If [Commander] or [Querier] requires a transaction implement [Transactional]
|
|
// the close function ends the transaction and resets [InvokeOpts].db.
|
|
// If a new transaction is started, it returns a function to end the transaction.
|
|
// The caller is responsible to call the returned function to end the transaction.
|
|
// If no new transaction is started, the returned function is a no-op and still safe to call.
|
|
func (i invoker) ensureTx(ctx context.Context, opts *InvokeOpts) (close func(err error) error, err error) {
|
|
beginner, ok := opts.DB().(database.Beginner)
|
|
if !ok {
|
|
return func(err error) error { return err }, nil
|
|
}
|
|
|
|
previousDB := opts.DB()
|
|
tx, err := beginner.Begin(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.db = tx
|
|
return func(err error) error {
|
|
opts.db = previousDB
|
|
return tx.End(ctx, err)
|
|
}, nil
|
|
}
|