Best practices for combining dry-monads, dry-validations, and command pattern

In my codebase I’m currently using dry-monads to implement a command pattern and dry-validation to perform validations on a per-command basis. Something like:

module Users
  class Register < BaseCommand
    class Contract < BaseContract
      params do
        email
        name
        etc....
      end
    end
 
   def call(params)
      yield Contract.call(params)

     account = Accounts::Create.call(some_param: params[:some], other_param: params[:other])
     yield do_account_setup_stuff(account: account, **params)
     yield Users::SendEmail.call(account: params[:email], **params)
     ....
     Success(account)
  end

  def do_account_setup_stuff(account:, another_thing:)
      # do stuff
  end

In my base contract class I have

 def self.call(params)
    new.call(params).to_monad
  end

so I can yield the results of the contract. My base command class does a similar thing, just instantiates the class and calls it.

Am I doing this all wrong? Everything feels pretty good except as you can see I ended up having to do a lot of weird stuff with the params being passed in. Since the contract needs everything in order to validate, but the other methods or other commands that compose this one don’t, I splat a lot of stuff I don’t need in order to cut down on wordiness or am forced to pull each arg out of the params hash. After all of this though, it just seems like a weird version of trailblazer and I can’t tell if I’m using these gems they way they were intended. Thanks so much in advance.

Personally I like when params are passed explicitly, because it’s very easy to see what needs what. An alternative is to use a context object that has all the things (already validated) and pass that to all other methods or objects.

If passing args explicitly bothers you though, you can add dry-effects to the mix and use reader effect to provide the context w/o passing it explicitly as an argument to each method/object.

In general, building commands/operations classes is not a trivial problem to solve. I’m still working on finding out a nice abstraction that wouldn’t be as complex as Trailblazer yet flexible enough to cover lots of use cases.

For now you can use a mixture of validation/monads/effects pretty successfuly but of course the learning curve is much steeper than if we had it all packed into a single abstraction. I’m hoping we’ll manage to come up with something as dry-transaction 1.0.0 - because that’s the gem that’s supposed to be a higher-level “operation abstraction”. Its current implementation is too limited though that’s why we decided to rebuild and improve it for its 1.0.0 release, but it’s gonna take a while.

1 Like

I occasionally use a contract, for instance my event-listener commands that process hash payloads have contracts to decide whether they will process the event or not.

But the vast majority of the time, I just use regular method arguments. They will get you really, really far.

I often do type validations of the individual arguments using the monad extension from dry-types.