Micro::Cases.flow(db_transaction: true)
serradura opened this issue ยท 8 comments
Wraps a Micro::Cases::Flow
inside of an ActiveRecord::Transactions
and if an exception happening or any step returns a failure, this flow will be halted because an ActiveRecord::Rollback
will be raised.
MyFlow = Micro::Cases.flow([
NormalizeParams,
ValidatePassword,
Micro::Cases.flow(db_transaction: true, steps: [
CreateUser,
CreateUserProfile
]),
EnqueueIndexingJob
])
# ---
MyFlowWrappedInATransaction = Micro::Cases.flow(db_transaction: true, steps: [
CreateUser,
CeateUserProfile
])
MyFlow = Micro::Cases.flow([
NormalizeParams,
ValidatePassword,
MyFlowWrappedInATransaction,
EnqueueIndexingJob
])
class MyFlow < Micro::Case
flow(db_transaction: true, steps: [
NormalizeParams,
ValidatePassword,
CreateUser,
CreateUserProfile
])
])
# ---
MyFlowWrappedInATransaction =
Micro::Cases.flow(db_transaction: true, steps: [
CreateUser,
CeateUserProfile
])
class MyFlow < Micro::Case
flow([
NormalizeParams,
ValidatePassword,
MyFlowWrappedInATransaction
])
])
Definition of done:
- This mode/plugin will be disabled by default, that is, it will be enabled to be available. Except for its core dependencies
kind
andu-attributes
, this gem never will require any external dependency by default.
Thanks, @marlosirapuan, @josuetex, @marcosgz, @MatheusRich, @mrbongiolo for helping me to elaborate on this idea. ๐
why not just transaction
? cause it's already a flow.. ๐ค
@marlosirapuan I'm not sure about the method name yet. But this mode could be enabled using a kwarg. e.g:
Micro::Cases.flow(transaction: true, [
CreateUser,
CeateUserProfile
])
# --
class MyCase < Micro::Case
flow(transaction: true, [
CreateUser,
CeateUserProfile
])
What do you think? cc: @MatheusRich
To me flow_in_a_db_transaction
is too much verbose.
An alternative:
MyFlowWrappedInATransaction = Micro::Cases.flow(db_transaction: true, [
CreateUser,
CeateUserProfile
])
@MatheusRich I liked the usage of db_transaction
kwarg. Much better and explicit!
@serradura how about?
MyFlow = Micro::Cases.flow(transaction: :db, [])
# or
MyFlow = Micro::Cases.flow(transaction: Micro::Cases::DB_TRANSACTION, [])
# or
MyFlow = Micro::Cases.flow(transaction: MyCustomImplementation, [])
@mrbongiolo I can think of different types of transactions. But I would like to start solving this first one: DB transactions. As you proposed, I believe that if a new kind of transaction appears, we could make db_transaction: true
become an alias for transaction: :db
. But its an excellent suggestion. Thank you!
@serradura I totally agree with @mrbongiolo suggestion of :transaction
naming: MyFlow = Micro::Cases.flow(transaction: MyCustomImplementation, [])
I've worked on applications that had more than one ActiveRecord or Sequel db connections. The transaction itself would not even need to be part of the project. And just provide mechanisms to group the a list of cases in a single transaction. And good examples in the documentation.
I don't think someone is using two ORM like this in same application. But a generic API would make that possible:
class SequelTransaction < Micro::Cases::Transaction
def call!
result = nil
SequelDB.transaction do
result = yield
raise Sequel::Rollback if result.failure?
end
result
end
end
class ActiveRecordTransaction < Micro::Cases::Transaction
def call!
result = nil
ApplicationRecord.transaction do
result = yield
raise ActiveRecord::Rollback if result.failure?
end
result
end
end
SignInFlow = Micro::Cases.flow(transaction: ActiveRecordTransaction, [CreateUser, CreateProfile])
SubscribeFlow = Micro::Cases.flow(transaction: SequelTransaction, [CreateOrder, CreateSubscription])
Ignore the syntax itself of this example. I'm talking the idea of a generic transaction.