-
Notifications
You must be signed in to change notification settings - Fork 5
Description
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:
- A simple create/update/delete of something.
- 1 + Afterward syncing to a socket.
- 1 (or 2) plus calling some async interactor in the background.
- Use multis for more complicated work flows
Current pain points are:
- After callbacks often really only relevant to success cases, but have to define both
- After callbacks sometimes need more then just the results
- No good way of hooking interactors together
- Lack of strict data structure gives me feeling of malaise.
Some Ideas
Lets look at Plug.Conn, Plug.Builder and Plug.Router for inspiration!
%Interaction{
assigns: %{},
results: nil,
success: false,
}
defmodule MyApp.CreateComment do
use Interactor.Builder
interaction MyApp.Auth.authorize, :create_comment
interaction :comment_changeset
interaction :insert, assign_as: :comment #built in! # but how does it know what to insert is it always from "result"?
interaction :sync_to_socket, async: true
interaction MyApp.UpdatePostCount, async: true
def comment_changeset(%Interaction{assigns: %{attributes: attrs}}}) do
%Comment{}
|> cast(attrs, [:title, :body])
|> Comment.validate # if non Interaction is returned it is put into results?
end
# Comment now both is result and is added to assigns.
def sync_to_socket(int = %{Interaction{results: %Comment{}, assigns: %Comment{}}}) do
# Push to socket.
end
end
defmodule CommentController do
# ...
def create(conn, params) do
case Interactor.call(CreateComment, %{attributes: params["comment"]}) do
%{success: true, results: comment} -> render(conn, :show, data: comment)
%{success: false, results: changeset} -> render(conn, :errors, data: changeset)
end
end
# ...
endBreakdown 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.