-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Data Structure & Better APIs #13
Comments
Just an idea I had today. /cc @beerlington @totallymike |
I like this 👍 It seems that The success is useful as well, though one could argue for the standard Another factor I'm considering is non-Ecto interactors. I see interactors being tremendously useful for chaining a sequence of API calls. I propose moving the Finally, what are the semantics around failures? I'm interested in the rollback story. |
@totallymike, I think if you return from an {:ok, thing} or {:error, thing} it would set the status and return value appropriately. I don't think there needs to be an Ecto dependency, repo should maybe just be passed into the interaction as an opt. I don't really have a great rollback/failure story besides that once an %Interaction{success: false} we stop calling the next on, like halt on conn. What are your needs/requirements for it? |
Alternative: every step adds to the assigns the key of the function name or the eg: interaction :comment_changeset
interaction :insert, source: :comment_changeset, assign_as: :comment
interaction :sync_to_websocket, async: true The only problem with this would be not having a standard result field to access in controllers etc unless you standardized on something like |
I like the It does get a bit muddled, however, when you consider ancillary use cases for an defmodule MyInteractor do
use Interactor.Builder
# In Phoenix, these are configured via config/config.exs, but I'll put it here for demonstration
@instrumentors [MyInstrumentor]
interactor :post_comment
# ...
end
defmodule MyInstrumentor do
def post_comment(:start, _meta, %Interaction{} = interaction), do: #...
def post_comment(:stop, time_delta, _interaction) do
ExStatsD.timer(time_delta, "post_comment.duration")
end
end |
As for failure semantics, it would nice to teach interaction steps how to rollback if a further step fails. Going back to chaining API calls, imagine that you have to create a number of entities in an API in sequence. If the second or third record fails because it's invalid or some such, you'd want to clean up after the early records. It would be burdensome to have to unpack the result and figure out where the failure happened to clean up appropriately, so steps could learn how to clean up for themselves, and they'd play in rewind. #12 has some facility for this, but I struggled a bit with what arguments to pass around during the rollback step, and it's inconsistent. |
For an example of prior art on the rollback stuff, see https://github.com/collectiveidea/interactor#rollback, from which I stole the basic idea as to what it should look like. |
One idea would be to let the interaction :post_comment, rollback: :delete_comment The If steps omit a rollback argument, we either use a default passthrough, or just don't call a rollback for that step. |
The rollback thing seems okay, RE instrumentation those seem like they could just be done with additional interaction steps and not need something special. |
I like where this is going. Couple questions/comments:
|
Responses:
|
In #12 I recurse through the interactors, and each one looks at the tagged tuple for an error, then calls the rollback. My implementation is a bit garbage because I was being hasty with my return values. Using the more formal |
Overview
This library has been great for building and maintaining large (API) applications in Elixir. However a few patterns have emerged that could be handled better.
Typical current use cases identified are:
Current pain points are:
Some Ideas
Lets look at Plug.Conn, Plug.Builder and Plug.Router for inspiration!
Breakdown of example
Idea is the same sort of pattern as plug. In particular you have an %Interaction{} data structure, then each Interactor really is just a function or module with call/2 function that accept the interaction and the opts and returns the Interaction.
All interaction chains are started with
Interactor.call/2
(/3?) which creates the %Interaction{} and calls call/2.With Interactor.Builder you get some convenience macros similar to plug builder. In particular you have the
interaction
macro which runs interactions in order as long as the previous interaction did not fail. In addition it gives you some options such as:assign_as
and:async
which provide simple patterns for common behavior. In addition when an interaction is not returned the return value becomes the result in the interaction.Built in interaction functions :insert, :update and :transaction also handle standard Repo calls and updating the interaction for you.
The text was updated successfully, but these errors were encountered: