serradura / u-case

Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code.
https://rubygems.org/gems/u-case
MIT License
527 stars 33 forks source link

Micro::Cases.flow(db_transaction: true) #44

Open serradura opened 4 years ago

serradura commented 4 years ago

Wraps a Micro::Cases::Flow inside of anActiveRecord::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:


Thanks, @marlosirapuan, @josuetex, @marcosgz, @MatheusRich, @mrbongiolo for helping me to elaborate on this idea. 🚀

marlosirapuan commented 4 years ago

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

serradura commented 4 years ago

@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

MatheusRich commented 4 years ago

To me flow_in_a_db_transaction is too much verbose.

An alternative:

MyFlowWrappedInATransaction = Micro::Cases.flow(db_transaction: true, [
  CreateUser,
  CeateUserProfile
])
serradura commented 4 years ago

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

mrbongiolo commented 4 years ago

@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, [])
serradura commented 4 years ago

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

marcosgz commented 4 years ago

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

mrbongiolo commented 4 years ago

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.

PavarinE commented 1 day ago

Hello @serradura . Do we have any plans to add this feature still? I was looking exactly for support to db transactions in a flow.