u-case icon indicating copy to clipboard operation
u-case copied to clipboard

Micro::Cases.flow(db_transaction: true)

Open serradura opened this issue 4 years ago • 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 and u-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. 🚀

serradura avatar Jul 24 '20 17:07 serradura

why not just transaction? cause it's already a flow.. 🤔

marlosirapuan avatar Jul 24 '20 17:07 marlosirapuan

@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

serradura avatar Jul 24 '20 17:07 serradura

To me flow_in_a_db_transaction is too much verbose.

An alternative:

MyFlowWrappedInATransaction = Micro::Cases.flow(db_transaction: true, [
  CreateUser,
  CeateUserProfile
])

MatheusRich avatar Jul 24 '20 17:07 MatheusRich

@MatheusRich I liked the usage of db_transaction kwarg. Much better and explicit!

serradura avatar Jul 24 '20 17:07 serradura

@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 avatar Jul 24 '20 21:07 mrbongiolo

@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 avatar Jul 27 '20 14:07 serradura

@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.

marcosgz avatar Jul 29 '20 03:07 marcosgz

I was thinking exactly in that way, so the gem could have its own AR db transaction implementation (just for easy of use), but it could receive any object that respects the u-case transaction API.

mrbongiolo avatar Jul 29 '20 03:07 mrbongiolo