Closed timriley closed 7 years ago
Yeah I love this idea. This will be more idiomatic and more flexible. I would also say that with this API we could consider releasing 1.0.0 (assuming we find it work well for us of course).
@solnic Great :) I'll get started on it. Agree that this feels like the kind of API we can make it to 1.0.0 with.
This sounds like a really good direction, especially the part about transaction specific logic to alter values between steps, when I was experimenting a while ago I ended up doing something like this:
module Blog
module Transactions
module Posts
Create = ::Blog::Transaction.new do
step :validate, with: ::Blog::Operations::Validator::Validate[
'blog.validators.posts.create'
]
step :persist, with: ::Blog::Operations::Repository::Create[
'blog.repositories.posts'
]
end
end
end
end
Where operations were just wrappers that would wrap certain command results in a monad for transactions.
This looks awesome to me 👍
SICK! 💯
🥇
After working with dry-transaction for a while, and across a number of apps, I see a few issues in its design:
Dry.Transaction
constructor creates objects with an ancestry that app authors have no control over, so there’s no way to add behaviour to all an app’s transactions (this is important in our apps at Icelab, because we use a little#to_queue/#call_from_queue
protocol for running operations in background jobs.I think we can solve all of these by moving to class-based transactions.
After thinking about it for a while, our transactions are really just a specialised case for auto-injection: a transaction collects a series of operation objects (mapped to container identifiers), and calls them in sequence.
So instead of building a transaction like this:
We could build one like this:
This would allow us to inject replacement dependencies to make testing easier:
For larger apps, this’d let us provide a per-container transaction mixin for easy reuse, too:
Or a base class:
Another benefit from class-based transactions is that the application author can add extra, local, steps if they needed to:
Or even wrap the existing steps:
And all the other stuff that we get with Ruby’s standard class behaviour.
I think this approach would make dry-transaction easier and more flexible to work with, would make it fit more naturally into larger apps, and handle a variety of different real-world scenarios, many of which we couldn’t even predict right now.
What do you think? (/cc @solnic, @flash-gordon, @AMHOL) Any objections to me working on this approach?