mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-27 09:42:16 +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]>
96 lines
2.9 KiB
Go
96 lines
2.9 KiB
Go
package domain_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
domainmock "github.com/zitadel/zitadel/backend/v3/domain/mock"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
|
)
|
|
|
|
type testTransactionalExecutor struct {
|
|
domain.Transactional
|
|
domain.Executor
|
|
}
|
|
|
|
func Test_transactionInvoker_Invoke(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
executor func(ctrl *gomock.Controller) domain.Executor
|
|
db func(ctrl *gomock.Controller) database.Pool
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "non-transactional executor does not start a transaction",
|
|
executor: func(ctrl *gomock.Controller) domain.Executor {
|
|
mock := domainmock.NewMockExecutor(ctrl)
|
|
mock.EXPECT().Execute(gomock.Any(), gomock.Any()).Return(nil)
|
|
return mock
|
|
},
|
|
db: func(ctrl *gomock.Controller) database.Pool {
|
|
return dbmock.NewMockPool(ctrl)
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "transactional executor starts a transaction execution successful",
|
|
executor: func(ctrl *gomock.Controller) domain.Executor {
|
|
transactional := domainmock.NewMockTransactional(ctrl)
|
|
executor := domainmock.NewMockExecutor(ctrl)
|
|
executor.EXPECT().Execute(gomock.Any(), gomock.Any()).Return(nil)
|
|
return &testTransactionalExecutor{
|
|
Transactional: transactional,
|
|
Executor: executor,
|
|
}
|
|
},
|
|
db: func(ctrl *gomock.Controller) database.Pool {
|
|
pool := dbmock.NewMockPool(ctrl)
|
|
tx := dbmock.NewMockTransaction(ctrl)
|
|
pool.EXPECT().Begin(gomock.Any(), gomock.Any()).Return(tx, nil)
|
|
tx.EXPECT().End(gomock.Any(), nil).Return(nil)
|
|
return pool
|
|
},
|
|
wantErr: nil,
|
|
},
|
|
{
|
|
name: "transactional executor starts a transaction execution failed",
|
|
executor: func(ctrl *gomock.Controller) domain.Executor {
|
|
transactional := domainmock.NewMockTransactional(ctrl)
|
|
executor := domainmock.NewMockExecutor(ctrl)
|
|
executor.EXPECT().Execute(gomock.Any(), gomock.Any()).Return(assert.AnError)
|
|
return &testTransactionalExecutor{
|
|
Transactional: transactional,
|
|
Executor: executor,
|
|
}
|
|
},
|
|
db: func(ctrl *gomock.Controller) database.Pool {
|
|
pool := dbmock.NewMockPool(ctrl)
|
|
tx := dbmock.NewMockTransaction(ctrl)
|
|
pool.EXPECT().Begin(gomock.Any(), gomock.Any()).Return(tx, nil)
|
|
tx.EXPECT().End(gomock.Any(), assert.AnError).DoAndReturn(func(ctx context.Context, err error) error { return err })
|
|
return pool
|
|
},
|
|
wantErr: assert.AnError,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctrl := gomock.NewController(t)
|
|
opts := &domain.InvokeOpts{}
|
|
domain.WithQueryExecutor(tt.db(ctrl))(opts)
|
|
|
|
invoker := domain.NewTransactionInvoker(nil)
|
|
gotErr := invoker.Invoke(t.Context(), tt.executor(ctrl), opts)
|
|
require.ErrorIs(t, gotErr, tt.wantErr)
|
|
})
|
|
}
|
|
}
|